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>) [[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>) [[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    /// MySQL-specific `ENFORCED` / `NOT ENFORCED` flag.
196    /// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
197    pub enforced: Option<bool>,
198}
199
200impl fmt::Display for CheckConstraint {
201    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202        use crate::ast::ddl::display_constraint_name;
203        write!(
204            f,
205            "{}CHECK ({})",
206            display_constraint_name(&self.name),
207            self.expr
208        )?;
209        if let Some(b) = self.enforced {
210            write!(f, " {}", if b { "ENFORCED" } else { "NOT ENFORCED" })
211        } else {
212            Ok(())
213        }
214    }
215}
216
217impl crate::ast::Spanned for CheckConstraint {
218    fn span(&self) -> Span {
219        self.expr
220            .span()
221            .union_opt(&self.name.as_ref().map(|i| i.span))
222    }
223}
224
225/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
226/// REFERENCES <foreign_table> (<referred_columns>) [ MATCH { FULL | PARTIAL | SIMPLE } ]
227/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
228///   [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
229/// }`).
230#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
231#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
232#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
233pub struct ForeignKeyConstraint {
234    /// Optional constraint name.
235    pub name: Option<Ident>,
236    /// MySQL-specific index name associated with the foreign key.
237    /// <https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html>
238    pub index_name: Option<Ident>,
239    /// Columns in the local table that participate in the foreign key.
240    pub columns: Vec<Ident>,
241    /// Referenced foreign table name.
242    pub foreign_table: ObjectName,
243    /// Columns in the referenced table.
244    pub referred_columns: Vec<Ident>,
245    /// Action to perform `ON DELETE`.
246    pub on_delete: Option<ReferentialAction>,
247    /// Action to perform `ON UPDATE`.
248    pub on_update: Option<ReferentialAction>,
249    /// Optional `MATCH` kind (FULL | PARTIAL | SIMPLE).
250    pub match_kind: Option<ConstraintReferenceMatchKind>,
251    /// Optional characteristics (e.g., `DEFERRABLE`).
252    pub characteristics: Option<ConstraintCharacteristics>,
253}
254
255impl fmt::Display for ForeignKeyConstraint {
256    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257        use crate::ast::ddl::{display_constraint_name, display_option_spaced};
258        write!(
259            f,
260            "{}FOREIGN KEY{} ({}) REFERENCES {}",
261            display_constraint_name(&self.name),
262            display_option_spaced(&self.index_name),
263            display_comma_separated(&self.columns),
264            self.foreign_table,
265        )?;
266        if !self.referred_columns.is_empty() {
267            write!(f, "({})", display_comma_separated(&self.referred_columns))?;
268        }
269        if let Some(match_kind) = &self.match_kind {
270            write!(f, " {match_kind}")?;
271        }
272        if let Some(action) = &self.on_delete {
273            write!(f, " ON DELETE {action}")?;
274        }
275        if let Some(action) = &self.on_update {
276            write!(f, " ON UPDATE {action}")?;
277        }
278        if let Some(characteristics) = &self.characteristics {
279            write!(f, " {characteristics}")?;
280        }
281        Ok(())
282    }
283}
284
285impl crate::ast::Spanned for ForeignKeyConstraint {
286    fn span(&self) -> Span {
287        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
288            Span::union_iter(iter)
289        }
290
291        union_spans(
292            self.name
293                .iter()
294                .map(|i| i.span)
295                .chain(self.index_name.iter().map(|i| i.span))
296                .chain(self.columns.iter().map(|i| i.span))
297                .chain(core::iter::once(self.foreign_table.span()))
298                .chain(self.referred_columns.iter().map(|i| i.span))
299                .chain(self.on_delete.iter().map(|i| i.span()))
300                .chain(self.on_update.iter().map(|i| i.span()))
301                .chain(self.characteristics.iter().map(|i| i.span())),
302        )
303    }
304}
305
306/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
307/// and MySQL displays both the same way, it is part of this definition as well.
308///
309/// Supported syntax:
310///
311/// ```markdown
312/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
313///
314/// key_part: col_name
315/// ```
316///
317/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
318/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html
319#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
320#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
321#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
322pub struct FullTextOrSpatialConstraint {
323    /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition.
324    pub fulltext: bool,
325    /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
326    pub index_type_display: KeyOrIndexDisplay,
327    /// Optional index name.
328    pub opt_index_name: Option<Ident>,
329    /// Referred column identifier list.
330    pub columns: Vec<IndexColumn>,
331}
332
333impl fmt::Display for FullTextOrSpatialConstraint {
334    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
335        if self.fulltext {
336            write!(f, "FULLTEXT")?;
337        } else {
338            write!(f, "SPATIAL")?;
339        }
340
341        write!(f, "{:>}", self.index_type_display)?;
342
343        if let Some(name) = &self.opt_index_name {
344            write!(f, " {name}")?;
345        }
346
347        write!(f, " ({})", display_comma_separated(&self.columns))?;
348
349        Ok(())
350    }
351}
352
353impl crate::ast::Spanned for FullTextOrSpatialConstraint {
354    fn span(&self) -> Span {
355        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
356            Span::union_iter(iter)
357        }
358
359        union_spans(
360            self.opt_index_name
361                .iter()
362                .map(|i| i.span)
363                .chain(self.columns.iter().map(|i| i.span())),
364        )
365    }
366}
367
368/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage
369/// is restricted to MySQL, as no other dialects that support this syntax were found.
370///
371/// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...`
372///
373/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
374#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
375#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
376#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
377pub struct IndexConstraint {
378    /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax.
379    pub display_as_key: bool,
380    /// Index name.
381    pub name: Option<Ident>,
382    /// Optional [index type][1].
383    ///
384    /// [1]: IndexType
385    pub index_type: Option<IndexType>,
386    /// Referred column identifier list.
387    pub columns: Vec<IndexColumn>,
388    /// Optional index options such as `USING`; see [`IndexOption`].
389    /// Options applied to the index (e.g., `COMMENT`, `WITH` options).
390    pub index_options: Vec<IndexOption>,
391}
392
393impl fmt::Display for IndexConstraint {
394    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395        write!(f, "{}", if self.display_as_key { "KEY" } else { "INDEX" })?;
396        if let Some(name) = &self.name {
397            write!(f, " {name}")?;
398        }
399        if let Some(index_type) = &self.index_type {
400            write!(f, " USING {index_type}")?;
401        }
402        write!(f, " ({})", display_comma_separated(&self.columns))?;
403        if !self.index_options.is_empty() {
404            write!(f, " {}", display_comma_separated(&self.index_options))?;
405        }
406        Ok(())
407    }
408}
409
410impl crate::ast::Spanned for IndexConstraint {
411    fn span(&self) -> Span {
412        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
413            Span::union_iter(iter)
414        }
415
416        union_spans(
417            self.name
418                .iter()
419                .map(|i| i.span)
420                .chain(self.columns.iter().map(|i| i.span())),
421        )
422    }
423}
424
425/// MySQL [definition][1] for `PRIMARY KEY` constraints statements:
426/// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
427///
428/// Actually the specification have no `[index_name]` but the next query will complete successfully:
429/// ```sql
430/// CREATE TABLE unspec_table (
431///   xid INT NOT NULL,
432///   CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid)
433/// );
434/// ```
435///
436/// where:
437/// * [index_type][2] is `USING {BTREE | HASH}`
438/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
439///
440/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
441/// [2]: IndexType
442/// [3]: IndexOption
443#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
444#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
445#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
446pub struct PrimaryKeyConstraint {
447    /// Constraint name.
448    ///
449    /// Can be not the same as `index_name`
450    pub name: Option<Ident>,
451    /// Index name
452    pub index_name: Option<Ident>,
453    /// Optional `USING` of [index type][1] statement before columns.
454    ///
455    /// [1]: IndexType
456    pub index_type: Option<IndexType>,
457    /// Identifiers of the columns that form the primary key.
458    pub columns: Vec<IndexColumn>,
459    /// Optional index options such as `USING`.
460    pub index_options: Vec<IndexOption>,
461    /// Optional characteristics like `DEFERRABLE`.
462    pub characteristics: Option<ConstraintCharacteristics>,
463}
464
465impl fmt::Display for PrimaryKeyConstraint {
466    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
467        use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced};
468        write!(
469            f,
470            "{}PRIMARY KEY{}{} ({})",
471            display_constraint_name(&self.name),
472            display_option_spaced(&self.index_name),
473            display_option(" USING ", "", &self.index_type),
474            display_comma_separated(&self.columns),
475        )?;
476
477        if !self.index_options.is_empty() {
478            write!(f, " {}", display_separated(&self.index_options, " "))?;
479        }
480
481        write!(f, "{}", display_option_spaced(&self.characteristics))?;
482        Ok(())
483    }
484}
485
486impl crate::ast::Spanned for PrimaryKeyConstraint {
487    fn span(&self) -> Span {
488        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
489            Span::union_iter(iter)
490        }
491
492        union_spans(
493            self.name
494                .iter()
495                .map(|i| i.span)
496                .chain(self.index_name.iter().map(|i| i.span))
497                .chain(self.columns.iter().map(|i| i.span()))
498                .chain(self.characteristics.iter().map(|i| i.span())),
499        )
500    }
501}
502
503#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
504#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
505#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
506/// Unique constraint definition.
507pub struct UniqueConstraint {
508    /// Constraint name.
509    ///
510    /// Can be not the same as `index_name`
511    pub name: Option<Ident>,
512    /// Index name
513    pub index_name: Option<Ident>,
514    /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
515    pub index_type_display: KeyOrIndexDisplay,
516    /// Optional `USING` of [index type][1] statement before columns.
517    ///
518    /// [1]: IndexType
519    pub index_type: Option<IndexType>,
520    /// Identifiers of the columns that are unique.
521    pub columns: Vec<IndexColumn>,
522    /// Optional index options such as `USING`.
523    pub index_options: Vec<IndexOption>,
524    /// Optional characteristics like `DEFERRABLE`.
525    pub characteristics: Option<ConstraintCharacteristics>,
526    /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
527    pub nulls_distinct: NullsDistinctOption,
528}
529
530impl fmt::Display for UniqueConstraint {
531    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
532        use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced};
533        write!(
534            f,
535            "{}UNIQUE{}{:>}{}{} ({})",
536            display_constraint_name(&self.name),
537            self.nulls_distinct,
538            self.index_type_display,
539            display_option_spaced(&self.index_name),
540            display_option(" USING ", "", &self.index_type),
541            display_comma_separated(&self.columns),
542        )?;
543
544        if !self.index_options.is_empty() {
545            write!(f, " {}", display_separated(&self.index_options, " "))?;
546        }
547
548        write!(f, "{}", display_option_spaced(&self.characteristics))?;
549        Ok(())
550    }
551}
552
553impl crate::ast::Spanned for UniqueConstraint {
554    fn span(&self) -> Span {
555        fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
556            Span::union_iter(iter)
557        }
558
559        union_spans(
560            self.name
561                .iter()
562                .map(|i| i.span)
563                .chain(self.index_name.iter().map(|i| i.span))
564                .chain(self.columns.iter().map(|i| i.span()))
565                .chain(self.characteristics.iter().map(|i| i.span())),
566        )
567    }
568}
569
570/// PostgreSQL constraint that promotes an existing unique index to a table constraint.
571///
572/// `[ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name
573///   [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
574///
575/// See <https://www.postgresql.org/docs/current/sql-altertable.html>
576#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
577#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
578#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
579pub struct ConstraintUsingIndex {
580    /// Optional constraint name.
581    pub name: Option<Ident>,
582    /// The name of the existing unique index to promote.
583    pub index_name: Ident,
584    /// Optional characteristics like `DEFERRABLE`.
585    pub characteristics: Option<ConstraintCharacteristics>,
586}
587
588impl ConstraintUsingIndex {
589    /// Format as `[CONSTRAINT name] <keyword> USING INDEX index_name [characteristics]`.
590    pub fn fmt_with_keyword(&self, f: &mut fmt::Formatter, keyword: &str) -> fmt::Result {
591        use crate::ast::ddl::{display_constraint_name, display_option_spaced};
592        write!(
593            f,
594            "{}{} USING INDEX {}",
595            display_constraint_name(&self.name),
596            keyword,
597            self.index_name,
598        )?;
599        write!(f, "{}", display_option_spaced(&self.characteristics))?;
600        Ok(())
601    }
602}
603
604impl crate::ast::Spanned for ConstraintUsingIndex {
605    fn span(&self) -> Span {
606        let start = self
607            .name
608            .as_ref()
609            .map(|i| i.span)
610            .unwrap_or(self.index_name.span);
611        let end = self
612            .characteristics
613            .as_ref()
614            .map(|c| c.span())
615            .unwrap_or(self.index_name.span);
616        start.union(&end)
617    }
618}
619
620/// The operator that follows `WITH` in an `EXCLUDE` constraint element.
621///
622/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE)
623#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
624#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
625#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
626pub enum ExcludeConstraintOperator {
627    /// A single operator token, e.g. `=`, `&&`, `<->`.
628    Token(String),
629    /// Postgres schema-qualified form: `OPERATOR(schema.op)`.
630    PGOperator(Vec<String>),
631}
632
633impl fmt::Display for ExcludeConstraintOperator {
634    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
635        match self {
636            ExcludeConstraintOperator::Token(token) => f.write_str(token),
637            ExcludeConstraintOperator::PGOperator(parts) => {
638                write!(f, "OPERATOR({})", display_separated(parts, "."))
639            }
640        }
641    }
642}
643
644/// One element in an `EXCLUDE` constraint's element list.
645///
646/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE)
647#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
648#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
649#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
650pub struct ExcludeConstraintElement {
651    /// The index column (`{ column_name | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]`).
652    pub column: IndexColumn,
653    /// The exclusion operator.
654    pub operator: ExcludeConstraintOperator,
655}
656
657impl fmt::Display for ExcludeConstraintElement {
658    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
659        write!(f, "{} WITH {}", self.column, self.operator)
660    }
661}
662
663/// An `EXCLUDE` constraint.
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 ExcludeConstraint {
670    /// Optional constraint name.
671    pub name: Option<Ident>,
672    /// Optional index method (e.g. `gist`, `spgist`).
673    pub index_method: Option<Ident>,
674    /// The list of index expressions with their exclusion operators.
675    pub elements: Vec<ExcludeConstraintElement>,
676    /// Optional list of additional columns to include in the index.
677    pub include: Vec<Ident>,
678    /// Optional `WHERE` predicate to restrict the constraint to a subset of rows.
679    pub where_clause: Option<Box<Expr>>,
680    /// Optional constraint characteristics like `DEFERRABLE`.
681    pub characteristics: Option<ConstraintCharacteristics>,
682}
683
684impl fmt::Display for ExcludeConstraint {
685    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
686        use crate::ast::ddl::display_constraint_name;
687        write!(f, "{}EXCLUDE", display_constraint_name(&self.name))?;
688        if let Some(method) = &self.index_method {
689            write!(f, " USING {method}")?;
690        }
691        write!(f, " ({})", display_comma_separated(&self.elements))?;
692        if !self.include.is_empty() {
693            write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?;
694        }
695        if let Some(predicate) = &self.where_clause {
696            write!(f, " WHERE ({predicate})")?;
697        }
698        if let Some(characteristics) = &self.characteristics {
699            write!(f, " {characteristics}")?;
700        }
701        Ok(())
702    }
703}
704
705impl crate::ast::Spanned for ExcludeConstraint {
706    fn span(&self) -> Span {
707        Span::union_iter(
708            self.name
709                .iter()
710                .map(|i| i.span)
711                .chain(self.index_method.iter().map(|i| i.span))
712                .chain(self.elements.iter().map(|e| e.span()))
713                .chain(self.include.iter().map(|i| i.span))
714                .chain(self.where_clause.iter().map(|e| e.span()))
715                .chain(self.characteristics.iter().map(|c| c.span())),
716        )
717    }
718}