mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-13 15:32:03 +00:00
Moved constraint variant outside of TableConstraint
enum (#2054)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
parent
ade4082656
commit
8c82fc0a19
9 changed files with 688 additions and 484 deletions
309
src/ast/ddl.rs
309
src/ast/ddl.rs
|
@ -30,15 +30,15 @@ use sqlparser_derive::{Visit, VisitMut};
|
||||||
|
|
||||||
use crate::ast::value::escape_single_quote_string;
|
use crate::ast::value::escape_single_quote_string;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
display_comma_separated, display_separated, ArgMode, CommentDef, ConditionalStatements,
|
display_comma_separated, display_separated, table_constraints::TableConstraint, ArgMode,
|
||||||
CreateFunctionBody, CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, DataType,
|
CommentDef, ConditionalStatements, CreateFunctionBody, CreateFunctionUsing,
|
||||||
Expr, FileFormat, FunctionBehavior, FunctionCalledOnNull, FunctionDeterminismSpecifier,
|
CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior,
|
||||||
FunctionParallel, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident,
|
FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle,
|
||||||
InitializeKind, MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens,
|
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InitializeKind, MySQLColumnPosition,
|
||||||
OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind, RowAccessPolicy,
|
ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect,
|
||||||
SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, TableVersion, Tag,
|
Query, RefreshModeKind, RowAccessPolicy, SequenceOptions, Spanned, SqlOption,
|
||||||
TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value,
|
StorageSerializationPolicy, TableVersion, Tag, TriggerEvent, TriggerExecBody, TriggerObject,
|
||||||
ValueWithSpan, WrappedCollection,
|
TriggerPeriod, TriggerReferencing, Value, ValueWithSpan, WrappedCollection,
|
||||||
};
|
};
|
||||||
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
|
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
|
||||||
use crate::keywords::Keyword;
|
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
|
/// Representation whether a definition can can contains the KEY or INDEX keywords with the same
|
||||||
/// meaning.
|
/// meaning.
|
||||||
///
|
///
|
||||||
|
@ -2065,7 +1780,7 @@ pub enum GeneratedExpressionMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[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>);
|
struct ConstraintName<'a>(&'a Option<Ident>);
|
||||||
impl fmt::Display for ConstraintName<'_> {
|
impl fmt::Display for ConstraintName<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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}"`
|
/// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"`
|
||||||
/// * `_` => do nothing
|
/// * `_` => do nothing
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn display_option<'a, T: fmt::Display>(
|
pub(crate) fn display_option<'a, T: fmt::Display>(
|
||||||
prefix: &'a str,
|
prefix: &'a str,
|
||||||
postfix: &'a str,
|
postfix: &'a str,
|
||||||
option: &'a Option<T>,
|
option: &'a Option<T>,
|
||||||
|
@ -2104,7 +1819,7 @@ fn display_option<'a, T: fmt::Display>(
|
||||||
/// * `Some(inner)` => create display struct for `" {inner}"`
|
/// * `Some(inner)` => create display struct for `" {inner}"`
|
||||||
/// * `_` => do nothing
|
/// * `_` => do nothing
|
||||||
#[must_use]
|
#[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)
|
display_option(" ", "", option)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,9 +68,8 @@ pub use self::ddl::{
|
||||||
DropBehavior, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
|
DropBehavior, DropTrigger, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
|
||||||
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
|
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
|
||||||
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition,
|
IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition,
|
||||||
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TableConstraint,
|
ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity, TagsColumnOption,
|
||||||
TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
|
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
|
||||||
ViewColumnDef,
|
|
||||||
};
|
};
|
||||||
pub use self::dml::{Delete, Insert};
|
pub use self::dml::{Delete, Insert};
|
||||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||||
|
@ -118,6 +117,11 @@ mod dcl;
|
||||||
mod ddl;
|
mod ddl;
|
||||||
mod dml;
|
mod dml;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
|
pub mod table_constraints;
|
||||||
|
pub use table_constraints::{
|
||||||
|
CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint,
|
||||||
|
PrimaryKeyConstraint, TableConstraint, UniqueConstraint,
|
||||||
|
};
|
||||||
mod operator;
|
mod operator;
|
||||||
mod query;
|
mod query;
|
||||||
mod spans;
|
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
|
where
|
||||||
T: fmt::Display,
|
T: fmt::Display,
|
||||||
{
|
{
|
||||||
DisplaySeparated { slice, sep }
|
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
|
where
|
||||||
T: fmt::Display,
|
T: fmt::Display,
|
||||||
{
|
{
|
||||||
|
|
|
@ -670,83 +670,12 @@ impl Spanned for ColumnOptionDef {
|
||||||
impl Spanned for TableConstraint {
|
impl Spanned for TableConstraint {
|
||||||
fn span(&self) -> Span {
|
fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
TableConstraint::Unique {
|
TableConstraint::Unique(constraint) => constraint.span(),
|
||||||
name,
|
TableConstraint::PrimaryKey(constraint) => constraint.span(),
|
||||||
index_name,
|
TableConstraint::ForeignKey(constraint) => constraint.span(),
|
||||||
index_type_display: _,
|
TableConstraint::Check(constraint) => constraint.span(),
|
||||||
index_type: _,
|
TableConstraint::Index(constraint) => constraint.span(),
|
||||||
columns,
|
TableConstraint::FulltextOrSpatial(constraint) => constraint.span(),
|
||||||
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())),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
516
src/ast/table_constraints.rs
Normal file
516
src/ast/table_constraints.rs
Normal 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())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8398,7 +8398,8 @@ impl<'a> Parser<'a> {
|
||||||
let columns = self.parse_parenthesized_index_column_list()?;
|
let columns = self.parse_parenthesized_index_column_list()?;
|
||||||
let index_options = self.parse_index_options()?;
|
let index_options = self.parse_index_options()?;
|
||||||
let characteristics = self.parse_constraint_characteristics()?;
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
Ok(Some(TableConstraint::Unique {
|
Ok(Some(
|
||||||
|
UniqueConstraint {
|
||||||
name,
|
name,
|
||||||
index_name,
|
index_name,
|
||||||
index_type_display,
|
index_type_display,
|
||||||
|
@ -8407,7 +8408,9 @@ impl<'a> Parser<'a> {
|
||||||
index_options,
|
index_options,
|
||||||
characteristics,
|
characteristics,
|
||||||
nulls_distinct,
|
nulls_distinct,
|
||||||
}))
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Token::Word(w) if w.keyword == Keyword::PRIMARY => {
|
Token::Word(w) if w.keyword == Keyword::PRIMARY => {
|
||||||
// after `PRIMARY` always stay `KEY`
|
// after `PRIMARY` always stay `KEY`
|
||||||
|
@ -8420,14 +8423,17 @@ impl<'a> Parser<'a> {
|
||||||
let columns = self.parse_parenthesized_index_column_list()?;
|
let columns = self.parse_parenthesized_index_column_list()?;
|
||||||
let index_options = self.parse_index_options()?;
|
let index_options = self.parse_index_options()?;
|
||||||
let characteristics = self.parse_constraint_characteristics()?;
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
Ok(Some(TableConstraint::PrimaryKey {
|
Ok(Some(
|
||||||
|
PrimaryKeyConstraint {
|
||||||
name,
|
name,
|
||||||
index_name,
|
index_name,
|
||||||
index_type,
|
index_type,
|
||||||
columns,
|
columns,
|
||||||
index_options,
|
index_options,
|
||||||
characteristics,
|
characteristics,
|
||||||
}))
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
|
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
|
||||||
self.expect_keyword_is(Keyword::KEY)?;
|
self.expect_keyword_is(Keyword::KEY)?;
|
||||||
|
@ -8452,7 +8458,8 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
let characteristics = self.parse_constraint_characteristics()?;
|
let characteristics = self.parse_constraint_characteristics()?;
|
||||||
|
|
||||||
Ok(Some(TableConstraint::ForeignKey {
|
Ok(Some(
|
||||||
|
ForeignKeyConstraint {
|
||||||
name,
|
name,
|
||||||
index_name,
|
index_name,
|
||||||
columns,
|
columns,
|
||||||
|
@ -8461,7 +8468,9 @@ impl<'a> Parser<'a> {
|
||||||
on_delete,
|
on_delete,
|
||||||
on_update,
|
on_update,
|
||||||
characteristics,
|
characteristics,
|
||||||
}))
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Token::Word(w) if w.keyword == Keyword::CHECK => {
|
Token::Word(w) if w.keyword == Keyword::CHECK => {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
|
@ -8476,11 +8485,14 @@ impl<'a> Parser<'a> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(TableConstraint::Check {
|
Ok(Some(
|
||||||
|
CheckConstraint {
|
||||||
name,
|
name,
|
||||||
expr,
|
expr,
|
||||||
enforced,
|
enforced,
|
||||||
}))
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Token::Word(w)
|
Token::Word(w)
|
||||||
if (w.keyword == Keyword::INDEX || w.keyword == Keyword::KEY)
|
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 columns = self.parse_parenthesized_index_column_list()?;
|
||||||
let index_options = self.parse_index_options()?;
|
let index_options = self.parse_index_options()?;
|
||||||
|
|
||||||
Ok(Some(TableConstraint::Index {
|
Ok(Some(
|
||||||
|
IndexConstraint {
|
||||||
display_as_key,
|
display_as_key,
|
||||||
name,
|
name,
|
||||||
index_type,
|
index_type,
|
||||||
columns,
|
columns,
|
||||||
index_options,
|
index_options,
|
||||||
}))
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Token::Word(w)
|
Token::Word(w)
|
||||||
if (w.keyword == Keyword::FULLTEXT || w.keyword == Keyword::SPATIAL)
|
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()?;
|
let columns = self.parse_parenthesized_index_column_list()?;
|
||||||
|
|
||||||
Ok(Some(TableConstraint::FulltextOrSpatial {
|
Ok(Some(
|
||||||
|
FullTextOrSpatialConstraint {
|
||||||
fulltext,
|
fulltext,
|
||||||
index_type_display,
|
index_type_display,
|
||||||
opt_index_name,
|
opt_index_name,
|
||||||
columns,
|
columns,
|
||||||
}))
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if name.is_some() {
|
if name.is_some() {
|
||||||
|
@ -18136,85 +18154,91 @@ mod tests {
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"INDEX (c1)",
|
"INDEX (c1)",
|
||||||
TableConstraint::Index {
|
IndexConstraint {
|
||||||
display_as_key: false,
|
display_as_key: false,
|
||||||
name: None,
|
name: None,
|
||||||
index_type: None,
|
index_type: None,
|
||||||
columns: vec![mk_expected_col("c1")],
|
columns: vec![mk_expected_col("c1")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"KEY (c1)",
|
"KEY (c1)",
|
||||||
TableConstraint::Index {
|
IndexConstraint {
|
||||||
display_as_key: true,
|
display_as_key: true,
|
||||||
name: None,
|
name: None,
|
||||||
index_type: None,
|
index_type: None,
|
||||||
columns: vec![mk_expected_col("c1")],
|
columns: vec![mk_expected_col("c1")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"INDEX 'index' (c1, c2)",
|
"INDEX 'index' (c1, c2)",
|
||||||
TableConstraint::Index {
|
TableConstraint::Index(IndexConstraint {
|
||||||
display_as_key: false,
|
display_as_key: false,
|
||||||
name: Some(Ident::with_quote('\'', "index")),
|
name: Some(Ident::with_quote('\'', "index")),
|
||||||
index_type: None,
|
index_type: None,
|
||||||
columns: vec![mk_expected_col("c1"), mk_expected_col("c2")],
|
columns: vec![mk_expected_col("c1"), mk_expected_col("c2")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"INDEX USING BTREE (c1)",
|
"INDEX USING BTREE (c1)",
|
||||||
TableConstraint::Index {
|
IndexConstraint {
|
||||||
display_as_key: false,
|
display_as_key: false,
|
||||||
name: None,
|
name: None,
|
||||||
index_type: Some(IndexType::BTree),
|
index_type: Some(IndexType::BTree),
|
||||||
columns: vec![mk_expected_col("c1")],
|
columns: vec![mk_expected_col("c1")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"INDEX USING HASH (c1)",
|
"INDEX USING HASH (c1)",
|
||||||
TableConstraint::Index {
|
IndexConstraint {
|
||||||
display_as_key: false,
|
display_as_key: false,
|
||||||
name: None,
|
name: None,
|
||||||
index_type: Some(IndexType::Hash),
|
index_type: Some(IndexType::Hash),
|
||||||
columns: vec![mk_expected_col("c1")],
|
columns: vec![mk_expected_col("c1")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"INDEX idx_name USING BTREE (c1)",
|
"INDEX idx_name USING BTREE (c1)",
|
||||||
TableConstraint::Index {
|
IndexConstraint {
|
||||||
display_as_key: false,
|
display_as_key: false,
|
||||||
name: Some(Ident::new("idx_name")),
|
name: Some(Ident::new("idx_name")),
|
||||||
index_type: Some(IndexType::BTree),
|
index_type: Some(IndexType::BTree),
|
||||||
columns: vec![mk_expected_col("c1")],
|
columns: vec![mk_expected_col("c1")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_table_constraint!(
|
test_parse_table_constraint!(
|
||||||
dialect,
|
dialect,
|
||||||
"INDEX idx_name USING HASH (c1)",
|
"INDEX idx_name USING HASH (c1)",
|
||||||
TableConstraint::Index {
|
IndexConstraint {
|
||||||
display_as_key: false,
|
display_as_key: false,
|
||||||
name: Some(Ident::new("idx_name")),
|
name: Some(Ident::new("idx_name")),
|
||||||
index_type: Some(IndexType::Hash),
|
index_type: Some(IndexType::Hash),
|
||||||
columns: vec![mk_expected_col("c1")],
|
columns: vec![mk_expected_col("c1")],
|
||||||
index_options: vec![],
|
index_options: vec![],
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -469,17 +469,17 @@ pub fn index_column(stmt: Statement) -> Expr {
|
||||||
}
|
}
|
||||||
Statement::CreateTable(CreateTable { constraints, .. }) => {
|
Statement::CreateTable(CreateTable { constraints, .. }) => {
|
||||||
match constraints.first().unwrap() {
|
match constraints.first().unwrap() {
|
||||||
TableConstraint::Index { columns, .. } => {
|
TableConstraint::Index(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
constraint.columns.first().unwrap().column.expr.clone()
|
||||||
}
|
}
|
||||||
TableConstraint::Unique { columns, .. } => {
|
TableConstraint::Unique(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
constraint.columns.first().unwrap().column.expr.clone()
|
||||||
}
|
}
|
||||||
TableConstraint::PrimaryKey { columns, .. } => {
|
TableConstraint::PrimaryKey(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
constraint.columns.first().unwrap().column.expr.clone()
|
||||||
}
|
}
|
||||||
TableConstraint::FulltextOrSpatial { columns, .. } => {
|
TableConstraint::FulltextOrSpatial(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
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)"),
|
_ => 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() {
|
Statement::AlterTable { operations, .. } => match operations.first().unwrap() {
|
||||||
AlterTableOperation::AddConstraint { constraint, .. } => {
|
AlterTableOperation::AddConstraint { constraint, .. } => {
|
||||||
match constraint {
|
match constraint {
|
||||||
TableConstraint::Index { columns, .. } => {
|
TableConstraint::Index(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
constraint.columns.first().unwrap().column.expr.clone()
|
||||||
}
|
}
|
||||||
TableConstraint::Unique { columns, .. } => {
|
TableConstraint::Unique(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
constraint.columns.first().unwrap().column.expr.clone()
|
||||||
}
|
}
|
||||||
TableConstraint::PrimaryKey { columns, .. } => {
|
TableConstraint::PrimaryKey(constraint) => {
|
||||||
columns.first().unwrap().column.expr.clone()
|
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)"),
|
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3818,7 +3818,7 @@ fn parse_create_table() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
constraints,
|
constraints,
|
||||||
vec![
|
vec![
|
||||||
TableConstraint::ForeignKey {
|
ForeignKeyConstraint {
|
||||||
name: Some("fkey".into()),
|
name: Some("fkey".into()),
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lat".into()],
|
columns: vec!["lat".into()],
|
||||||
|
@ -3827,8 +3827,9 @@ fn parse_create_table() {
|
||||||
on_delete: Some(ReferentialAction::Restrict),
|
on_delete: Some(ReferentialAction::Restrict),
|
||||||
on_update: None,
|
on_update: None,
|
||||||
characteristics: None,
|
characteristics: None,
|
||||||
},
|
}
|
||||||
TableConstraint::ForeignKey {
|
.into(),
|
||||||
|
ForeignKeyConstraint {
|
||||||
name: Some("fkey2".into()),
|
name: Some("fkey2".into()),
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lat".into()],
|
columns: vec!["lat".into()],
|
||||||
|
@ -3837,8 +3838,9 @@ fn parse_create_table() {
|
||||||
on_delete: Some(ReferentialAction::NoAction),
|
on_delete: Some(ReferentialAction::NoAction),
|
||||||
on_update: Some(ReferentialAction::Restrict),
|
on_update: Some(ReferentialAction::Restrict),
|
||||||
characteristics: None,
|
characteristics: None,
|
||||||
},
|
}
|
||||||
TableConstraint::ForeignKey {
|
.into(),
|
||||||
|
ForeignKeyConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lat".into()],
|
columns: vec!["lat".into()],
|
||||||
|
@ -3847,8 +3849,9 @@ fn parse_create_table() {
|
||||||
on_delete: Some(ReferentialAction::Cascade),
|
on_delete: Some(ReferentialAction::Cascade),
|
||||||
on_update: Some(ReferentialAction::SetDefault),
|
on_update: Some(ReferentialAction::SetDefault),
|
||||||
characteristics: None,
|
characteristics: None,
|
||||||
},
|
}
|
||||||
TableConstraint::ForeignKey {
|
.into(),
|
||||||
|
ForeignKeyConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lng".into()],
|
columns: vec!["lng".into()],
|
||||||
|
@ -3857,7 +3860,8 @@ fn parse_create_table() {
|
||||||
on_delete: None,
|
on_delete: None,
|
||||||
on_update: Some(ReferentialAction::SetNull),
|
on_update: Some(ReferentialAction::SetNull),
|
||||||
characteristics: None,
|
characteristics: None,
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(table_options, CreateTableOptions::None);
|
assert_eq!(table_options, CreateTableOptions::None);
|
||||||
|
@ -3945,7 +3949,7 @@ fn parse_create_table_with_constraint_characteristics() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
constraints,
|
constraints,
|
||||||
vec![
|
vec![
|
||||||
TableConstraint::ForeignKey {
|
ForeignKeyConstraint {
|
||||||
name: Some("fkey".into()),
|
name: Some("fkey".into()),
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lat".into()],
|
columns: vec!["lat".into()],
|
||||||
|
@ -3958,8 +3962,9 @@ fn parse_create_table_with_constraint_characteristics() {
|
||||||
initially: Some(DeferrableInitial::Deferred),
|
initially: Some(DeferrableInitial::Deferred),
|
||||||
enforced: None
|
enforced: None
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
TableConstraint::ForeignKey {
|
.into(),
|
||||||
|
ForeignKeyConstraint {
|
||||||
name: Some("fkey2".into()),
|
name: Some("fkey2".into()),
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lat".into()],
|
columns: vec!["lat".into()],
|
||||||
|
@ -3972,8 +3977,9 @@ fn parse_create_table_with_constraint_characteristics() {
|
||||||
initially: Some(DeferrableInitial::Immediate),
|
initially: Some(DeferrableInitial::Immediate),
|
||||||
enforced: None,
|
enforced: None,
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
TableConstraint::ForeignKey {
|
.into(),
|
||||||
|
ForeignKeyConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lat".into()],
|
columns: vec!["lat".into()],
|
||||||
|
@ -3986,8 +3992,9 @@ fn parse_create_table_with_constraint_characteristics() {
|
||||||
initially: Some(DeferrableInitial::Deferred),
|
initially: Some(DeferrableInitial::Deferred),
|
||||||
enforced: Some(false),
|
enforced: Some(false),
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
TableConstraint::ForeignKey {
|
.into(),
|
||||||
|
ForeignKeyConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["lng".into()],
|
columns: vec!["lng".into()],
|
||||||
|
@ -4000,7 +4007,8 @@ fn parse_create_table_with_constraint_characteristics() {
|
||||||
initially: Some(DeferrableInitial::Immediate),
|
initially: Some(DeferrableInitial::Immediate),
|
||||||
enforced: Some(true),
|
enforced: Some(true),
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(table_options, CreateTableOptions::None);
|
assert_eq!(table_options, CreateTableOptions::None);
|
||||||
|
|
|
@ -684,7 +684,7 @@ fn table_constraint_unique_primary_ctor(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
match unique_index_type_display {
|
match unique_index_type_display {
|
||||||
Some(index_type_display) => TableConstraint::Unique {
|
Some(index_type_display) => UniqueConstraint {
|
||||||
name,
|
name,
|
||||||
index_name,
|
index_name,
|
||||||
index_type_display,
|
index_type_display,
|
||||||
|
@ -693,15 +693,17 @@ fn table_constraint_unique_primary_ctor(
|
||||||
index_options,
|
index_options,
|
||||||
characteristics,
|
characteristics,
|
||||||
nulls_distinct: NullsDistinctOption::None,
|
nulls_distinct: NullsDistinctOption::None,
|
||||||
},
|
}
|
||||||
None => TableConstraint::PrimaryKey {
|
.into(),
|
||||||
|
None => PrimaryKeyConstraint {
|
||||||
name,
|
name,
|
||||||
index_name,
|
index_name,
|
||||||
index_type,
|
index_type,
|
||||||
columns,
|
columns,
|
||||||
index_options,
|
index_options,
|
||||||
characteristics,
|
characteristics,
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -607,9 +607,10 @@ fn parse_alter_table_constraints_unique_nulls_distinct() {
|
||||||
{
|
{
|
||||||
Statement::AlterTable { operations, .. } => match &operations[0] {
|
Statement::AlterTable { operations, .. } => match &operations[0] {
|
||||||
AlterTableOperation::AddConstraint {
|
AlterTableOperation::AddConstraint {
|
||||||
constraint: TableConstraint::Unique { nulls_distinct, .. },
|
constraint: TableConstraint::Unique(constraint),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
let nulls_distinct = &constraint.nulls_distinct;
|
||||||
assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct)
|
assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -5578,7 +5579,7 @@ fn parse_create_domain() {
|
||||||
data_type: DataType::Integer(None),
|
data_type: DataType::Integer(None),
|
||||||
collation: None,
|
collation: None,
|
||||||
default: None,
|
default: None,
|
||||||
constraints: vec![TableConstraint::Check {
|
constraints: vec![CheckConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
expr: Box::new(Expr::BinaryOp {
|
expr: Box::new(Expr::BinaryOp {
|
||||||
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
|
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())),
|
right: Box::new(Expr::Value(test_utils::number("0").into())),
|
||||||
}),
|
}),
|
||||||
enforced: None,
|
enforced: None,
|
||||||
}],
|
}
|
||||||
|
.into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(pg().verified_stmt(sql1), expected);
|
assert_eq!(pg().verified_stmt(sql1), expected);
|
||||||
|
@ -5597,7 +5599,7 @@ fn parse_create_domain() {
|
||||||
data_type: DataType::Integer(None),
|
data_type: DataType::Integer(None),
|
||||||
collation: Some(Ident::with_quote('"', "en_US")),
|
collation: Some(Ident::with_quote('"', "en_US")),
|
||||||
default: None,
|
default: None,
|
||||||
constraints: vec![TableConstraint::Check {
|
constraints: vec![CheckConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
expr: Box::new(Expr::BinaryOp {
|
expr: Box::new(Expr::BinaryOp {
|
||||||
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
|
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())),
|
right: Box::new(Expr::Value(test_utils::number("0").into())),
|
||||||
}),
|
}),
|
||||||
enforced: None,
|
enforced: None,
|
||||||
}],
|
}
|
||||||
|
.into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(pg().verified_stmt(sql2), expected);
|
assert_eq!(pg().verified_stmt(sql2), expected);
|
||||||
|
@ -5616,7 +5619,7 @@ fn parse_create_domain() {
|
||||||
data_type: DataType::Integer(None),
|
data_type: DataType::Integer(None),
|
||||||
collation: None,
|
collation: None,
|
||||||
default: Some(Expr::Value(test_utils::number("1").into())),
|
default: Some(Expr::Value(test_utils::number("1").into())),
|
||||||
constraints: vec![TableConstraint::Check {
|
constraints: vec![CheckConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
expr: Box::new(Expr::BinaryOp {
|
expr: Box::new(Expr::BinaryOp {
|
||||||
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
|
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())),
|
right: Box::new(Expr::Value(test_utils::number("0").into())),
|
||||||
}),
|
}),
|
||||||
enforced: None,
|
enforced: None,
|
||||||
}],
|
}
|
||||||
|
.into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(pg().verified_stmt(sql3), expected);
|
assert_eq!(pg().verified_stmt(sql3), expected);
|
||||||
|
@ -5635,7 +5639,7 @@ fn parse_create_domain() {
|
||||||
data_type: DataType::Integer(None),
|
data_type: DataType::Integer(None),
|
||||||
collation: Some(Ident::with_quote('"', "en_US")),
|
collation: Some(Ident::with_quote('"', "en_US")),
|
||||||
default: Some(Expr::Value(test_utils::number("1").into())),
|
default: Some(Expr::Value(test_utils::number("1").into())),
|
||||||
constraints: vec![TableConstraint::Check {
|
constraints: vec![CheckConstraint {
|
||||||
name: None,
|
name: None,
|
||||||
expr: Box::new(Expr::BinaryOp {
|
expr: Box::new(Expr::BinaryOp {
|
||||||
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
|
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())),
|
right: Box::new(Expr::Value(test_utils::number("0").into())),
|
||||||
}),
|
}),
|
||||||
enforced: None,
|
enforced: None,
|
||||||
}],
|
}
|
||||||
|
.into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(pg().verified_stmt(sql4), expected);
|
assert_eq!(pg().verified_stmt(sql4), expected);
|
||||||
|
@ -5654,7 +5659,7 @@ fn parse_create_domain() {
|
||||||
data_type: DataType::Integer(None),
|
data_type: DataType::Integer(None),
|
||||||
collation: None,
|
collation: None,
|
||||||
default: None,
|
default: None,
|
||||||
constraints: vec![TableConstraint::Check {
|
constraints: vec![CheckConstraint {
|
||||||
name: Some(Ident::new("my_constraint")),
|
name: Some(Ident::new("my_constraint")),
|
||||||
expr: Box::new(Expr::BinaryOp {
|
expr: Box::new(Expr::BinaryOp {
|
||||||
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
|
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())),
|
right: Box::new(Expr::Value(test_utils::number("0").into())),
|
||||||
}),
|
}),
|
||||||
enforced: None,
|
enforced: None,
|
||||||
}],
|
}
|
||||||
|
.into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(pg().verified_stmt(sql5), expected);
|
assert_eq!(pg().verified_stmt(sql5), expected);
|
||||||
|
@ -6467,7 +6473,7 @@ fn parse_alter_table_constraint_not_valid() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
operations,
|
operations,
|
||||||
vec![AlterTableOperation::AddConstraint {
|
vec![AlterTableOperation::AddConstraint {
|
||||||
constraint: TableConstraint::ForeignKey {
|
constraint: ForeignKeyConstraint {
|
||||||
name: Some("bar".into()),
|
name: Some("bar".into()),
|
||||||
index_name: None,
|
index_name: None,
|
||||||
columns: vec!["baz".into()],
|
columns: vec!["baz".into()],
|
||||||
|
@ -6476,7 +6482,8 @@ fn parse_alter_table_constraint_not_valid() {
|
||||||
on_delete: None,
|
on_delete: None,
|
||||||
on_update: None,
|
on_update: None,
|
||||||
characteristics: None,
|
characteristics: None,
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
not_valid: true,
|
not_valid: true,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue