Skip to main content

sqlparser/ast/
table_constraints.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! SQL Abstract Syntax Tree (AST) types for table constraints
19
20use crate::ast::{
21    display_comma_separated, display_separated, ConstraintCharacteristics,
22    ConstraintReferenceMatchKind, Expr, Ident, IndexColumn, IndexOption, IndexType,
23    KeyOrIndexDisplay, NullsDistinctOption, ObjectName, ReferentialAction,
24};
25use crate::tokenizer::Span;
26use core::fmt;
27
28#[cfg(not(feature = "std"))]
29use alloc::{boxed::Box, string::String, vec::Vec};
30
31#[cfg(feature = "serde")]
32use serde::{Deserialize, Serialize};
33
34#[cfg(feature = "visitor")]
35use sqlparser_derive::{Visit, VisitMut};
36
37/// A table-level constraint, specified in a `CREATE TABLE` or an
38/// `ALTER TABLE ADD <constraint>` statement.
39#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
42pub enum TableConstraint {
43    /// MySQL [definition][1] for `UNIQUE` constraints statements:\
44    /// * `[CONSTRAINT [<name>]] UNIQUE <index_type_display> [<index_name>] [index_type] (<columns>) <index_options>`
45    ///
46    /// where:
47    /// * [index_type][2] is `USING {BTREE | HASH}`
48    /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
49    /// * [index_type_display][4] is `[INDEX | KEY]`
50    ///
51    /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
52    /// [2]: IndexType
53    /// [3]: IndexOption
54    /// [4]: KeyOrIndexDisplay
55    Unique(UniqueConstraint),
56    /// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\
57    /// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
58    ///
59    /// Actually the specification have no `[index_name]` but the next query will complete successfully:
60    /// ```sql
61    /// CREATE TABLE unspec_table (
62    ///   xid INT NOT NULL,
63    ///   CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid)
64    /// );
65    /// ```
66    ///
67    /// where:
68    /// * [index_type][2] is `USING {BTREE | HASH}`
69    /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
70    ///
71    /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
72    /// [2]: IndexType
73    /// [3]: IndexOption
74    PrimaryKey(PrimaryKeyConstraint),
75    /// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
76    /// REFERENCES <foreign_table> (<referred_columns>)
77    /// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
78    ///   [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
79    /// }`).
80    ForeignKey(ForeignKeyConstraint),
81    /// `[ CONSTRAINT <name> ] CHECK (<expr>) [NO INHERIT] [[NOT] ENFORCED]`
82    Check(CheckConstraint),
83    /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage
84    /// is restricted to MySQL, as no other dialects that support this syntax were found.
85    ///
86    /// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...`
87    ///
88    /// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
89    Index(IndexConstraint),
90    /// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
91    /// and MySQL displays both the same way, it is part of this definition as well.
92    ///
93    /// Supported syntax:
94    ///
95    /// ```markdown
96    /// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
97    ///
98    /// key_part: col_name
99    /// ```
100    ///
101    /// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
102    /// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html
103    FulltextOrSpatial(FullTextOrSpatialConstraint),
104    /// PostgreSQL [definition][1] for promoting an existing unique index to a
105    /// `PRIMARY KEY` constraint:
106    ///
107    /// `[ CONSTRAINT constraint_name ] PRIMARY KEY USING INDEX index_name
108    ///   [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
109    ///
110    /// [1]: https://www.postgresql.org/docs/current/sql-altertable.html
111    PrimaryKeyUsingIndex(ConstraintUsingIndex),
112    /// PostgreSQL [definition][1] for promoting an existing unique index to a
113    /// `UNIQUE` constraint:
114    ///
115    /// `[ CONSTRAINT constraint_name ] UNIQUE USING INDEX index_name
116    ///   [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
117    ///
118    /// [1]: https://www.postgresql.org/docs/current/sql-altertable.html
119    UniqueUsingIndex(ConstraintUsingIndex),
120    /// `EXCLUDE` constraint.
121    ///
122    /// `[ CONSTRAINT <name> ] EXCLUDE [ USING <index_method> ] ( <element> WITH <operator> [, ...] ) [ INCLUDE (<cols>) ] [ WHERE (<predicate>) ]`
123    ///
124    /// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE)
125    Exclude(ExcludeConstraint),
126}
127
128impl From<UniqueConstraint> for TableConstraint {
129    fn from(constraint: UniqueConstraint) -> Self {
130        TableConstraint::Unique(constraint)
131    }
132}
133
134impl From<PrimaryKeyConstraint> for TableConstraint {
135    fn from(constraint: PrimaryKeyConstraint) -> Self {
136        TableConstraint::PrimaryKey(constraint)
137    }
138}
139
140impl From<ForeignKeyConstraint> for TableConstraint {
141    fn from(constraint: ForeignKeyConstraint) -> Self {
142        TableConstraint::ForeignKey(constraint)
143    }
144}
145
146impl From<CheckConstraint> for TableConstraint {
147    fn from(constraint: CheckConstraint) -> Self {
148        TableConstraint::Check(constraint)
149    }
150}
151
152impl From<IndexConstraint> for TableConstraint {
153    fn from(constraint: IndexConstraint) -> Self {
154        TableConstraint::Index(constraint)
155    }
156}
157
158impl From<FullTextOrSpatialConstraint> for TableConstraint {
159    fn from(constraint: FullTextOrSpatialConstraint) -> Self {
160        TableConstraint::FulltextOrSpatial(constraint)
161    }
162}
163
164impl From<ExcludeConstraint> for TableConstraint {
165    fn from(constraint: ExcludeConstraint) -> Self {
166        TableConstraint::Exclude(constraint)
167    }
168}
169
170impl fmt::Display for TableConstraint {
171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172        match self {
173            TableConstraint::Unique(constraint) => constraint.fmt(f),
174            TableConstraint::PrimaryKey(constraint) => constraint.fmt(f),
175            TableConstraint::ForeignKey(constraint) => constraint.fmt(f),
176            TableConstraint::Check(constraint) => constraint.fmt(f),
177            TableConstraint::Index(constraint) => constraint.fmt(f),
178            TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f),
179            TableConstraint::PrimaryKeyUsingIndex(c) => c.fmt_with_keyword(f, "PRIMARY KEY"),
180            TableConstraint::UniqueUsingIndex(c) => c.fmt_with_keyword(f, "UNIQUE"),
181            TableConstraint::Exclude(constraint) => constraint.fmt(f),
182        }
183    }
184}
185
186#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
187#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
188#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
189/// A `CHECK` constraint (`[ CONSTRAINT <name> ] CHECK (<expr>) [NO INHERIT] [[NOT] ENFORCED]`).
190pub struct CheckConstraint {
191    /// Optional constraint name.
192    pub name: Option<Ident>,
193    /// The boolean expression the CHECK constraint enforces.
194    pub expr: Box<Expr>,
195    /// PostgreSQL-specific `NO INHERIT` flag: child tables do not inherit the constraint.
196    /// <https://www.postgresql.org/docs/current/sql-createtable.html>
197    pub no_inherit: bool,
198    /// MySQL-specific `ENFORCED` / `NOT ENFORCED` flag.
199    /// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
200    pub enforced: Option<bool>,
201}
202
203impl fmt::Display for CheckConstraint {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        use crate::ast::ddl::display_constraint_name;
206        write!(
207            f,
208            "{}CHECK ({})",
209            display_constraint_name(&self.name),
210            self.expr
211        )?;
212        if self.no_inherit {
213            write!(f, " NO INHERIT")?;
214        }
215        if let Some(b) = self.enforced {
216            write!(f, " {}", if b { "ENFORCED" } else { "NOT ENFORCED" })?;
217        }
218        Ok(())
219    }
220}
221
222impl crate::ast::Spanned for CheckConstraint {
223    fn span(&self) -> Span {
224        self.expr
225            .span()
226            .union_opt(&self.name.as_ref().map(|i| i.span))
227    }
228}
229
230/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
231/// REFERENCES <foreign_table> (<referred_columns>) [ MATCH { FULL | PARTIAL | SIMPLE } ]
232/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
233///   [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
234/// }`).
235#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
236#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
237#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
238pub struct ForeignKeyConstraint {
239    /// Optional constraint name.
240    pub name: Option<Ident>,
241    /// MySQL-specific index name associated with the foreign key.
242    /// <https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html>
243    pub index_name: Option<Ident>,
244    /// Columns in the local table that participate in the foreign key.
245    pub columns: Vec<Ident>,
246    /// Referenced foreign table name.
247    pub foreign_table: ObjectName,
248    /// Columns in the referenced table.
249    pub referred_columns: Vec<Ident>,
250    /// Action to perform `ON DELETE`.
251    pub on_delete: Option<ReferentialAction>,
252    /// Action to perform `ON UPDATE`.
253    pub on_update: Option<ReferentialAction>,
254    /// Optional `MATCH` kind (FULL | PARTIAL | SIMPLE).
255    pub match_kind: Option<ConstraintReferenceMatchKind>,
256    /// Optional characteristics (e.g., `DEFERRABLE`).
257    pub characteristics: Option<ConstraintCharacteristics>,
258}
259
260impl fmt::Display for ForeignKeyConstraint {
261    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262        use crate::ast::ddl::{display_constraint_name, display_option_spaced};
263        write!(
264            f,
265            "{}FOREIGN KEY{} ({}) REFERENCES {}",
266            display_constraint_name(&self.name),
267            display_option_spaced(&self.index_name),
268            display_comma_separated(&self.columns),
269            self.foreign_table,
270        )?;
271        if !self.referred_columns.is_empty() {
272            write!(f, "({})", display_comma_separated(&self.referred_columns))?;
273        }
274        if let Some(match_kind) = &self.match_kind {
275            write!(f, " {match_kind}")?;
276        }
277        if let Some(action) = &self.on_delete {
278            write!(f, " ON DELETE {action}")?;
279        }
280        if let Some(action) = &self.on_update {
281            write!(f, " ON UPDATE {action}")?;
282        }
283        if let Some(characteristics) = &self.characteristics {
284            write!(f, " {characteristics}")?;
285        }
286        Ok(())
287    }
288}
289
290impl crate::ast::Spanned for ForeignKeyConstraint {
291    fn span(&self) -> Span {
292        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
293            Span::union_iter(iter)
294        }
295
296        union_spans(
297            self.name
298                .iter()
299                .map(|i| i.span)
300                .chain(self.index_name.iter().map(|i| i.span))
301                .chain(self.columns.iter().map(|i| i.span))
302                .chain(core::iter::once(self.foreign_table.span()))
303                .chain(self.referred_columns.iter().map(|i| i.span))
304                .chain(self.on_delete.iter().map(|i| i.span()))
305                .chain(self.on_update.iter().map(|i| i.span()))
306                .chain(self.characteristics.iter().map(|i| i.span())),
307        )
308    }
309}
310
311/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
312/// and MySQL displays both the same way, it is part of this definition as well.
313///
314/// Supported syntax:
315///
316/// ```markdown
317/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
318///
319/// key_part: col_name
320/// ```
321///
322/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
323/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html
324#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
325#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
326#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
327pub struct FullTextOrSpatialConstraint {
328    /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition.
329    pub fulltext: bool,
330    /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
331    pub index_type_display: KeyOrIndexDisplay,
332    /// Optional index name.
333    pub opt_index_name: Option<Ident>,
334    /// Referred column identifier list.
335    pub columns: Vec<IndexColumn>,
336}
337
338impl fmt::Display for FullTextOrSpatialConstraint {
339    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
340        if self.fulltext {
341            write!(f, "FULLTEXT")?;
342        } else {
343            write!(f, "SPATIAL")?;
344        }
345
346        write!(f, "{:>}", self.index_type_display)?;
347
348        if let Some(name) = &self.opt_index_name {
349            write!(f, " {name}")?;
350        }
351
352        write!(f, " ({})", display_comma_separated(&self.columns))?;
353
354        Ok(())
355    }
356}
357
358impl crate::ast::Spanned for FullTextOrSpatialConstraint {
359    fn span(&self) -> Span {
360        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
361            Span::union_iter(iter)
362        }
363
364        union_spans(
365            self.opt_index_name
366                .iter()
367                .map(|i| i.span)
368                .chain(self.columns.iter().map(|i| i.span())),
369        )
370    }
371}
372
373/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage
374/// is restricted to MySQL, as no other dialects that support this syntax were found.
375///
376/// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...`
377///
378/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
379#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
380#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
381#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
382pub struct IndexConstraint {
383    /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax.
384    pub display_as_key: bool,
385    /// Index name.
386    pub name: Option<Ident>,
387    /// Optional [index type][1].
388    ///
389    /// [1]: IndexType
390    pub index_type: Option<IndexType>,
391    /// Referred column identifier list.
392    pub columns: Vec<IndexColumn>,
393    /// Optional index options such as `USING`; see [`IndexOption`].
394    /// Options applied to the index (e.g., `COMMENT`, `WITH` options).
395    pub index_options: Vec<IndexOption>,
396}
397
398impl fmt::Display for IndexConstraint {
399    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
400        write!(f, "{}", if self.display_as_key { "KEY" } else { "INDEX" })?;
401        if let Some(name) = &self.name {
402            write!(f, " {name}")?;
403        }
404        if let Some(index_type) = &self.index_type {
405            write!(f, " USING {index_type}")?;
406        }
407        write!(f, " ({})", display_comma_separated(&self.columns))?;
408        if !self.index_options.is_empty() {
409            write!(f, " {}", display_comma_separated(&self.index_options))?;
410        }
411        Ok(())
412    }
413}
414
415impl crate::ast::Spanned for IndexConstraint {
416    fn span(&self) -> Span {
417        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
418            Span::union_iter(iter)
419        }
420
421        union_spans(
422            self.name
423                .iter()
424                .map(|i| i.span)
425                .chain(self.columns.iter().map(|i| i.span())),
426        )
427    }
428}
429
430/// MySQL [definition][1] for `PRIMARY KEY` constraints statements:
431/// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
432///
433/// Actually the specification have no `[index_name]` but the next query will complete successfully:
434/// ```sql
435/// CREATE TABLE unspec_table (
436///   xid INT NOT NULL,
437///   CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid)
438/// );
439/// ```
440///
441/// where:
442/// * [index_type][2] is `USING {BTREE | HASH}`
443/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
444///
445/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
446/// [2]: IndexType
447/// [3]: IndexOption
448#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
449#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
450#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
451pub struct PrimaryKeyConstraint {
452    /// Constraint name.
453    ///
454    /// Can be not the same as `index_name`
455    pub name: Option<Ident>,
456    /// Index name
457    pub index_name: Option<Ident>,
458    /// Optional `USING` of [index type][1] statement before columns.
459    ///
460    /// [1]: IndexType
461    pub index_type: Option<IndexType>,
462    /// Identifiers of the columns that form the primary key.
463    pub columns: Vec<IndexColumn>,
464    /// INCLUDE clause: <https://www.postgresql.org/docs/current/sql-createtable.html>
465    pub include: Vec<Ident>,
466    /// Optional index options such as `USING`.
467    pub index_options: Vec<IndexOption>,
468    /// Optional characteristics like `DEFERRABLE`.
469    pub characteristics: Option<ConstraintCharacteristics>,
470}
471
472impl fmt::Display for PrimaryKeyConstraint {
473    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
474        use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced};
475        write!(
476            f,
477            "{}PRIMARY KEY{}{} ({})",
478            display_constraint_name(&self.name),
479            display_option_spaced(&self.index_name),
480            display_option(" USING ", "", &self.index_type),
481            display_comma_separated(&self.columns),
482        )?;
483
484        if !self.include.is_empty() {
485            write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?;
486        }
487
488        if !self.index_options.is_empty() {
489            write!(f, " {}", display_separated(&self.index_options, " "))?;
490        }
491
492        write!(f, "{}", display_option_spaced(&self.characteristics))?;
493        Ok(())
494    }
495}
496
497impl crate::ast::Spanned for PrimaryKeyConstraint {
498    fn span(&self) -> Span {
499        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
500            Span::union_iter(iter)
501        }
502
503        union_spans(
504            self.name
505                .iter()
506                .map(|i| i.span)
507                .chain(self.index_name.iter().map(|i| i.span))
508                .chain(self.columns.iter().map(|i| i.span()))
509                .chain(self.include.iter().map(|i| i.span))
510                .chain(self.characteristics.iter().map(|i| i.span())),
511        )
512    }
513}
514
515#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
516#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
517#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
518/// Unique constraint definition.
519pub struct UniqueConstraint {
520    /// Constraint name.
521    ///
522    /// Can be not the same as `index_name`
523    pub name: Option<Ident>,
524    /// Index name
525    pub index_name: Option<Ident>,
526    /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
527    pub index_type_display: KeyOrIndexDisplay,
528    /// Optional `USING` of [index type][1] statement before columns.
529    ///
530    /// [1]: IndexType
531    pub index_type: Option<IndexType>,
532    /// Identifiers of the columns that are unique.
533    pub columns: Vec<IndexColumn>,
534    /// INCLUDE clause: <https://www.postgresql.org/docs/current/sql-createtable.html>
535    pub include: Vec<Ident>,
536    /// Optional index options such as `USING`.
537    pub index_options: Vec<IndexOption>,
538    /// Optional characteristics like `DEFERRABLE`.
539    pub characteristics: Option<ConstraintCharacteristics>,
540    /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
541    pub nulls_distinct: NullsDistinctOption,
542}
543
544impl fmt::Display for UniqueConstraint {
545    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
546        use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced};
547        write!(
548            f,
549            "{}UNIQUE{}{:>}{}{} ({})",
550            display_constraint_name(&self.name),
551            self.nulls_distinct,
552            self.index_type_display,
553            display_option_spaced(&self.index_name),
554            display_option(" USING ", "", &self.index_type),
555            display_comma_separated(&self.columns),
556        )?;
557
558        if !self.include.is_empty() {
559            write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?;
560        }
561
562        if !self.index_options.is_empty() {
563            write!(f, " {}", display_separated(&self.index_options, " "))?;
564        }
565
566        write!(f, "{}", display_option_spaced(&self.characteristics))?;
567        Ok(())
568    }
569}
570
571impl crate::ast::Spanned for UniqueConstraint {
572    fn span(&self) -> Span {
573        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
574            Span::union_iter(iter)
575        }
576
577        union_spans(
578            self.name
579                .iter()
580                .map(|i| i.span)
581                .chain(self.index_name.iter().map(|i| i.span))
582                .chain(self.columns.iter().map(|i| i.span()))
583                .chain(self.include.iter().map(|i| i.span))
584                .chain(self.characteristics.iter().map(|i| i.span())),
585        )
586    }
587}
588
589/// PostgreSQL constraint that promotes an existing unique index to a table constraint.
590///
591/// `[ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name
592///   [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
593///
594/// See <https://www.postgresql.org/docs/current/sql-altertable.html>
595#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
596#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
597#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
598pub struct ConstraintUsingIndex {
599    /// Optional constraint name.
600    pub name: Option<Ident>,
601    /// The name of the existing unique index to promote.
602    pub index_name: Ident,
603    /// Optional characteristics like `DEFERRABLE`.
604    pub characteristics: Option<ConstraintCharacteristics>,
605}
606
607impl ConstraintUsingIndex {
608    /// Format as `[CONSTRAINT name] <keyword> USING INDEX index_name [characteristics]`.
609    pub fn fmt_with_keyword(&self, f: &mut fmt::Formatter, keyword: &str) -> fmt::Result {
610        use crate::ast::ddl::{display_constraint_name, display_option_spaced};
611        write!(
612            f,
613            "{}{} USING INDEX {}",
614            display_constraint_name(&self.name),
615            keyword,
616            self.index_name,
617        )?;
618        write!(f, "{}", display_option_spaced(&self.characteristics))?;
619        Ok(())
620    }
621}
622
623impl crate::ast::Spanned for ConstraintUsingIndex {
624    fn span(&self) -> Span {
625        let start = self
626            .name
627            .as_ref()
628            .map(|i| i.span)
629            .unwrap_or(self.index_name.span);
630        let end = self
631            .characteristics
632            .as_ref()
633            .map(|c| c.span())
634            .unwrap_or(self.index_name.span);
635        start.union(&end)
636    }
637}
638
639/// The operator that follows `WITH` in an `EXCLUDE` constraint element.
640///
641/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE)
642#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
643#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
644#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
645pub enum ExcludeConstraintOperator {
646    /// A single operator token, e.g. `=`, `&&`, `<->`.
647    Token(String),
648    /// Postgres schema-qualified form: `OPERATOR(schema.op)`.
649    PGOperator(Vec<String>),
650}
651
652impl fmt::Display for ExcludeConstraintOperator {
653    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
654        match self {
655            ExcludeConstraintOperator::Token(token) => f.write_str(token),
656            ExcludeConstraintOperator::PGOperator(parts) => {
657                write!(f, "OPERATOR({})", display_separated(parts, "."))
658            }
659        }
660    }
661}
662
663/// One element in an `EXCLUDE` constraint's element list.
664///
665/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE)
666#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
667#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
668#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
669pub struct ExcludeConstraintElement {
670    /// The index column (`{ column_name | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]`).
671    pub column: IndexColumn,
672    /// The exclusion operator.
673    pub operator: ExcludeConstraintOperator,
674}
675
676impl fmt::Display for ExcludeConstraintElement {
677    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
678        write!(f, "{} WITH {}", self.column, self.operator)
679    }
680}
681
682/// An `EXCLUDE` constraint.
683///
684/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE)
685#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
686#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
687#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
688pub struct ExcludeConstraint {
689    /// Optional constraint name.
690    pub name: Option<Ident>,
691    /// Optional index method (e.g. `gist`, `spgist`).
692    pub index_method: Option<Ident>,
693    /// The list of index expressions with their exclusion operators.
694    pub elements: Vec<ExcludeConstraintElement>,
695    /// Optional list of additional columns to include in the index.
696    pub include: Vec<Ident>,
697    /// Optional `WHERE` predicate to restrict the constraint to a subset of rows.
698    pub where_clause: Option<Box<Expr>>,
699    /// Optional constraint characteristics like `DEFERRABLE`.
700    pub characteristics: Option<ConstraintCharacteristics>,
701}
702
703impl fmt::Display for ExcludeConstraint {
704    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
705        use crate::ast::ddl::display_constraint_name;
706        write!(f, "{}EXCLUDE", display_constraint_name(&self.name))?;
707        if let Some(method) = &self.index_method {
708            write!(f, " USING {method}")?;
709        }
710        write!(f, " ({})", display_comma_separated(&self.elements))?;
711        if !self.include.is_empty() {
712            write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?;
713        }
714        if let Some(predicate) = &self.where_clause {
715            write!(f, " WHERE ({predicate})")?;
716        }
717        if let Some(characteristics) = &self.characteristics {
718            write!(f, " {characteristics}")?;
719        }
720        Ok(())
721    }
722}
723
724impl crate::ast::Spanned for ExcludeConstraint {
725    fn span(&self) -> Span {
726        Span::union_iter(
727            self.name
728                .iter()
729                .map(|i| i.span)
730                .chain(self.index_method.iter().map(|i| i.span))
731                .chain(self.elements.iter().map(|e| e.span()))
732                .chain(self.include.iter().map(|i| i.span))
733                .chain(self.where_clause.iter().map(|e| e.span()))
734                .chain(self.characteristics.iter().map(|c| c.span())),
735        )
736    }
737}