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