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