Moved constraint variant outside of TableConstraint enum (#2054)

Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
Luca Cappelletti 2025-10-08 13:03:26 +02:00 committed by GitHub
parent ade4082656
commit 8c82fc0a19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 688 additions and 484 deletions

View file

@ -30,15 +30,15 @@ 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::TableConstraint, 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;
@ -1029,291 +1029,6 @@ impl fmt::Display for AlterColumnOperation {
}
}
/// A table-level constraint, specified in a `CREATE TABLE` or an
/// `ALTER TABLE ADD <constraint>` 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 [<name>]] UNIQUE <index_type_display> [<index_name>] [index_type] (<columns>) <index_options>`
///
/// 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 {
/// Constraint name.
///
/// Can be not the same as `index_name`
name: Option<Ident>,
/// Index name
index_name: Option<Ident>,
/// 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<IndexType>,
/// Identifiers of the columns that are unique.
columns: Vec<IndexColumn>,
index_options: Vec<IndexOption>,
characteristics: Option<ConstraintCharacteristics>,
/// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
nulls_distinct: NullsDistinctOption,
},
/// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\
/// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
///
/// 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 {
/// Constraint name.
///
/// Can be not the same as `index_name`
name: Option<Ident>,
/// Index name
index_name: Option<Ident>,
/// Optional `USING` of [index type][1] statement before columns.
///
/// [1]: IndexType
index_type: Option<IndexType>,
/// Identifiers of the columns that form the primary key.
columns: Vec<IndexColumn>,
index_options: Vec<IndexOption>,
characteristics: Option<ConstraintCharacteristics>,
},
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
/// REFERENCES <foreign_table> (<referred_columns>)
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
/// }`).
ForeignKey {
name: Option<Ident>,
/// MySQL-specific field
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html>
index_name: Option<Ident>,
columns: Vec<Ident>,
foreign_table: ObjectName,
referred_columns: Vec<Ident>,
on_delete: Option<ReferentialAction>,
on_update: Option<ReferentialAction>,
characteristics: Option<ConstraintCharacteristics>,
},
/// `[ CONSTRAINT <name> ] CHECK (<expr>) [[NOT] ENFORCED]`
Check {
name: Option<Ident>,
expr: Box<Expr>,
/// MySQL-specific syntax
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
enforced: Option<bool>,
},
/// 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<Ident>,
/// Optional [index type][1].
///
/// [1]: IndexType
index_type: Option<IndexType>,
/// Referred column identifier list.
columns: Vec<IndexColumn>,
/// Optional index options such as `USING`; see [`IndexOption`].
index_options: Vec<IndexOption>,
},
/// 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 {
/// 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<Ident>,
/// Referred column identifier list.
columns: Vec<IndexColumn>,
},
}
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),
)?;
if !index_options.is_empty() {
write!(f, " {}", display_separated(index_options, " "))?;
}
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")?;
}
write!(f, "{index_type_display:>}")?;
if let Some(name) = opt_index_name {
write!(f, " {name}")?;
}
write!(f, " ({})", display_comma_separated(columns))?;
Ok(())
}
}
}
}
/// Representation whether a definition can can contains the KEY or INDEX keywords with the same
/// meaning.
///
@ -2065,7 +1780,7 @@ pub enum GeneratedExpressionMode {
}
#[must_use]
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
pub(crate) fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
struct ConstraintName<'a>(&'a Option<Ident>);
impl fmt::Display for ConstraintName<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -2082,7 +1797,7 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
/// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"`
/// * `_` => do nothing
#[must_use]
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<T>,
@ -2104,7 +1819,7 @@ fn display_option<'a, T: fmt::Display>(
/// * `Some(inner)` => create display struct for `" {inner}"`
/// * `_` => do nothing
#[must_use]
fn display_option_spaced<T: fmt::Display>(option: &Option<T>) -> impl fmt::Display + '_ {
pub(crate) fn display_option_spaced<T: fmt::Display>(option: &Option<T>) -> impl fmt::Display + '_ {
display_option(" ", "", option)
}

View file

@ -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, TableConstraint,
TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
ViewColumnDef,
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
};
pub use self::dml::{Delete, Insert};
pub use self::operator::{BinaryOperator, UnaryOperator};
@ -118,6 +117,11 @@ mod dcl;
mod ddl;
mod dml;
pub mod helpers;
pub mod table_constraints;
pub use table_constraints::{
CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint,
PrimaryKeyConstraint, TableConstraint, UniqueConstraint,
};
mod operator;
mod query;
mod spans;
@ -152,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<T>(slice: &[T]) -> DisplaySeparated<'_, T>
pub(crate) fn display_comma_separated<T>(slice: &[T]) -> DisplaySeparated<'_, T>
where
T: fmt::Display,
{

View file

@ -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(),
}
}
}

View file

@ -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 <constraint>` 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 [<name>]] UNIQUE <index_type_display> [<index_name>] [index_type] (<columns>) <index_options>`
///
/// 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 [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
///
/// 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 <name> ] FOREIGN KEY (<columns>)
/// REFERENCES <foreign_table> (<referred_columns>)
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
/// }`).
ForeignKey(ForeignKeyConstraint),
/// `[ CONSTRAINT <name> ] CHECK (<expr>) [[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<UniqueConstraint> for TableConstraint {
fn from(constraint: UniqueConstraint) -> Self {
TableConstraint::Unique(constraint)
}
}
impl From<PrimaryKeyConstraint> for TableConstraint {
fn from(constraint: PrimaryKeyConstraint) -> Self {
TableConstraint::PrimaryKey(constraint)
}
}
impl From<ForeignKeyConstraint> for TableConstraint {
fn from(constraint: ForeignKeyConstraint) -> Self {
TableConstraint::ForeignKey(constraint)
}
}
impl From<CheckConstraint> for TableConstraint {
fn from(constraint: CheckConstraint) -> Self {
TableConstraint::Check(constraint)
}
}
impl From<IndexConstraint> for TableConstraint {
fn from(constraint: IndexConstraint) -> Self {
TableConstraint::Index(constraint)
}
}
impl From<FullTextOrSpatialConstraint> 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<Ident>,
pub expr: Box<Expr>,
/// MySQL-specific syntax
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
pub enforced: Option<bool>,
}
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 <name> ] FOREIGN KEY (<columns>)
/// REFERENCES <foreign_table> (<referred_columns>)
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
/// }`).
#[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<Ident>,
/// MySQL-specific field
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html>
pub index_name: Option<Ident>,
pub columns: Vec<Ident>,
pub foreign_table: ObjectName,
pub referred_columns: Vec<Ident>,
pub on_delete: Option<ReferentialAction>,
pub on_update: Option<ReferentialAction>,
pub characteristics: Option<ConstraintCharacteristics>,
}
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<I: Iterator<Item = Span>>(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<Ident>,
/// Referred column identifier list.
pub columns: Vec<IndexColumn>,
}
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<I: Iterator<Item = Span>>(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<Ident>,
/// Optional [index type][1].
///
/// [1]: IndexType
pub index_type: Option<IndexType>,
/// Referred column identifier list.
pub columns: Vec<IndexColumn>,
/// Optional index options such as `USING`; see [`IndexOption`].
pub index_options: Vec<IndexOption>,
}
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<I: Iterator<Item = Span>>(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 [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
///
/// 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<Ident>,
/// Index name
pub index_name: Option<Ident>,
/// Optional `USING` of [index type][1] statement before columns.
///
/// [1]: IndexType
pub index_type: Option<IndexType>,
/// Identifiers of the columns that form the primary key.
pub columns: Vec<IndexColumn>,
pub index_options: Vec<IndexOption>,
pub characteristics: Option<ConstraintCharacteristics>,
}
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<I: Iterator<Item = Span>>(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<Ident>,
/// Index name
pub index_name: Option<Ident>,
/// 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<IndexType>,
/// Identifiers of the columns that are unique.
pub columns: Vec<IndexColumn>,
pub index_options: Vec<IndexOption>,
pub characteristics: Option<ConstraintCharacteristics>,
/// 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<I: Iterator<Item = Span>>(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())),
)
}
}

View file

@ -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()
);
}

View file

@ -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)"),
}
}

View file

@ -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);

View file

@ -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(),
}
}

View file

@ -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,
}]
);