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