From facbdfd4040419d4c54a0a4c2b1e33d11d13e142 Mon Sep 17 00:00:00 2001 From: Luca Date: Fri, 3 Oct 2025 11:49:04 +0200 Subject: [PATCH 1/7] Moved constraint variant outside of `TableConstraint` enum --- src/ast/ddl.rs | 285 ++++-------------- src/ast/mod.rs | 5 + src/ast/spans.rs | 83 +---- src/ast/table_constraints/check_constraint.rs | 64 ++++ .../foreign_key_constraint.rs | 99 ++++++ .../full_text_or_spatial_constraint.rs | 92 ++++++ src/ast/table_constraints/index_constraint.rs | 84 ++++++ src/ast/table_constraints/mod.rs | 31 ++ .../primary_key_constraint.rs | 107 +++++++ .../table_constraints/unique_constraint.rs | 95 ++++++ src/parser/mod.rs | 132 ++++---- src/test_utils.rs | 35 ++- tests/sqlparser_common.rs | 40 ++- tests/sqlparser_mysql.rs | 10 +- tests/sqlparser_postgres.rs | 33 +- 15 files changed, 789 insertions(+), 406 deletions(-) create mode 100644 src/ast/table_constraints/check_constraint.rs create mode 100644 src/ast/table_constraints/foreign_key_constraint.rs create mode 100644 src/ast/table_constraints/full_text_or_spatial_constraint.rs create mode 100644 src/ast/table_constraints/index_constraint.rs create mode 100644 src/ast/table_constraints/mod.rs create mode 100644 src/ast/table_constraints/primary_key_constraint.rs create mode 100644 src/ast/table_constraints/unique_constraint.rs diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index c4f769675..437ffc100 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -30,15 +30,19 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::value::escape_single_quote_string; use crate::ast::{ - display_comma_separated, display_separated, ArgMode, CommentDef, ConditionalStatements, - CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, DataType, - Expr, FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDeterminismSpecifier, - FunctionParallel, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, - InitializeKind, MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens, - OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind, RowAccessPolicy, - SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, TableVersion, Tag, - TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value, - ValueWithSpan, WrappedCollection, + display_comma_separated, display_separated, + table_constraints::{ + CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint, + PrimaryKeyConstraint, UniqueConstraint, + }, + ArgMode, CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing, + CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior, + FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle, + HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InitializeKind, MySQLColumnPosition, + ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, + Query, RefreshModeKind, RowAccessPolicy, SequenceOptions, Spanned, SqlOption, + StorageSerializationPolicy, TableVersion, Tag, TriggerEvent, TriggerExecBody, TriggerObject, + TriggerPeriod, TriggerReferencing, Value, ValueWithSpan, WrappedCollection, }; use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline}; use crate::keywords::Keyword; @@ -1047,26 +1051,7 @@ pub enum TableConstraint { /// [2]: IndexType /// [3]: IndexOption /// [4]: KeyOrIndexDisplay - Unique { - /// Constraint name. - /// - /// Can be not the same as `index_name` - name: Option, - /// Index name - index_name: Option, - /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. - index_type_display: KeyOrIndexDisplay, - /// Optional `USING` of [index type][1] statement before columns. - /// - /// [1]: IndexType - index_type: Option, - /// Identifiers of the columns that are unique. - columns: Vec, - index_options: Vec, - characteristics: Option, - /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` - nulls_distinct: NullsDistinctOption, - }, + Unique(UniqueConstraint), /// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\ /// * `[CONSTRAINT []] PRIMARY KEY [index_name] [index_type] () ` /// @@ -1085,67 +1070,22 @@ pub enum TableConstraint { /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html /// [2]: IndexType /// [3]: IndexOption - PrimaryKey { - /// Constraint name. - /// - /// Can be not the same as `index_name` - name: Option, - /// Index name - index_name: Option, - /// Optional `USING` of [index type][1] statement before columns. - /// - /// [1]: IndexType - index_type: Option, - /// Identifiers of the columns that form the primary key. - columns: Vec, - index_options: Vec, - characteristics: Option, - }, + PrimaryKey(PrimaryKeyConstraint), /// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () /// REFERENCES () /// { [ON DELETE ] [ON UPDATE ] | /// [ON UPDATE ] [ON DELETE ] /// }`). - ForeignKey { - name: Option, - /// MySQL-specific field - /// - index_name: Option, - columns: Vec, - foreign_table: ObjectName, - referred_columns: Vec, - on_delete: Option, - on_update: Option, - characteristics: Option, - }, + ForeignKey(ForeignKeyConstraint), /// `[ CONSTRAINT ] CHECK () [[NOT] ENFORCED]` - Check { - name: Option, - expr: Box, - /// MySQL-specific syntax - /// - enforced: Option, - }, + Check(CheckConstraint), /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage /// is restricted to MySQL, as no other dialects that support this syntax were found. /// /// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...` /// /// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html - Index { - /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax. - display_as_key: bool, - /// Index name. - name: Option, - /// Optional [index type][1]. - /// - /// [1]: IndexType - index_type: Option, - /// Referred column identifier list. - columns: Vec, - /// Optional index options such as `USING`; see [`IndexOption`]. - index_options: Vec, - }, + Index(IndexConstraint), /// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, /// and MySQL displays both the same way, it is part of this definition as well. /// @@ -1159,157 +1099,54 @@ pub enum TableConstraint { /// /// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html /// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html - FulltextOrSpatial { - /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition. - fulltext: bool, - /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. - index_type_display: KeyOrIndexDisplay, - /// Optional index name. - opt_index_name: Option, - /// Referred column identifier list. - columns: Vec, - }, + FulltextOrSpatial(FullTextOrSpatialConstraint), } -impl fmt::Display for TableConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - TableConstraint::Unique { - name, - index_name, - index_type_display, - index_type, - columns, - index_options, - characteristics, - nulls_distinct, - } => { - write!( - f, - "{}UNIQUE{nulls_distinct}{index_type_display:>}{}{} ({})", - display_constraint_name(name), - display_option_spaced(index_name), - display_option(" USING ", "", index_type), - display_comma_separated(columns), - )?; - - if !index_options.is_empty() { - write!(f, " {}", display_separated(index_options, " "))?; - } - - write!(f, "{}", display_option_spaced(characteristics))?; - Ok(()) - } - TableConstraint::PrimaryKey { - name, - index_name, - index_type, - columns, - index_options, - characteristics, - } => { - write!( - f, - "{}PRIMARY KEY{}{} ({})", - display_constraint_name(name), - display_option_spaced(index_name), - display_option(" USING ", "", index_type), - display_comma_separated(columns), - )?; +impl From for TableConstraint { + fn from(constraint: UniqueConstraint) -> Self { + TableConstraint::Unique(constraint) + } +} - if !index_options.is_empty() { - write!(f, " {}", display_separated(index_options, " "))?; - } +impl From for TableConstraint { + fn from(constraint: PrimaryKeyConstraint) -> Self { + TableConstraint::PrimaryKey(constraint) + } +} - write!(f, "{}", display_option_spaced(characteristics))?; - Ok(()) - } - TableConstraint::ForeignKey { - name, - index_name, - columns, - foreign_table, - referred_columns, - on_delete, - on_update, - characteristics, - } => { - write!( - f, - "{}FOREIGN KEY{} ({}) REFERENCES {}", - display_constraint_name(name), - display_option_spaced(index_name), - display_comma_separated(columns), - foreign_table, - )?; - if !referred_columns.is_empty() { - write!(f, "({})", display_comma_separated(referred_columns))?; - } - if let Some(action) = on_delete { - write!(f, " ON DELETE {action}")?; - } - if let Some(action) = on_update { - write!(f, " ON UPDATE {action}")?; - } - if let Some(characteristics) = characteristics { - write!(f, " {characteristics}")?; - } - Ok(()) - } - TableConstraint::Check { - name, - expr, - enforced, - } => { - write!(f, "{}CHECK ({})", display_constraint_name(name), expr)?; - if let Some(b) = enforced { - write!(f, " {}", if *b { "ENFORCED" } else { "NOT ENFORCED" }) - } else { - Ok(()) - } - } - TableConstraint::Index { - display_as_key, - name, - index_type, - columns, - index_options, - } => { - write!(f, "{}", if *display_as_key { "KEY" } else { "INDEX" })?; - if let Some(name) = name { - write!(f, " {name}")?; - } - if let Some(index_type) = index_type { - write!(f, " USING {index_type}")?; - } - write!(f, " ({})", display_comma_separated(columns))?; - if !index_options.is_empty() { - write!(f, " {}", display_comma_separated(index_options))?; - } - Ok(()) - } - Self::FulltextOrSpatial { - fulltext, - index_type_display, - opt_index_name, - columns, - } => { - if *fulltext { - write!(f, "FULLTEXT")?; - } else { - write!(f, "SPATIAL")?; - } +impl From for TableConstraint { + fn from(constraint: ForeignKeyConstraint) -> Self { + TableConstraint::ForeignKey(constraint) + } +} - write!(f, "{index_type_display:>}")?; +impl From for TableConstraint { + fn from(constraint: CheckConstraint) -> Self { + TableConstraint::Check(constraint) + } +} - if let Some(name) = opt_index_name { - write!(f, " {name}")?; - } +impl From for TableConstraint { + fn from(constraint: IndexConstraint) -> Self { + TableConstraint::Index(constraint) + } +} - write!(f, " ({})", display_comma_separated(columns))?; +impl From for TableConstraint { + fn from(constraint: FullTextOrSpatialConstraint) -> Self { + TableConstraint::FulltextOrSpatial(constraint) + } +} - Ok(()) - } +impl fmt::Display for TableConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TableConstraint::Unique(constraint) => constraint.fmt(f), + TableConstraint::PrimaryKey(constraint) => constraint.fmt(f), + TableConstraint::ForeignKey(constraint) => constraint.fmt(f), + TableConstraint::Check(constraint) => constraint.fmt(f), + TableConstraint::Index(constraint) => constraint.fmt(f), + TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f), } } } @@ -2065,7 +1902,7 @@ pub enum GeneratedExpressionMode { } #[must_use] -fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { +pub fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { struct ConstraintName<'a>(&'a Option); impl fmt::Display for ConstraintName<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -2082,7 +1919,7 @@ fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { /// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"` /// * `_` => do nothing #[must_use] -fn display_option<'a, T: fmt::Display>( +pub fn display_option<'a, T: fmt::Display>( prefix: &'a str, postfix: &'a str, option: &'a Option, @@ -2104,7 +1941,7 @@ fn display_option<'a, T: fmt::Display>( /// * `Some(inner)` => create display struct for `" {inner}"` /// * `_` => do nothing #[must_use] -fn display_option_spaced(option: &Option) -> impl fmt::Display + '_ { +pub fn display_option_spaced(option: &Option) -> impl fmt::Display + '_ { display_option(" ", "", option) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4c1743feb..f53fad334 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -118,6 +118,11 @@ mod dcl; mod ddl; mod dml; pub mod helpers; +pub mod table_constraints; +pub use table_constraints::{ + CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint, + PrimaryKeyConstraint, UniqueConstraint, +}; mod operator; mod query; mod spans; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 4c53e55ce..0a303fcfd 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -670,83 +670,12 @@ impl Spanned for ColumnOptionDef { impl Spanned for TableConstraint { fn span(&self) -> Span { match self { - TableConstraint::Unique { - name, - index_name, - index_type_display: _, - index_type: _, - columns, - index_options: _, - characteristics, - nulls_distinct: _, - } => union_spans( - name.iter() - .map(|i| i.span) - .chain(index_name.iter().map(|i| i.span)) - .chain(columns.iter().map(|i| i.span())) - .chain(characteristics.iter().map(|i| i.span())), - ), - TableConstraint::PrimaryKey { - name, - index_name, - index_type: _, - columns, - index_options: _, - characteristics, - } => union_spans( - name.iter() - .map(|i| i.span) - .chain(index_name.iter().map(|i| i.span)) - .chain(columns.iter().map(|i| i.span())) - .chain(characteristics.iter().map(|i| i.span())), - ), - TableConstraint::ForeignKey { - name, - columns, - index_name, - foreign_table, - referred_columns, - on_delete, - on_update, - characteristics, - } => union_spans( - name.iter() - .map(|i| i.span) - .chain(index_name.iter().map(|i| i.span)) - .chain(columns.iter().map(|i| i.span)) - .chain(core::iter::once(foreign_table.span())) - .chain(referred_columns.iter().map(|i| i.span)) - .chain(on_delete.iter().map(|i| i.span())) - .chain(on_update.iter().map(|i| i.span())) - .chain(characteristics.iter().map(|i| i.span())), - ), - TableConstraint::Check { - name, - expr, - enforced: _, - } => expr.span().union_opt(&name.as_ref().map(|i| i.span)), - TableConstraint::Index { - display_as_key: _, - name, - index_type: _, - columns, - index_options: _, - } => union_spans( - name.iter() - .map(|i| i.span) - .chain(columns.iter().map(|i| i.span())), - ), - TableConstraint::FulltextOrSpatial { - fulltext: _, - index_type_display: _, - opt_index_name, - columns, - } => union_spans( - opt_index_name - .iter() - .map(|i| i.span) - .chain(columns.iter().map(|i| i.span())), - ), + TableConstraint::Unique(constraint) => constraint.span(), + TableConstraint::PrimaryKey(constraint) => constraint.span(), + TableConstraint::ForeignKey(constraint) => constraint.span(), + TableConstraint::Check(constraint) => constraint.span(), + TableConstraint::Index(constraint) => constraint.span(), + TableConstraint::FulltextOrSpatial(constraint) => constraint.span(), } } } diff --git a/src/ast/table_constraints/check_constraint.rs b/src/ast/table_constraints/check_constraint.rs new file mode 100644 index 000000000..89dcec299 --- /dev/null +++ b/src/ast/table_constraints/check_constraint.rs @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) for the `CheckConstraint` table constraint. + +use crate::ast::{Expr, Ident}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct CheckConstraint { + pub name: Option, + pub expr: Box, + /// MySQL-specific syntax + /// + pub enforced: Option, +} + +impl fmt::Display for CheckConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::display_constraint_name; + write!( + f, + "{}CHECK ({})", + display_constraint_name(&self.name), + self.expr + )?; + if let Some(b) = self.enforced { + write!(f, " {}", if b { "ENFORCED" } else { "NOT ENFORCED" }) + } else { + Ok(()) + } + } +} + +impl crate::ast::Spanned for CheckConstraint { + fn span(&self) -> Span { + self.expr + .span() + .union_opt(&self.name.as_ref().map(|i| i.span)) + } +} diff --git a/src/ast/table_constraints/foreign_key_constraint.rs b/src/ast/table_constraints/foreign_key_constraint.rs new file mode 100644 index 000000000..4a7c3376c --- /dev/null +++ b/src/ast/table_constraints/foreign_key_constraint.rs @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) for the `ForeignKeyConstraint` table constraint. + +use crate::ast::{ + display_comma_separated, ConstraintCharacteristics, Ident, ObjectName, ReferentialAction, +}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +/// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () +/// REFERENCES () +/// { [ON DELETE ] [ON UPDATE ] | +/// [ON UPDATE ] [ON DELETE ] +/// }`). +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct ForeignKeyConstraint { + pub name: Option, + /// MySQL-specific field + /// + pub index_name: Option, + pub columns: Vec, + pub foreign_table: ObjectName, + pub referred_columns: Vec, + pub on_delete: Option, + pub on_update: Option, + pub characteristics: Option, +} + +impl fmt::Display for ForeignKeyConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::{display_constraint_name, display_option_spaced}; + write!( + f, + "{}FOREIGN KEY{} ({}) REFERENCES {}", + display_constraint_name(&self.name), + display_option_spaced(&self.index_name), + display_comma_separated(&self.columns), + self.foreign_table, + )?; + if !self.referred_columns.is_empty() { + write!(f, "({})", display_comma_separated(&self.referred_columns))?; + } + if let Some(action) = &self.on_delete { + write!(f, " ON DELETE {action}")?; + } + if let Some(action) = &self.on_update { + write!(f, " ON UPDATE {action}")?; + } + if let Some(characteristics) = &self.characteristics { + write!(f, " {characteristics}")?; + } + Ok(()) + } +} + +impl crate::ast::Spanned for ForeignKeyConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.index_name.iter().map(|i| i.span)) + .chain(self.columns.iter().map(|i| i.span)) + .chain(core::iter::once(self.foreign_table.span())) + .chain(self.referred_columns.iter().map(|i| i.span)) + .chain(self.on_delete.iter().map(|i| i.span())) + .chain(self.on_update.iter().map(|i| i.span())) + .chain(self.characteristics.iter().map(|i| i.span())), + ) + } +} diff --git a/src/ast/table_constraints/full_text_or_spatial_constraint.rs b/src/ast/table_constraints/full_text_or_spatial_constraint.rs new file mode 100644 index 000000000..061dd01f9 --- /dev/null +++ b/src/ast/table_constraints/full_text_or_spatial_constraint.rs @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) for the `FullTextOrSpatialConstraint` table constraint. + +use crate::ast::{Ident, IndexColumn, KeyOrIndexDisplay}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, +/// and MySQL displays both the same way, it is part of this definition as well. +/// +/// Supported syntax: +/// +/// ```markdown +/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...) +/// +/// key_part: col_name +/// ``` +/// +/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html +/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct FullTextOrSpatialConstraint { + /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition. + pub fulltext: bool, + /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. + pub index_type_display: KeyOrIndexDisplay, + /// Optional index name. + pub opt_index_name: Option, + /// Referred column identifier list. + pub columns: Vec, +} + +impl fmt::Display for FullTextOrSpatialConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::display_comma_separated; + + if self.fulltext { + write!(f, "FULLTEXT")?; + } else { + write!(f, "SPATIAL")?; + } + + write!(f, "{:>}", self.index_type_display)?; + + if let Some(name) = &self.opt_index_name { + write!(f, " {name}")?; + } + + write!(f, " ({})", display_comma_separated(&self.columns))?; + + Ok(()) + } +} + +impl crate::ast::Spanned for FullTextOrSpatialConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.opt_index_name + .iter() + .map(|i| i.span) + .chain(self.columns.iter().map(|i| i.span())), + ) + } +} diff --git a/src/ast/table_constraints/index_constraint.rs b/src/ast/table_constraints/index_constraint.rs new file mode 100644 index 000000000..466dd95f9 --- /dev/null +++ b/src/ast/table_constraints/index_constraint.rs @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) for the `IndexConstraint` table constraint. + +use crate::ast::{display_comma_separated, Ident, IndexColumn, IndexOption, IndexType}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage +/// is restricted to MySQL, as no other dialects that support this syntax were found. +/// +/// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...` +/// +/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct IndexConstraint { + /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax. + pub display_as_key: bool, + /// Index name. + pub name: Option, + /// Optional [index type][1]. + /// + /// [1]: IndexType + pub index_type: Option, + /// Referred column identifier list. + pub columns: Vec, + /// Optional index options such as `USING`; see [`IndexOption`]. + pub index_options: Vec, +} + +impl fmt::Display for IndexConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", if self.display_as_key { "KEY" } else { "INDEX" })?; + if let Some(name) = &self.name { + write!(f, " {name}")?; + } + if let Some(index_type) = &self.index_type { + write!(f, " USING {index_type}")?; + } + write!(f, " ({})", display_comma_separated(&self.columns))?; + if !self.index_options.is_empty() { + write!(f, " {}", display_comma_separated(&self.index_options))?; + } + Ok(()) + } +} + +impl crate::ast::Spanned for IndexConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.columns.iter().map(|i| i.span())), + ) + } +} diff --git a/src/ast/table_constraints/mod.rs b/src/ast/table_constraints/mod.rs new file mode 100644 index 000000000..933381c9d --- /dev/null +++ b/src/ast/table_constraints/mod.rs @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) types for table constraints + +mod check_constraint; +pub use check_constraint::CheckConstraint; +mod foreign_key_constraint; +pub use foreign_key_constraint::ForeignKeyConstraint; +mod full_text_or_spatial_constraint; +pub use full_text_or_spatial_constraint::FullTextOrSpatialConstraint; +mod index_constraint; +pub use index_constraint::IndexConstraint; +mod primary_key_constraint; +pub use primary_key_constraint::PrimaryKeyConstraint; +mod unique_constraint; +pub use unique_constraint::UniqueConstraint; diff --git a/src/ast/table_constraints/primary_key_constraint.rs b/src/ast/table_constraints/primary_key_constraint.rs new file mode 100644 index 000000000..09aa5bcd3 --- /dev/null +++ b/src/ast/table_constraints/primary_key_constraint.rs @@ -0,0 +1,107 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) for the `PrimaryKeyConstraint` table constraint. + +use crate::ast::{ + display_comma_separated, display_separated, ConstraintCharacteristics, Ident, IndexColumn, + IndexOption, IndexType, +}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +/// MySQL [definition][1] for `PRIMARY KEY` constraints statements: +/// * `[CONSTRAINT []] PRIMARY KEY [index_name] [index_type] () ` +/// +/// Actually the specification have no `[index_name]` but the next query will complete successfully: +/// ```sql +/// CREATE TABLE unspec_table ( +/// xid INT NOT NULL, +/// CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid) +/// ); +/// ``` +/// +/// where: +/// * [index_type][2] is `USING {BTREE | HASH}` +/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` +/// +/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html +/// [2]: IndexType +/// [3]: IndexOption +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct PrimaryKeyConstraint { + /// Constraint name. + /// + /// Can be not the same as `index_name` + pub name: Option, + /// Index name + pub index_name: Option, + /// Optional `USING` of [index type][1] statement before columns. + /// + /// [1]: IndexType + pub index_type: Option, + /// Identifiers of the columns that form the primary key. + pub columns: Vec, + pub index_options: Vec, + pub characteristics: Option, +} + +impl fmt::Display for PrimaryKeyConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced}; + write!( + f, + "{}PRIMARY KEY{}{} ({})", + display_constraint_name(&self.name), + display_option_spaced(&self.index_name), + display_option(" USING ", "", &self.index_type), + display_comma_separated(&self.columns), + )?; + + if !self.index_options.is_empty() { + write!(f, " {}", display_separated(&self.index_options, " "))?; + } + + write!(f, "{}", display_option_spaced(&self.characteristics))?; + Ok(()) + } +} + +impl crate::ast::Spanned for PrimaryKeyConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.index_name.iter().map(|i| i.span)) + .chain(self.columns.iter().map(|i| i.span())) + .chain(self.characteristics.iter().map(|i| i.span())), + ) + } +} diff --git a/src/ast/table_constraints/unique_constraint.rs b/src/ast/table_constraints/unique_constraint.rs new file mode 100644 index 000000000..2d1712406 --- /dev/null +++ b/src/ast/table_constraints/unique_constraint.rs @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) for the `UniqueConstraint` table constraint. + +use crate::ast::{ + display_comma_separated, display_separated, ConstraintCharacteristics, Ident, IndexColumn, + IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, +}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct UniqueConstraint { + /// Constraint name. + /// + /// Can be not the same as `index_name` + pub name: Option, + /// Index name + pub index_name: Option, + /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. + pub index_type_display: KeyOrIndexDisplay, + /// Optional `USING` of [index type][1] statement before columns. + /// + /// [1]: IndexType + pub index_type: Option, + /// Identifiers of the columns that are unique. + pub columns: Vec, + pub index_options: Vec, + pub characteristics: Option, + /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` + pub nulls_distinct: NullsDistinctOption, +} + +impl fmt::Display for UniqueConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced}; + write!( + f, + "{}UNIQUE{}{:>}{}{} ({})", + display_constraint_name(&self.name), + self.nulls_distinct, + self.index_type_display, + display_option_spaced(&self.index_name), + display_option(" USING ", "", &self.index_type), + display_comma_separated(&self.columns), + )?; + + if !self.index_options.is_empty() { + write!(f, " {}", display_separated(&self.index_options, " "))?; + } + + write!(f, "{}", display_option_spaced(&self.characteristics))?; + Ok(()) + } +} + +impl crate::ast::Spanned for UniqueConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.index_name.iter().map(|i| i.span)) + .chain(self.columns.iter().map(|i| i.span())) + .chain(self.characteristics.iter().map(|i| i.span())), + ) + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d97fa1bd0..deca3b577 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8398,16 +8398,19 @@ impl<'a> Parser<'a> { let columns = self.parse_parenthesized_index_column_list()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; - Ok(Some(TableConstraint::Unique { - name, - index_name, - index_type_display, - index_type, - columns, - index_options, - characteristics, - nulls_distinct, - })) + Ok(Some( + UniqueConstraint { + name, + index_name, + index_type_display, + index_type, + columns, + index_options, + characteristics, + nulls_distinct, + } + .into(), + )) } Token::Word(w) if w.keyword == Keyword::PRIMARY => { // after `PRIMARY` always stay `KEY` @@ -8420,14 +8423,17 @@ impl<'a> Parser<'a> { let columns = self.parse_parenthesized_index_column_list()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; - Ok(Some(TableConstraint::PrimaryKey { - name, - index_name, - index_type, - columns, - index_options, - characteristics, - })) + Ok(Some( + PrimaryKeyConstraint { + name, + index_name, + index_type, + columns, + index_options, + characteristics, + } + .into(), + )) } Token::Word(w) if w.keyword == Keyword::FOREIGN => { self.expect_keyword_is(Keyword::KEY)?; @@ -8452,16 +8458,19 @@ impl<'a> Parser<'a> { let characteristics = self.parse_constraint_characteristics()?; - Ok(Some(TableConstraint::ForeignKey { - name, - index_name, - columns, - foreign_table, - referred_columns, - on_delete, - on_update, - characteristics, - })) + Ok(Some( + ForeignKeyConstraint { + name, + index_name, + columns, + foreign_table, + referred_columns, + on_delete, + on_update, + characteristics, + } + .into(), + )) } Token::Word(w) if w.keyword == Keyword::CHECK => { self.expect_token(&Token::LParen)?; @@ -8476,11 +8485,14 @@ impl<'a> Parser<'a> { None }; - Ok(Some(TableConstraint::Check { - name, - expr, - enforced, - })) + Ok(Some( + CheckConstraint { + name, + expr, + enforced, + } + .into(), + )) } Token::Word(w) if (w.keyword == Keyword::INDEX || w.keyword == Keyword::KEY) @@ -8498,13 +8510,16 @@ impl<'a> Parser<'a> { let columns = self.parse_parenthesized_index_column_list()?; let index_options = self.parse_index_options()?; - Ok(Some(TableConstraint::Index { - display_as_key, - name, - index_type, - columns, - index_options, - })) + Ok(Some( + IndexConstraint { + display_as_key, + name, + index_type, + columns, + index_options, + } + .into(), + )) } Token::Word(w) if (w.keyword == Keyword::FULLTEXT || w.keyword == Keyword::SPATIAL) @@ -8528,12 +8543,15 @@ impl<'a> Parser<'a> { let columns = self.parse_parenthesized_index_column_list()?; - Ok(Some(TableConstraint::FulltextOrSpatial { - fulltext, - index_type_display, - opt_index_name, - columns, - })) + Ok(Some( + FullTextOrSpatialConstraint { + fulltext, + index_type_display, + opt_index_name, + columns, + } + .into(), + )) } _ => { if name.is_some() { @@ -18136,85 +18154,91 @@ mod tests { test_parse_table_constraint!( dialect, "INDEX (c1)", - TableConstraint::Index { + IndexConstraint { display_as_key: false, name: None, index_type: None, columns: vec![mk_expected_col("c1")], index_options: vec![], } + .into() ); test_parse_table_constraint!( dialect, "KEY (c1)", - TableConstraint::Index { + IndexConstraint { display_as_key: true, name: None, index_type: None, columns: vec![mk_expected_col("c1")], index_options: vec![], } + .into() ); test_parse_table_constraint!( dialect, "INDEX 'index' (c1, c2)", - TableConstraint::Index { + TableConstraint::Index(IndexConstraint { display_as_key: false, name: Some(Ident::with_quote('\'', "index")), index_type: None, columns: vec![mk_expected_col("c1"), mk_expected_col("c2")], index_options: vec![], - } + }) ); test_parse_table_constraint!( dialect, "INDEX USING BTREE (c1)", - TableConstraint::Index { + IndexConstraint { display_as_key: false, name: None, index_type: Some(IndexType::BTree), columns: vec![mk_expected_col("c1")], index_options: vec![], } + .into() ); test_parse_table_constraint!( dialect, "INDEX USING HASH (c1)", - TableConstraint::Index { + IndexConstraint { display_as_key: false, name: None, index_type: Some(IndexType::Hash), columns: vec![mk_expected_col("c1")], index_options: vec![], } + .into() ); test_parse_table_constraint!( dialect, "INDEX idx_name USING BTREE (c1)", - TableConstraint::Index { + IndexConstraint { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::BTree), columns: vec![mk_expected_col("c1")], index_options: vec![], } + .into() ); test_parse_table_constraint!( dialect, "INDEX idx_name USING HASH (c1)", - TableConstraint::Index { + IndexConstraint { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::Hash), columns: vec![mk_expected_col("c1")], index_options: vec![], } + .into() ); } diff --git a/src/test_utils.rs b/src/test_utils.rs index ab2cf89b2..03fdee8ad 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -469,17 +469,17 @@ pub fn index_column(stmt: Statement) -> Expr { } Statement::CreateTable(CreateTable { constraints, .. }) => { match constraints.first().unwrap() { - TableConstraint::Index { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::Index(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } - TableConstraint::Unique { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::Unique(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } - TableConstraint::PrimaryKey { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::PrimaryKey(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } - TableConstraint::FulltextOrSpatial { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::FulltextOrSpatial(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } _ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"), } @@ -487,19 +487,18 @@ pub fn index_column(stmt: Statement) -> Expr { Statement::AlterTable { operations, .. } => match operations.first().unwrap() { AlterTableOperation::AddConstraint { constraint, .. } => { match constraint { - TableConstraint::Index { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::Index(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } - TableConstraint::Unique { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::Unique(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } - TableConstraint::PrimaryKey { columns, .. } => { - columns.first().unwrap().column.expr.clone() + TableConstraint::PrimaryKey(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() + } + TableConstraint::FulltextOrSpatial(constraint) => { + constraint.columns.first().unwrap().column.expr.clone() } - TableConstraint::FulltextOrSpatial { - columns, - .. - } => columns.first().unwrap().column.expr.clone(), _ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"), } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 365d5469e..343ea4955 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3818,7 +3818,7 @@ fn parse_create_table() { assert_eq!( constraints, vec![ - TableConstraint::ForeignKey { + ForeignKeyConstraint { name: Some("fkey".into()), index_name: None, columns: vec!["lat".into()], @@ -3827,8 +3827,9 @@ fn parse_create_table() { on_delete: Some(ReferentialAction::Restrict), on_update: None, characteristics: None, - }, - TableConstraint::ForeignKey { + } + .into(), + ForeignKeyConstraint { name: Some("fkey2".into()), index_name: None, columns: vec!["lat".into()], @@ -3837,8 +3838,9 @@ fn parse_create_table() { on_delete: Some(ReferentialAction::NoAction), on_update: Some(ReferentialAction::Restrict), characteristics: None, - }, - TableConstraint::ForeignKey { + } + .into(), + ForeignKeyConstraint { name: None, index_name: None, columns: vec!["lat".into()], @@ -3847,8 +3849,9 @@ fn parse_create_table() { on_delete: Some(ReferentialAction::Cascade), on_update: Some(ReferentialAction::SetDefault), characteristics: None, - }, - TableConstraint::ForeignKey { + } + .into(), + ForeignKeyConstraint { name: None, index_name: None, columns: vec!["lng".into()], @@ -3857,7 +3860,8 @@ fn parse_create_table() { on_delete: None, on_update: Some(ReferentialAction::SetNull), characteristics: None, - }, + } + .into(), ] ); assert_eq!(table_options, CreateTableOptions::None); @@ -3945,7 +3949,7 @@ fn parse_create_table_with_constraint_characteristics() { assert_eq!( constraints, vec![ - TableConstraint::ForeignKey { + ForeignKeyConstraint { name: Some("fkey".into()), index_name: None, columns: vec!["lat".into()], @@ -3958,8 +3962,9 @@ fn parse_create_table_with_constraint_characteristics() { initially: Some(DeferrableInitial::Deferred), enforced: None }), - }, - TableConstraint::ForeignKey { + } + .into(), + ForeignKeyConstraint { name: Some("fkey2".into()), index_name: None, columns: vec!["lat".into()], @@ -3972,8 +3977,9 @@ fn parse_create_table_with_constraint_characteristics() { initially: Some(DeferrableInitial::Immediate), enforced: None, }), - }, - TableConstraint::ForeignKey { + } + .into(), + ForeignKeyConstraint { name: None, index_name: None, columns: vec!["lat".into()], @@ -3986,8 +3992,9 @@ fn parse_create_table_with_constraint_characteristics() { initially: Some(DeferrableInitial::Deferred), enforced: Some(false), }), - }, - TableConstraint::ForeignKey { + } + .into(), + ForeignKeyConstraint { name: None, index_name: None, columns: vec!["lng".into()], @@ -4000,7 +4007,8 @@ fn parse_create_table_with_constraint_characteristics() { initially: Some(DeferrableInitial::Immediate), enforced: Some(true), }), - }, + } + .into(), ] ); assert_eq!(table_options, CreateTableOptions::None); diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 5d75aa508..35bac548c 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -684,7 +684,7 @@ fn table_constraint_unique_primary_ctor( }) .collect(); match unique_index_type_display { - Some(index_type_display) => TableConstraint::Unique { + Some(index_type_display) => UniqueConstraint { name, index_name, index_type_display, @@ -693,15 +693,17 @@ fn table_constraint_unique_primary_ctor( index_options, characteristics, nulls_distinct: NullsDistinctOption::None, - }, - None => TableConstraint::PrimaryKey { + } + .into(), + None => PrimaryKeyConstraint { name, index_name, index_type, columns, index_options, characteristics, - }, + } + .into(), } } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 196a82f54..45ae32dac 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -607,9 +607,10 @@ fn parse_alter_table_constraints_unique_nulls_distinct() { { Statement::AlterTable { operations, .. } => match &operations[0] { AlterTableOperation::AddConstraint { - constraint: TableConstraint::Unique { nulls_distinct, .. }, + constraint: TableConstraint::Unique(constraint), .. } => { + let nulls_distinct = &constraint.nulls_distinct; assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct) } _ => unreachable!(), @@ -5578,7 +5579,7 @@ fn parse_create_domain() { data_type: DataType::Integer(None), collation: None, default: None, - constraints: vec![TableConstraint::Check { + constraints: vec![CheckConstraint { name: None, expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("VALUE"))), @@ -5586,7 +5587,8 @@ fn parse_create_domain() { right: Box::new(Expr::Value(test_utils::number("0").into())), }), enforced: None, - }], + } + .into()], }); assert_eq!(pg().verified_stmt(sql1), expected); @@ -5597,7 +5599,7 @@ fn parse_create_domain() { data_type: DataType::Integer(None), collation: Some(Ident::with_quote('"', "en_US")), default: None, - constraints: vec![TableConstraint::Check { + constraints: vec![CheckConstraint { name: None, expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("VALUE"))), @@ -5605,7 +5607,8 @@ fn parse_create_domain() { right: Box::new(Expr::Value(test_utils::number("0").into())), }), enforced: None, - }], + } + .into()], }); assert_eq!(pg().verified_stmt(sql2), expected); @@ -5616,7 +5619,7 @@ fn parse_create_domain() { data_type: DataType::Integer(None), collation: None, default: Some(Expr::Value(test_utils::number("1").into())), - constraints: vec![TableConstraint::Check { + constraints: vec![CheckConstraint { name: None, expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("VALUE"))), @@ -5624,7 +5627,8 @@ fn parse_create_domain() { right: Box::new(Expr::Value(test_utils::number("0").into())), }), enforced: None, - }], + } + .into()], }); assert_eq!(pg().verified_stmt(sql3), expected); @@ -5635,7 +5639,7 @@ fn parse_create_domain() { data_type: DataType::Integer(None), collation: Some(Ident::with_quote('"', "en_US")), default: Some(Expr::Value(test_utils::number("1").into())), - constraints: vec![TableConstraint::Check { + constraints: vec![CheckConstraint { name: None, expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("VALUE"))), @@ -5643,7 +5647,8 @@ fn parse_create_domain() { right: Box::new(Expr::Value(test_utils::number("0").into())), }), enforced: None, - }], + } + .into()], }); assert_eq!(pg().verified_stmt(sql4), expected); @@ -5654,7 +5659,7 @@ fn parse_create_domain() { data_type: DataType::Integer(None), collation: None, default: None, - constraints: vec![TableConstraint::Check { + constraints: vec![CheckConstraint { name: Some(Ident::new("my_constraint")), expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("VALUE"))), @@ -5662,7 +5667,8 @@ fn parse_create_domain() { right: Box::new(Expr::Value(test_utils::number("0").into())), }), enforced: None, - }], + } + .into()], }); assert_eq!(pg().verified_stmt(sql5), expected); @@ -6467,7 +6473,7 @@ fn parse_alter_table_constraint_not_valid() { assert_eq!( operations, vec![AlterTableOperation::AddConstraint { - constraint: TableConstraint::ForeignKey { + constraint: ForeignKeyConstraint { name: Some("bar".into()), index_name: None, columns: vec!["baz".into()], @@ -6476,7 +6482,8 @@ fn parse_alter_table_constraint_not_valid() { on_delete: None, on_update: None, characteristics: None, - }, + } + .into(), not_valid: true, }] ); From 9d3309779dcbee690efccce1ce2d67cba735d5d7 Mon Sep 17 00:00:00 2001 From: Luca Date: Fri, 3 Oct 2025 11:55:57 +0200 Subject: [PATCH 2/7] Fixed support for no-std --- src/ast/table_constraints/check_constraint.rs | 3 +++ src/ast/table_constraints/foreign_key_constraint.rs | 3 +++ src/ast/table_constraints/full_text_or_spatial_constraint.rs | 3 +++ src/ast/table_constraints/index_constraint.rs | 3 +++ src/ast/table_constraints/primary_key_constraint.rs | 3 +++ src/ast/table_constraints/unique_constraint.rs | 3 +++ 6 files changed, 18 insertions(+) diff --git a/src/ast/table_constraints/check_constraint.rs b/src/ast/table_constraints/check_constraint.rs index 89dcec299..9fbc42dff 100644 --- a/src/ast/table_constraints/check_constraint.rs +++ b/src/ast/table_constraints/check_constraint.rs @@ -21,6 +21,9 @@ use crate::ast::{Expr, Ident}; use crate::tokenizer::Span; use core::fmt; +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/ast/table_constraints/foreign_key_constraint.rs b/src/ast/table_constraints/foreign_key_constraint.rs index 4a7c3376c..74fc48174 100644 --- a/src/ast/table_constraints/foreign_key_constraint.rs +++ b/src/ast/table_constraints/foreign_key_constraint.rs @@ -23,6 +23,9 @@ use crate::ast::{ use crate::tokenizer::Span; use core::fmt; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/ast/table_constraints/full_text_or_spatial_constraint.rs b/src/ast/table_constraints/full_text_or_spatial_constraint.rs index 061dd01f9..3ac407075 100644 --- a/src/ast/table_constraints/full_text_or_spatial_constraint.rs +++ b/src/ast/table_constraints/full_text_or_spatial_constraint.rs @@ -21,6 +21,9 @@ use crate::ast::{Ident, IndexColumn, KeyOrIndexDisplay}; use crate::tokenizer::Span; use core::fmt; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/ast/table_constraints/index_constraint.rs b/src/ast/table_constraints/index_constraint.rs index 466dd95f9..e7542a005 100644 --- a/src/ast/table_constraints/index_constraint.rs +++ b/src/ast/table_constraints/index_constraint.rs @@ -21,6 +21,9 @@ use crate::ast::{display_comma_separated, Ident, IndexColumn, IndexOption, Index use crate::tokenizer::Span; use core::fmt; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/ast/table_constraints/primary_key_constraint.rs b/src/ast/table_constraints/primary_key_constraint.rs index 09aa5bcd3..ebc442f62 100644 --- a/src/ast/table_constraints/primary_key_constraint.rs +++ b/src/ast/table_constraints/primary_key_constraint.rs @@ -24,6 +24,9 @@ use crate::ast::{ use crate::tokenizer::Span; use core::fmt; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/ast/table_constraints/unique_constraint.rs b/src/ast/table_constraints/unique_constraint.rs index 2d1712406..2043a87ed 100644 --- a/src/ast/table_constraints/unique_constraint.rs +++ b/src/ast/table_constraints/unique_constraint.rs @@ -24,6 +24,9 @@ use crate::ast::{ use crate::tokenizer::Span; use core::fmt; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; From dd2054dbec36f6f58936a059fdd8c5c35769dd61 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Sat, 4 Oct 2025 08:56:53 +0200 Subject: [PATCH 3/7] Update src/ast/ddl.rs Co-authored-by: Ifeanyi Ubah --- src/ast/ddl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 437ffc100..402d27cee 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1919,7 +1919,7 @@ pub fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + ' /// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"` /// * `_` => do nothing #[must_use] -pub fn display_option<'a, T: fmt::Display>( +pub(crate) fn display_option<'a, T: fmt::Display>( prefix: &'a str, postfix: &'a str, option: &'a Option, From 359c96ab6842d849155b2ac92770b8560fa0aca4 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Sat, 4 Oct 2025 08:57:09 +0200 Subject: [PATCH 4/7] Update src/ast/ddl.rs Co-authored-by: Ifeanyi Ubah --- src/ast/ddl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 402d27cee..e39c30118 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1902,7 +1902,7 @@ pub enum GeneratedExpressionMode { } #[must_use] -pub fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { +pub(crate) fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { struct ConstraintName<'a>(&'a Option); impl fmt::Display for ConstraintName<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { From f13b2eb9b62b729bee04508dc6ea132282fc7814 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Sat, 4 Oct 2025 08:57:15 +0200 Subject: [PATCH 5/7] Update src/ast/ddl.rs Co-authored-by: Ifeanyi Ubah --- src/ast/ddl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index e39c30118..4f454ef53 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1941,7 +1941,7 @@ pub(crate) fn display_option<'a, T: fmt::Display>( /// * `Some(inner)` => create display struct for `" {inner}"` /// * `_` => do nothing #[must_use] -pub fn display_option_spaced(option: &Option) -> impl fmt::Display + '_ { +pub(crate) fn display_option_spaced(option: &Option) -> impl fmt::Display + '_ { display_option(" ", "", option) } From 258cc8cb65603824c86a7e21089a426e021abcf8 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 5 Oct 2025 11:08:44 +0200 Subject: [PATCH 6/7] Folded all `table_constraints/*.rs` submodules into `table_constraints.rs` module --- src/ast/ddl.rs | 122 +---- src/ast/mod.rs | 4 +- src/ast/table_constraints.rs | 516 ++++++++++++++++++ src/ast/table_constraints/check_constraint.rs | 67 --- .../foreign_key_constraint.rs | 102 ---- .../full_text_or_spatial_constraint.rs | 95 ---- src/ast/table_constraints/index_constraint.rs | 87 --- src/ast/table_constraints/mod.rs | 31 -- .../primary_key_constraint.rs | 110 ---- .../table_constraints/unique_constraint.rs | 98 ---- 10 files changed, 519 insertions(+), 713 deletions(-) create mode 100644 src/ast/table_constraints.rs delete mode 100644 src/ast/table_constraints/check_constraint.rs delete mode 100644 src/ast/table_constraints/foreign_key_constraint.rs delete mode 100644 src/ast/table_constraints/full_text_or_spatial_constraint.rs delete mode 100644 src/ast/table_constraints/index_constraint.rs delete mode 100644 src/ast/table_constraints/mod.rs delete mode 100644 src/ast/table_constraints/primary_key_constraint.rs delete mode 100644 src/ast/table_constraints/unique_constraint.rs diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 4f454ef53..17976ee38 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -31,10 +31,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::value::escape_single_quote_string; use crate::ast::{ display_comma_separated, display_separated, - table_constraints::{ - CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint, - PrimaryKeyConstraint, UniqueConstraint, - }, + table_constraints::TableConstraint, ArgMode, CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle, @@ -1033,123 +1030,6 @@ impl fmt::Display for AlterColumnOperation { } } -/// A table-level constraint, specified in a `CREATE TABLE` or an -/// `ALTER TABLE ADD ` statement. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub enum TableConstraint { - /// MySQL [definition][1] for `UNIQUE` constraints statements:\ - /// * `[CONSTRAINT []] UNIQUE [] [index_type] () ` - /// - /// where: - /// * [index_type][2] is `USING {BTREE | HASH}` - /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` - /// * [index_type_display][4] is `[INDEX | KEY]` - /// - /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html - /// [2]: IndexType - /// [3]: IndexOption - /// [4]: KeyOrIndexDisplay - Unique(UniqueConstraint), - /// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\ - /// * `[CONSTRAINT []] PRIMARY KEY [index_name] [index_type] () ` - /// - /// Actually the specification have no `[index_name]` but the next query will complete successfully: - /// ```sql - /// CREATE TABLE unspec_table ( - /// xid INT NOT NULL, - /// CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid) - /// ); - /// ``` - /// - /// where: - /// * [index_type][2] is `USING {BTREE | HASH}` - /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` - /// - /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html - /// [2]: IndexType - /// [3]: IndexOption - PrimaryKey(PrimaryKeyConstraint), - /// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () - /// REFERENCES () - /// { [ON DELETE ] [ON UPDATE ] | - /// [ON UPDATE ] [ON DELETE ] - /// }`). - ForeignKey(ForeignKeyConstraint), - /// `[ CONSTRAINT ] CHECK () [[NOT] ENFORCED]` - Check(CheckConstraint), - /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage - /// is restricted to MySQL, as no other dialects that support this syntax were found. - /// - /// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...` - /// - /// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html - Index(IndexConstraint), - /// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, - /// and MySQL displays both the same way, it is part of this definition as well. - /// - /// Supported syntax: - /// - /// ```markdown - /// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...) - /// - /// key_part: col_name - /// ``` - /// - /// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html - /// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html - FulltextOrSpatial(FullTextOrSpatialConstraint), -} - -impl From for TableConstraint { - fn from(constraint: UniqueConstraint) -> Self { - TableConstraint::Unique(constraint) - } -} - -impl From for TableConstraint { - fn from(constraint: PrimaryKeyConstraint) -> Self { - TableConstraint::PrimaryKey(constraint) - } -} - -impl From for TableConstraint { - fn from(constraint: ForeignKeyConstraint) -> Self { - TableConstraint::ForeignKey(constraint) - } -} - -impl From for TableConstraint { - fn from(constraint: CheckConstraint) -> Self { - TableConstraint::Check(constraint) - } -} - -impl From for TableConstraint { - fn from(constraint: IndexConstraint) -> Self { - TableConstraint::Index(constraint) - } -} - -impl From for TableConstraint { - fn from(constraint: FullTextOrSpatialConstraint) -> Self { - TableConstraint::FulltextOrSpatial(constraint) - } -} - -impl fmt::Display for TableConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - TableConstraint::Unique(constraint) => constraint.fmt(f), - TableConstraint::PrimaryKey(constraint) => constraint.fmt(f), - TableConstraint::ForeignKey(constraint) => constraint.fmt(f), - TableConstraint::Check(constraint) => constraint.fmt(f), - TableConstraint::Index(constraint) => constraint.fmt(f), - TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f), - } - } -} /// Representation whether a definition can can contains the KEY or INDEX keywords with the same /// meaning. diff --git a/src/ast/mod.rs b/src/ast/mod.rs index f53fad334..369058804 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -68,7 +68,7 @@ pub use self::ddl::{ DropBehavior, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, - ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TableConstraint, + ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, }; @@ -121,7 +121,7 @@ pub mod helpers; pub mod table_constraints; pub use table_constraints::{ CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint, - PrimaryKeyConstraint, UniqueConstraint, + PrimaryKeyConstraint, TableConstraint, UniqueConstraint, }; mod operator; mod query; diff --git a/src/ast/table_constraints.rs b/src/ast/table_constraints.rs new file mode 100644 index 000000000..f7e70b8a5 --- /dev/null +++ b/src/ast/table_constraints.rs @@ -0,0 +1,516 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! SQL Abstract Syntax Tree (AST) types for table constraints + +use crate::ast::{ + display_comma_separated, display_separated, ConstraintCharacteristics, Expr, Ident, + IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, ObjectName, + ReferentialAction, +}; +use crate::tokenizer::Span; +use core::fmt; + +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, vec::Vec}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +/// A table-level constraint, specified in a `CREATE TABLE` or an +/// `ALTER TABLE ADD ` statement. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum TableConstraint { + /// MySQL [definition][1] for `UNIQUE` constraints statements:\ + /// * `[CONSTRAINT []] UNIQUE [] [index_type] () ` + /// + /// where: + /// * [index_type][2] is `USING {BTREE | HASH}` + /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` + /// * [index_type_display][4] is `[INDEX | KEY]` + /// + /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html + /// [2]: IndexType + /// [3]: IndexOption + /// [4]: KeyOrIndexDisplay + Unique(UniqueConstraint), + /// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\ + /// * `[CONSTRAINT []] PRIMARY KEY [index_name] [index_type] () ` + /// + /// Actually the specification have no `[index_name]` but the next query will complete successfully: + /// ```sql + /// CREATE TABLE unspec_table ( + /// xid INT NOT NULL, + /// CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid) + /// ); + /// ``` + /// + /// where: + /// * [index_type][2] is `USING {BTREE | HASH}` + /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` + /// + /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html + /// [2]: IndexType + /// [3]: IndexOption + PrimaryKey(PrimaryKeyConstraint), + /// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () + /// REFERENCES () + /// { [ON DELETE ] [ON UPDATE ] | + /// [ON UPDATE ] [ON DELETE ] + /// }`). + ForeignKey(ForeignKeyConstraint), + /// `[ CONSTRAINT ] CHECK () [[NOT] ENFORCED]` + Check(CheckConstraint), + /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage + /// is restricted to MySQL, as no other dialects that support this syntax were found. + /// + /// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...` + /// + /// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html + Index(IndexConstraint), + /// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, + /// and MySQL displays both the same way, it is part of this definition as well. + /// + /// Supported syntax: + /// + /// ```markdown + /// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...) + /// + /// key_part: col_name + /// ``` + /// + /// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html + /// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html + FulltextOrSpatial(FullTextOrSpatialConstraint), +} + +impl From for TableConstraint { + fn from(constraint: UniqueConstraint) -> Self { + TableConstraint::Unique(constraint) + } +} + +impl From for TableConstraint { + fn from(constraint: PrimaryKeyConstraint) -> Self { + TableConstraint::PrimaryKey(constraint) + } +} + +impl From for TableConstraint { + fn from(constraint: ForeignKeyConstraint) -> Self { + TableConstraint::ForeignKey(constraint) + } +} + +impl From for TableConstraint { + fn from(constraint: CheckConstraint) -> Self { + TableConstraint::Check(constraint) + } +} + +impl From for TableConstraint { + fn from(constraint: IndexConstraint) -> Self { + TableConstraint::Index(constraint) + } +} + +impl From for TableConstraint { + fn from(constraint: FullTextOrSpatialConstraint) -> Self { + TableConstraint::FulltextOrSpatial(constraint) + } +} + +impl fmt::Display for TableConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TableConstraint::Unique(constraint) => constraint.fmt(f), + TableConstraint::PrimaryKey(constraint) => constraint.fmt(f), + TableConstraint::ForeignKey(constraint) => constraint.fmt(f), + TableConstraint::Check(constraint) => constraint.fmt(f), + TableConstraint::Index(constraint) => constraint.fmt(f), + TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f), + } + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct CheckConstraint { + pub name: Option, + pub expr: Box, + /// MySQL-specific syntax + /// + pub enforced: Option, +} + +impl fmt::Display for CheckConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::display_constraint_name; + write!( + f, + "{}CHECK ({})", + display_constraint_name(&self.name), + self.expr + )?; + if let Some(b) = self.enforced { + write!(f, " {}", if b { "ENFORCED" } else { "NOT ENFORCED" }) + } else { + Ok(()) + } + } +} + +impl crate::ast::Spanned for CheckConstraint { + fn span(&self) -> Span { + self.expr + .span() + .union_opt(&self.name.as_ref().map(|i| i.span)) + } +} + +/// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () +/// REFERENCES () +/// { [ON DELETE ] [ON UPDATE ] | +/// [ON UPDATE ] [ON DELETE ] +/// }`). +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct ForeignKeyConstraint { + pub name: Option, + /// MySQL-specific field + /// + pub index_name: Option, + pub columns: Vec, + pub foreign_table: ObjectName, + pub referred_columns: Vec, + pub on_delete: Option, + pub on_update: Option, + pub characteristics: Option, +} + +impl fmt::Display for ForeignKeyConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::{display_constraint_name, display_option_spaced}; + write!( + f, + "{}FOREIGN KEY{} ({}) REFERENCES {}", + display_constraint_name(&self.name), + display_option_spaced(&self.index_name), + display_comma_separated(&self.columns), + self.foreign_table, + )?; + if !self.referred_columns.is_empty() { + write!(f, "({})", display_comma_separated(&self.referred_columns))?; + } + if let Some(action) = &self.on_delete { + write!(f, " ON DELETE {action}")?; + } + if let Some(action) = &self.on_update { + write!(f, " ON UPDATE {action}")?; + } + if let Some(characteristics) = &self.characteristics { + write!(f, " {characteristics}")?; + } + Ok(()) + } +} + +impl crate::ast::Spanned for ForeignKeyConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.index_name.iter().map(|i| i.span)) + .chain(self.columns.iter().map(|i| i.span)) + .chain(core::iter::once(self.foreign_table.span())) + .chain(self.referred_columns.iter().map(|i| i.span)) + .chain(self.on_delete.iter().map(|i| i.span())) + .chain(self.on_update.iter().map(|i| i.span())) + .chain(self.characteristics.iter().map(|i| i.span())), + ) + } +} + +/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, +/// and MySQL displays both the same way, it is part of this definition as well. +/// +/// Supported syntax: +/// +/// ```markdown +/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...) +/// +/// key_part: col_name +/// ``` +/// +/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html +/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct FullTextOrSpatialConstraint { + /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition. + pub fulltext: bool, + /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. + pub index_type_display: KeyOrIndexDisplay, + /// Optional index name. + pub opt_index_name: Option, + /// Referred column identifier list. + pub columns: Vec, +} + +impl fmt::Display for FullTextOrSpatialConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.fulltext { + write!(f, "FULLTEXT")?; + } else { + write!(f, "SPATIAL")?; + } + + write!(f, "{:>}", self.index_type_display)?; + + if let Some(name) = &self.opt_index_name { + write!(f, " {name}")?; + } + + write!(f, " ({})", display_comma_separated(&self.columns))?; + + Ok(()) + } +} + +impl crate::ast::Spanned for FullTextOrSpatialConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.opt_index_name + .iter() + .map(|i| i.span) + .chain(self.columns.iter().map(|i| i.span())), + ) + } +} + +/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage +/// is restricted to MySQL, as no other dialects that support this syntax were found. +/// +/// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...` +/// +/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct IndexConstraint { + /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax. + pub display_as_key: bool, + /// Index name. + pub name: Option, + /// Optional [index type][1]. + /// + /// [1]: IndexType + pub index_type: Option, + /// Referred column identifier list. + pub columns: Vec, + /// Optional index options such as `USING`; see [`IndexOption`]. + pub index_options: Vec, +} + +impl fmt::Display for IndexConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", if self.display_as_key { "KEY" } else { "INDEX" })?; + if let Some(name) = &self.name { + write!(f, " {name}")?; + } + if let Some(index_type) = &self.index_type { + write!(f, " USING {index_type}")?; + } + write!(f, " ({})", display_comma_separated(&self.columns))?; + if !self.index_options.is_empty() { + write!(f, " {}", display_comma_separated(&self.index_options))?; + } + Ok(()) + } +} + +impl crate::ast::Spanned for IndexConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.columns.iter().map(|i| i.span())), + ) + } +} + +/// MySQL [definition][1] for `PRIMARY KEY` constraints statements: +/// * `[CONSTRAINT []] PRIMARY KEY [index_name] [index_type] () ` +/// +/// Actually the specification have no `[index_name]` but the next query will complete successfully: +/// ```sql +/// CREATE TABLE unspec_table ( +/// xid INT NOT NULL, +/// CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid) +/// ); +/// ``` +/// +/// where: +/// * [index_type][2] is `USING {BTREE | HASH}` +/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` +/// +/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html +/// [2]: IndexType +/// [3]: IndexOption +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct PrimaryKeyConstraint { + /// Constraint name. + /// + /// Can be not the same as `index_name` + pub name: Option, + /// Index name + pub index_name: Option, + /// Optional `USING` of [index type][1] statement before columns. + /// + /// [1]: IndexType + pub index_type: Option, + /// Identifiers of the columns that form the primary key. + pub columns: Vec, + pub index_options: Vec, + pub characteristics: Option, +} + +impl fmt::Display for PrimaryKeyConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced}; + write!( + f, + "{}PRIMARY KEY{}{} ({})", + display_constraint_name(&self.name), + display_option_spaced(&self.index_name), + display_option(" USING ", "", &self.index_type), + display_comma_separated(&self.columns), + )?; + + if !self.index_options.is_empty() { + write!(f, " {}", display_separated(&self.index_options, " "))?; + } + + write!(f, "{}", display_option_spaced(&self.characteristics))?; + Ok(()) + } +} + +impl crate::ast::Spanned for PrimaryKeyConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.index_name.iter().map(|i| i.span)) + .chain(self.columns.iter().map(|i| i.span())) + .chain(self.characteristics.iter().map(|i| i.span())), + ) + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct UniqueConstraint { + /// Constraint name. + /// + /// Can be not the same as `index_name` + pub name: Option, + /// Index name + pub index_name: Option, + /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. + pub index_type_display: KeyOrIndexDisplay, + /// Optional `USING` of [index type][1] statement before columns. + /// + /// [1]: IndexType + pub index_type: Option, + /// Identifiers of the columns that are unique. + pub columns: Vec, + pub index_options: Vec, + pub characteristics: Option, + /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` + pub nulls_distinct: NullsDistinctOption, +} + +impl fmt::Display for UniqueConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced}; + write!( + f, + "{}UNIQUE{}{:>}{}{} ({})", + display_constraint_name(&self.name), + self.nulls_distinct, + self.index_type_display, + display_option_spaced(&self.index_name), + display_option(" USING ", "", &self.index_type), + display_comma_separated(&self.columns), + )?; + + if !self.index_options.is_empty() { + write!(f, " {}", display_separated(&self.index_options, " "))?; + } + + write!(f, "{}", display_option_spaced(&self.characteristics))?; + Ok(()) + } +} + +impl crate::ast::Spanned for UniqueConstraint { + fn span(&self) -> Span { + fn union_spans>(iter: I) -> Span { + Span::union_iter(iter) + } + + union_spans( + self.name + .iter() + .map(|i| i.span) + .chain(self.index_name.iter().map(|i| i.span)) + .chain(self.columns.iter().map(|i| i.span())) + .chain(self.characteristics.iter().map(|i| i.span())), + ) + } +} \ No newline at end of file diff --git a/src/ast/table_constraints/check_constraint.rs b/src/ast/table_constraints/check_constraint.rs deleted file mode 100644 index 9fbc42dff..000000000 --- a/src/ast/table_constraints/check_constraint.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) for the `CheckConstraint` table constraint. - -use crate::ast::{Expr, Ident}; -use crate::tokenizer::Span; -use core::fmt; - -#[cfg(not(feature = "std"))] -use alloc::boxed::Box; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "visitor")] -use sqlparser_derive::{Visit, VisitMut}; - -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct CheckConstraint { - pub name: Option, - pub expr: Box, - /// MySQL-specific syntax - /// - pub enforced: Option, -} - -impl fmt::Display for CheckConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::ast::ddl::display_constraint_name; - write!( - f, - "{}CHECK ({})", - display_constraint_name(&self.name), - self.expr - )?; - if let Some(b) = self.enforced { - write!(f, " {}", if b { "ENFORCED" } else { "NOT ENFORCED" }) - } else { - Ok(()) - } - } -} - -impl crate::ast::Spanned for CheckConstraint { - fn span(&self) -> Span { - self.expr - .span() - .union_opt(&self.name.as_ref().map(|i| i.span)) - } -} diff --git a/src/ast/table_constraints/foreign_key_constraint.rs b/src/ast/table_constraints/foreign_key_constraint.rs deleted file mode 100644 index 74fc48174..000000000 --- a/src/ast/table_constraints/foreign_key_constraint.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) for the `ForeignKeyConstraint` table constraint. - -use crate::ast::{ - display_comma_separated, ConstraintCharacteristics, Ident, ObjectName, ReferentialAction, -}; -use crate::tokenizer::Span; -use core::fmt; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "visitor")] -use sqlparser_derive::{Visit, VisitMut}; - -/// A referential integrity constraint (`[ CONSTRAINT ] FOREIGN KEY () -/// REFERENCES () -/// { [ON DELETE ] [ON UPDATE ] | -/// [ON UPDATE ] [ON DELETE ] -/// }`). -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct ForeignKeyConstraint { - pub name: Option, - /// MySQL-specific field - /// - pub index_name: Option, - pub columns: Vec, - pub foreign_table: ObjectName, - pub referred_columns: Vec, - pub on_delete: Option, - pub on_update: Option, - pub characteristics: Option, -} - -impl fmt::Display for ForeignKeyConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::ast::ddl::{display_constraint_name, display_option_spaced}; - write!( - f, - "{}FOREIGN KEY{} ({}) REFERENCES {}", - display_constraint_name(&self.name), - display_option_spaced(&self.index_name), - display_comma_separated(&self.columns), - self.foreign_table, - )?; - if !self.referred_columns.is_empty() { - write!(f, "({})", display_comma_separated(&self.referred_columns))?; - } - if let Some(action) = &self.on_delete { - write!(f, " ON DELETE {action}")?; - } - if let Some(action) = &self.on_update { - write!(f, " ON UPDATE {action}")?; - } - if let Some(characteristics) = &self.characteristics { - write!(f, " {characteristics}")?; - } - Ok(()) - } -} - -impl crate::ast::Spanned for ForeignKeyConstraint { - fn span(&self) -> Span { - fn union_spans>(iter: I) -> Span { - Span::union_iter(iter) - } - - union_spans( - self.name - .iter() - .map(|i| i.span) - .chain(self.index_name.iter().map(|i| i.span)) - .chain(self.columns.iter().map(|i| i.span)) - .chain(core::iter::once(self.foreign_table.span())) - .chain(self.referred_columns.iter().map(|i| i.span)) - .chain(self.on_delete.iter().map(|i| i.span())) - .chain(self.on_update.iter().map(|i| i.span())) - .chain(self.characteristics.iter().map(|i| i.span())), - ) - } -} diff --git a/src/ast/table_constraints/full_text_or_spatial_constraint.rs b/src/ast/table_constraints/full_text_or_spatial_constraint.rs deleted file mode 100644 index 3ac407075..000000000 --- a/src/ast/table_constraints/full_text_or_spatial_constraint.rs +++ /dev/null @@ -1,95 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) for the `FullTextOrSpatialConstraint` table constraint. - -use crate::ast::{Ident, IndexColumn, KeyOrIndexDisplay}; -use crate::tokenizer::Span; -use core::fmt; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "visitor")] -use sqlparser_derive::{Visit, VisitMut}; - -/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, -/// and MySQL displays both the same way, it is part of this definition as well. -/// -/// Supported syntax: -/// -/// ```markdown -/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...) -/// -/// key_part: col_name -/// ``` -/// -/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html -/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct FullTextOrSpatialConstraint { - /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition. - pub fulltext: bool, - /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. - pub index_type_display: KeyOrIndexDisplay, - /// Optional index name. - pub opt_index_name: Option, - /// Referred column identifier list. - pub columns: Vec, -} - -impl fmt::Display for FullTextOrSpatialConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::ast::display_comma_separated; - - if self.fulltext { - write!(f, "FULLTEXT")?; - } else { - write!(f, "SPATIAL")?; - } - - write!(f, "{:>}", self.index_type_display)?; - - if let Some(name) = &self.opt_index_name { - write!(f, " {name}")?; - } - - write!(f, " ({})", display_comma_separated(&self.columns))?; - - Ok(()) - } -} - -impl crate::ast::Spanned for FullTextOrSpatialConstraint { - fn span(&self) -> Span { - fn union_spans>(iter: I) -> Span { - Span::union_iter(iter) - } - - union_spans( - self.opt_index_name - .iter() - .map(|i| i.span) - .chain(self.columns.iter().map(|i| i.span())), - ) - } -} diff --git a/src/ast/table_constraints/index_constraint.rs b/src/ast/table_constraints/index_constraint.rs deleted file mode 100644 index e7542a005..000000000 --- a/src/ast/table_constraints/index_constraint.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) for the `IndexConstraint` table constraint. - -use crate::ast::{display_comma_separated, Ident, IndexColumn, IndexOption, IndexType}; -use crate::tokenizer::Span; -use core::fmt; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "visitor")] -use sqlparser_derive::{Visit, VisitMut}; - -/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage -/// is restricted to MySQL, as no other dialects that support this syntax were found. -/// -/// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...` -/// -/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct IndexConstraint { - /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax. - pub display_as_key: bool, - /// Index name. - pub name: Option, - /// Optional [index type][1]. - /// - /// [1]: IndexType - pub index_type: Option, - /// Referred column identifier list. - pub columns: Vec, - /// Optional index options such as `USING`; see [`IndexOption`]. - pub index_options: Vec, -} - -impl fmt::Display for IndexConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", if self.display_as_key { "KEY" } else { "INDEX" })?; - if let Some(name) = &self.name { - write!(f, " {name}")?; - } - if let Some(index_type) = &self.index_type { - write!(f, " USING {index_type}")?; - } - write!(f, " ({})", display_comma_separated(&self.columns))?; - if !self.index_options.is_empty() { - write!(f, " {}", display_comma_separated(&self.index_options))?; - } - Ok(()) - } -} - -impl crate::ast::Spanned for IndexConstraint { - fn span(&self) -> Span { - fn union_spans>(iter: I) -> Span { - Span::union_iter(iter) - } - - union_spans( - self.name - .iter() - .map(|i| i.span) - .chain(self.columns.iter().map(|i| i.span())), - ) - } -} diff --git a/src/ast/table_constraints/mod.rs b/src/ast/table_constraints/mod.rs deleted file mode 100644 index 933381c9d..000000000 --- a/src/ast/table_constraints/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) types for table constraints - -mod check_constraint; -pub use check_constraint::CheckConstraint; -mod foreign_key_constraint; -pub use foreign_key_constraint::ForeignKeyConstraint; -mod full_text_or_spatial_constraint; -pub use full_text_or_spatial_constraint::FullTextOrSpatialConstraint; -mod index_constraint; -pub use index_constraint::IndexConstraint; -mod primary_key_constraint; -pub use primary_key_constraint::PrimaryKeyConstraint; -mod unique_constraint; -pub use unique_constraint::UniqueConstraint; diff --git a/src/ast/table_constraints/primary_key_constraint.rs b/src/ast/table_constraints/primary_key_constraint.rs deleted file mode 100644 index ebc442f62..000000000 --- a/src/ast/table_constraints/primary_key_constraint.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) for the `PrimaryKeyConstraint` table constraint. - -use crate::ast::{ - display_comma_separated, display_separated, ConstraintCharacteristics, Ident, IndexColumn, - IndexOption, IndexType, -}; -use crate::tokenizer::Span; -use core::fmt; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "visitor")] -use sqlparser_derive::{Visit, VisitMut}; - -/// MySQL [definition][1] for `PRIMARY KEY` constraints statements: -/// * `[CONSTRAINT []] PRIMARY KEY [index_name] [index_type] () ` -/// -/// Actually the specification have no `[index_name]` but the next query will complete successfully: -/// ```sql -/// CREATE TABLE unspec_table ( -/// xid INT NOT NULL, -/// CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid) -/// ); -/// ``` -/// -/// where: -/// * [index_type][2] is `USING {BTREE | HASH}` -/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...` -/// -/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html -/// [2]: IndexType -/// [3]: IndexOption -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct PrimaryKeyConstraint { - /// Constraint name. - /// - /// Can be not the same as `index_name` - pub name: Option, - /// Index name - pub index_name: Option, - /// Optional `USING` of [index type][1] statement before columns. - /// - /// [1]: IndexType - pub index_type: Option, - /// Identifiers of the columns that form the primary key. - pub columns: Vec, - pub index_options: Vec, - pub characteristics: Option, -} - -impl fmt::Display for PrimaryKeyConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced}; - write!( - f, - "{}PRIMARY KEY{}{} ({})", - display_constraint_name(&self.name), - display_option_spaced(&self.index_name), - display_option(" USING ", "", &self.index_type), - display_comma_separated(&self.columns), - )?; - - if !self.index_options.is_empty() { - write!(f, " {}", display_separated(&self.index_options, " "))?; - } - - write!(f, "{}", display_option_spaced(&self.characteristics))?; - Ok(()) - } -} - -impl crate::ast::Spanned for PrimaryKeyConstraint { - fn span(&self) -> Span { - fn union_spans>(iter: I) -> Span { - Span::union_iter(iter) - } - - union_spans( - self.name - .iter() - .map(|i| i.span) - .chain(self.index_name.iter().map(|i| i.span)) - .chain(self.columns.iter().map(|i| i.span())) - .chain(self.characteristics.iter().map(|i| i.span())), - ) - } -} diff --git a/src/ast/table_constraints/unique_constraint.rs b/src/ast/table_constraints/unique_constraint.rs deleted file mode 100644 index 2043a87ed..000000000 --- a/src/ast/table_constraints/unique_constraint.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//! SQL Abstract Syntax Tree (AST) for the `UniqueConstraint` table constraint. - -use crate::ast::{ - display_comma_separated, display_separated, ConstraintCharacteristics, Ident, IndexColumn, - IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, -}; -use crate::tokenizer::Span; -use core::fmt; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "visitor")] -use sqlparser_derive::{Visit, VisitMut}; - -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct UniqueConstraint { - /// Constraint name. - /// - /// Can be not the same as `index_name` - pub name: Option, - /// Index name - pub index_name: Option, - /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all. - pub index_type_display: KeyOrIndexDisplay, - /// Optional `USING` of [index type][1] statement before columns. - /// - /// [1]: IndexType - pub index_type: Option, - /// Identifiers of the columns that are unique. - pub columns: Vec, - pub index_options: Vec, - pub characteristics: Option, - /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` - pub nulls_distinct: NullsDistinctOption, -} - -impl fmt::Display for UniqueConstraint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced}; - write!( - f, - "{}UNIQUE{}{:>}{}{} ({})", - display_constraint_name(&self.name), - self.nulls_distinct, - self.index_type_display, - display_option_spaced(&self.index_name), - display_option(" USING ", "", &self.index_type), - display_comma_separated(&self.columns), - )?; - - if !self.index_options.is_empty() { - write!(f, " {}", display_separated(&self.index_options, " "))?; - } - - write!(f, "{}", display_option_spaced(&self.characteristics))?; - Ok(()) - } -} - -impl crate::ast::Spanned for UniqueConstraint { - fn span(&self) -> Span { - fn union_spans>(iter: I) -> Span { - Span::union_iter(iter) - } - - union_spans( - self.name - .iter() - .map(|i| i.span) - .chain(self.index_name.iter().map(|i| i.span)) - .chain(self.columns.iter().map(|i| i.span())) - .chain(self.characteristics.iter().map(|i| i.span())), - ) - } -} From 7e72e208280b6cffac5aa25ff81cdbad7546c498 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 5 Oct 2025 11:10:35 +0200 Subject: [PATCH 7/7] Merged with PR changes --- src/ast/ddl.rs | 6 ++---- src/ast/mod.rs | 9 ++++----- src/ast/table_constraints.rs | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 17976ee38..b7e020f5b 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -30,9 +30,8 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::value::escape_single_quote_string; use crate::ast::{ - display_comma_separated, display_separated, - table_constraints::TableConstraint, - ArgMode, CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing, + display_comma_separated, display_separated, table_constraints::TableConstraint, ArgMode, + CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InitializeKind, MySQLColumnPosition, @@ -1030,7 +1029,6 @@ impl fmt::Display for AlterColumnOperation { } } - /// Representation whether a definition can can contains the KEY or INDEX keywords with the same /// meaning. /// diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 369058804..52968d7db 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -68,9 +68,8 @@ pub use self::ddl::{ DropBehavior, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, - ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, - TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, - ViewColumnDef, + ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, }; pub use self::dml::{Delete, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -157,14 +156,14 @@ where } } -pub fn display_separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T> +pub(crate) fn display_separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T> where T: fmt::Display, { DisplaySeparated { slice, sep } } -pub fn display_comma_separated(slice: &[T]) -> DisplaySeparated<'_, T> +pub(crate) fn display_comma_separated(slice: &[T]) -> DisplaySeparated<'_, T> where T: fmt::Display, { diff --git a/src/ast/table_constraints.rs b/src/ast/table_constraints.rs index f7e70b8a5..afcf62959 100644 --- a/src/ast/table_constraints.rs +++ b/src/ast/table_constraints.rs @@ -513,4 +513,4 @@ impl crate::ast::Spanned for UniqueConstraint { .chain(self.characteristics.iter().map(|i| i.span())), ) } -} \ No newline at end of file +}