mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-11 14:32:04 +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::{
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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,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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}]
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue