mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 23:14:07 +00:00
Support MySQL UNIQUE
table constraint (#1164)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
6da8828c1b
commit
8f67d1a713
4 changed files with 441 additions and 94 deletions
176
src/ast/ddl.rs
176
src/ast/ddl.rs
|
@ -15,7 +15,7 @@
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||
use core::fmt;
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -397,12 +397,68 @@ impl fmt::Display for AlterColumnOperation {
|
|||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum TableConstraint {
|
||||
/// `[ CONSTRAINT <name> ] { PRIMARY KEY | UNIQUE } (<columns>)`
|
||||
/// 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<Ident>,
|
||||
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
|
||||
is_primary: bool,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// 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<Ident>,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
},
|
||||
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
||||
|
@ -472,22 +528,51 @@ impl fmt::Display for TableConstraint {
|
|||
match self {
|
||||
TableConstraint::Unique {
|
||||
name,
|
||||
index_name,
|
||||
index_type_display,
|
||||
index_type,
|
||||
columns,
|
||||
is_primary,
|
||||
index_options,
|
||||
characteristics,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"{}{} ({})",
|
||||
"{}UNIQUE{index_type_display:>}{}{} ({})",
|
||||
display_constraint_name(name),
|
||||
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
|
||||
display_comma_separated(columns)
|
||||
display_option_spaced(index_name),
|
||||
display_option(" USING ", "", index_type),
|
||||
display_comma_separated(columns),
|
||||
)?;
|
||||
|
||||
if let Some(characteristics) = characteristics {
|
||||
write!(f, " {}", characteristics)?;
|
||||
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 {
|
||||
|
@ -550,9 +635,7 @@ impl fmt::Display for TableConstraint {
|
|||
write!(f, "SPATIAL")?;
|
||||
}
|
||||
|
||||
if !matches!(index_type_display, KeyOrIndexDisplay::None) {
|
||||
write!(f, " {index_type_display}")?;
|
||||
}
|
||||
write!(f, "{index_type_display:>}")?;
|
||||
|
||||
if let Some(name) = opt_index_name {
|
||||
write!(f, " {name}")?;
|
||||
|
@ -585,8 +668,20 @@ pub enum KeyOrIndexDisplay {
|
|||
Index,
|
||||
}
|
||||
|
||||
impl KeyOrIndexDisplay {
|
||||
pub fn is_none(self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyOrIndexDisplay {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let left_space = matches!(f.align(), Some(fmt::Alignment::Right));
|
||||
|
||||
if left_space && !self.is_none() {
|
||||
f.write_char(' ')?
|
||||
}
|
||||
|
||||
match self {
|
||||
KeyOrIndexDisplay::None => {
|
||||
write!(f, "")
|
||||
|
@ -626,6 +721,30 @@ impl fmt::Display for IndexType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MySQLs index option.
|
||||
///
|
||||
/// This structure used here [`MySQL` CREATE TABLE][1], [`MySQL` CREATE INDEX][2].
|
||||
///
|
||||
/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
|
||||
/// [2]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum IndexOption {
|
||||
Using(IndexType),
|
||||
Comment(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for IndexOption {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Using(index_type) => write!(f, "USING {index_type}"),
|
||||
Self::Comment(s) => write!(f, "COMMENT '{s}'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
|
@ -909,6 +1028,7 @@ pub enum GeneratedExpressionMode {
|
|||
Stored,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
||||
struct ConstraintName<'a>(&'a Option<Ident>);
|
||||
impl<'a> fmt::Display for ConstraintName<'a> {
|
||||
|
@ -922,6 +1042,36 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
|||
ConstraintName(name)
|
||||
}
|
||||
|
||||
/// If `option` is
|
||||
/// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"`
|
||||
/// * `_` => do nothing
|
||||
#[must_use]
|
||||
fn display_option<'a, T: fmt::Display>(
|
||||
prefix: &'a str,
|
||||
postfix: &'a str,
|
||||
option: &'a Option<T>,
|
||||
) -> impl fmt::Display + 'a {
|
||||
struct OptionDisplay<'a, T>(&'a str, &'a str, &'a Option<T>);
|
||||
impl<'a, T: fmt::Display> fmt::Display for OptionDisplay<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(inner) = self.2 {
|
||||
let (prefix, postfix) = (self.0, self.1);
|
||||
write!(f, "{prefix}{inner}{postfix}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
OptionDisplay(prefix, postfix, option)
|
||||
}
|
||||
|
||||
/// If `option` is
|
||||
/// * `Some(inner)` => create display struct for `" {inner}"`
|
||||
/// * `_` => do nothing
|
||||
#[must_use]
|
||||
fn display_option_spaced<T: fmt::Display>(option: &Option<T>) -> impl fmt::Display + '_ {
|
||||
display_option(" ", "", option)
|
||||
}
|
||||
|
||||
/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
|
||||
///
|
||||
/// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order.
|
||||
|
|
|
@ -33,7 +33,7 @@ pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}
|
|||
pub use self::ddl::{
|
||||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
||||
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
|
||||
GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
|
||||
GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
|
||||
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
||||
UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
};
|
||||
|
|
|
@ -5149,23 +5149,49 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let next_token = self.next_token();
|
||||
match next_token.token {
|
||||
Token::Word(w) if w.keyword == Keyword::PRIMARY || w.keyword == Keyword::UNIQUE => {
|
||||
let is_primary = w.keyword == Keyword::PRIMARY;
|
||||
Token::Word(w) if w.keyword == Keyword::UNIQUE => {
|
||||
let index_type_display = self.parse_index_type_display();
|
||||
if !dialect_of!(self is GenericDialect | MySqlDialect)
|
||||
&& !index_type_display.is_none()
|
||||
{
|
||||
return self
|
||||
.expected("`index_name` or `(column_name [, ...])`", self.peek_token());
|
||||
}
|
||||
|
||||
// parse optional [KEY]
|
||||
let _ = self.parse_keyword(Keyword::KEY);
|
||||
|
||||
// optional constraint name
|
||||
let name = self
|
||||
.maybe_parse(|parser| parser.parse_identifier(false))
|
||||
.or(name);
|
||||
// optional index name
|
||||
let index_name = self.parse_optional_indent();
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
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,
|
||||
is_primary,
|
||||
index_options,
|
||||
characteristics,
|
||||
}))
|
||||
}
|
||||
Token::Word(w) if w.keyword == Keyword::PRIMARY => {
|
||||
// after `PRIMARY` always stay `KEY`
|
||||
self.expect_keyword(Keyword::KEY)?;
|
||||
|
||||
// optional index name
|
||||
let index_name = self.parse_optional_indent();
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
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,
|
||||
}))
|
||||
}
|
||||
|
@ -5209,20 +5235,17 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
Token::Word(w)
|
||||
if (w.keyword == Keyword::INDEX || w.keyword == Keyword::KEY)
|
||||
&& dialect_of!(self is GenericDialect | MySqlDialect) =>
|
||||
&& dialect_of!(self is GenericDialect | MySqlDialect)
|
||||
&& name.is_none() =>
|
||||
{
|
||||
let display_as_key = w.keyword == Keyword::KEY;
|
||||
|
||||
let name = match self.peek_token().token {
|
||||
Token::Word(word) if word.keyword == Keyword::USING => None,
|
||||
_ => self.maybe_parse(|parser| parser.parse_identifier(false)),
|
||||
_ => self.parse_optional_indent(),
|
||||
};
|
||||
|
||||
let index_type = if self.parse_keyword(Keyword::USING) {
|
||||
Some(self.parse_index_type()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
|
||||
Ok(Some(TableConstraint::Index {
|
||||
|
@ -5248,15 +5271,9 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let fulltext = w.keyword == Keyword::FULLTEXT;
|
||||
|
||||
let index_type_display = if self.parse_keyword(Keyword::KEY) {
|
||||
KeyOrIndexDisplay::Key
|
||||
} else if self.parse_keyword(Keyword::INDEX) {
|
||||
KeyOrIndexDisplay::Index
|
||||
} else {
|
||||
KeyOrIndexDisplay::None
|
||||
};
|
||||
let index_type_display = self.parse_index_type_display();
|
||||
|
||||
let opt_index_name = self.maybe_parse(|parser| parser.parse_identifier(false));
|
||||
let opt_index_name = self.parse_optional_indent();
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||
|
||||
|
@ -5313,6 +5330,56 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse [USING {BTREE | HASH}]
|
||||
pub fn parse_optional_using_then_index_type(
|
||||
&mut self,
|
||||
) -> Result<Option<IndexType>, ParserError> {
|
||||
if self.parse_keyword(Keyword::USING) {
|
||||
Ok(Some(self.parse_index_type()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `[ident]`, mostly `ident` is name, like:
|
||||
/// `window_name`, `index_name`, ...
|
||||
pub fn parse_optional_indent(&mut self) -> Option<Ident> {
|
||||
self.maybe_parse(|parser| parser.parse_identifier(false))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parse_index_type_display(&mut self) -> KeyOrIndexDisplay {
|
||||
if self.parse_keyword(Keyword::KEY) {
|
||||
KeyOrIndexDisplay::Key
|
||||
} else if self.parse_keyword(Keyword::INDEX) {
|
||||
KeyOrIndexDisplay::Index
|
||||
} else {
|
||||
KeyOrIndexDisplay::None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_optional_index_option(&mut self) -> Result<Option<IndexOption>, ParserError> {
|
||||
if let Some(index_type) = self.parse_optional_using_then_index_type()? {
|
||||
Ok(Some(IndexOption::Using(index_type)))
|
||||
} else if self.parse_keyword(Keyword::COMMENT) {
|
||||
let s = self.parse_literal_string()?;
|
||||
Ok(Some(IndexOption::Comment(s)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_index_options(&mut self) -> Result<Vec<IndexOption>, ParserError> {
|
||||
let mut options = Vec::new();
|
||||
|
||||
loop {
|
||||
match self.parse_optional_index_option()? {
|
||||
Some(index_option) => options.push(index_option),
|
||||
None => return Ok(options),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
|
||||
let name = self.parse_identifier(false)?;
|
||||
self.expect_token(&Token::Eq)?;
|
||||
|
@ -9537,9 +9604,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
|
||||
let window_name = match self.peek_token().token {
|
||||
Token::Word(word) if word.keyword == Keyword::NoKeyword => {
|
||||
self.maybe_parse(|parser| parser.parse_identifier(false))
|
||||
}
|
||||
Token::Word(word) if word.keyword == Keyword::NoKeyword => self.parse_optional_indent(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
|
|
@ -500,63 +500,186 @@ fn parse_create_table_auto_increment() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_unique_key() {
|
||||
let sql = "CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, UNIQUE KEY bar_key (bar))";
|
||||
let canonical = "CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key UNIQUE (bar))";
|
||||
match mysql().one_statement_parses_to(sql, canonical) {
|
||||
Statement::CreateTable {
|
||||
/// if `unique_index_type_display` is `Some` create `TableConstraint::Unique`
|
||||
/// otherwise create `TableConstraint::Primary`
|
||||
fn table_constraint_unique_primary_ctor(
|
||||
name: Option<Ident>,
|
||||
index_name: Option<Ident>,
|
||||
index_type: Option<IndexType>,
|
||||
columns: Vec<Ident>,
|
||||
index_options: Vec<IndexOption>,
|
||||
characteristics: Option<ConstraintCharacteristics>,
|
||||
unique_index_type_display: Option<KeyOrIndexDisplay>,
|
||||
) -> TableConstraint {
|
||||
match unique_index_type_display {
|
||||
Some(index_type_display) => TableConstraint::Unique {
|
||||
name,
|
||||
index_name,
|
||||
index_type_display,
|
||||
index_type,
|
||||
columns,
|
||||
constraints,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
assert_eq!(
|
||||
vec![TableConstraint::Unique {
|
||||
name: Some(Ident::new("bar_key")),
|
||||
columns: vec![Ident::new("bar")],
|
||||
is_primary: false,
|
||||
characteristics: None,
|
||||
}],
|
||||
constraints
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
ColumnDef {
|
||||
name: Ident::new("id"),
|
||||
data_type: DataType::Int(None),
|
||||
collation: None,
|
||||
options: vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
index_options,
|
||||
characteristics,
|
||||
},
|
||||
None => TableConstraint::PrimaryKey {
|
||||
name,
|
||||
index_name,
|
||||
index_type,
|
||||
columns,
|
||||
index_options,
|
||||
characteristics,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_primary_and_unique_key() {
|
||||
let sqls = ["UNIQUE KEY", "PRIMARY KEY"]
|
||||
.map(|key_ty|format!("CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key {key_ty} (bar))"));
|
||||
|
||||
let index_type_display = [Some(KeyOrIndexDisplay::Key), None];
|
||||
|
||||
for (sql, index_type_display) in sqls.iter().zip(index_type_display) {
|
||||
match mysql().one_statement_parses_to(sql, "") {
|
||||
Statement::CreateTable {
|
||||
name,
|
||||
columns,
|
||||
constraints,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
||||
let expected_constraint = table_constraint_unique_primary_ctor(
|
||||
Some(Ident::new("bar_key")),
|
||||
None,
|
||||
None,
|
||||
vec![Ident::new("bar")],
|
||||
vec![],
|
||||
None,
|
||||
index_type_display,
|
||||
);
|
||||
assert_eq!(vec![expected_constraint], constraints);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
ColumnDef {
|
||||
name: Ident::new("id"),
|
||||
data_type: DataType::Int(None),
|
||||
collation: None,
|
||||
options: vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Unique {
|
||||
is_primary: true,
|
||||
characteristics: None
|
||||
},
|
||||
},
|
||||
},
|
||||
ColumnOptionDef {
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::DialectSpecific(vec![
|
||||
Token::make_keyword("AUTO_INCREMENT")
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("bar"),
|
||||
data_type: DataType::Int(None),
|
||||
collation: None,
|
||||
options: vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::DialectSpecific(vec![Token::make_keyword(
|
||||
"AUTO_INCREMENT"
|
||||
)]),
|
||||
},
|
||||
],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("bar"),
|
||||
data_type: DataType::Int(None),
|
||||
collation: None,
|
||||
options: vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::NotNull,
|
||||
},],
|
||||
},
|
||||
],
|
||||
columns
|
||||
);
|
||||
option: ColumnOption::NotNull,
|
||||
},],
|
||||
},
|
||||
],
|
||||
columns
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_primary_and_unique_key_with_index_options() {
|
||||
let sqls = ["UNIQUE INDEX", "PRIMARY KEY"]
|
||||
.map(|key_ty|format!("CREATE TABLE foo (bar INT, var INT, CONSTRAINT constr {key_ty} index_name (bar, var) USING HASH COMMENT 'yes, ' USING BTREE COMMENT 'MySQL allows')"));
|
||||
|
||||
let index_type_display = [Some(KeyOrIndexDisplay::Index), None];
|
||||
|
||||
for (sql, index_type_display) in sqls.iter().zip(index_type_display) {
|
||||
match mysql_and_generic().one_statement_parses_to(sql, "") {
|
||||
Statement::CreateTable {
|
||||
name, constraints, ..
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
||||
let expected_constraint = table_constraint_unique_primary_ctor(
|
||||
Some(Ident::new("constr")),
|
||||
Some(Ident::new("index_name")),
|
||||
None,
|
||||
vec![Ident::new("bar"), Ident::new("var")],
|
||||
vec![
|
||||
IndexOption::Using(IndexType::Hash),
|
||||
IndexOption::Comment("yes, ".into()),
|
||||
IndexOption::Using(IndexType::BTree),
|
||||
IndexOption::Comment("MySQL allows".into()),
|
||||
],
|
||||
None,
|
||||
index_type_display,
|
||||
);
|
||||
assert_eq!(vec![expected_constraint], constraints);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
mysql_and_generic().verified_stmt(sql);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_primary_and_unique_key_with_index_type() {
|
||||
let sqls = ["UNIQUE", "PRIMARY KEY"].map(|key_ty| {
|
||||
format!("CREATE TABLE foo (bar INT, {key_ty} index_name USING BTREE (bar) USING HASH)")
|
||||
});
|
||||
|
||||
let index_type_display = [Some(KeyOrIndexDisplay::None), None];
|
||||
|
||||
for (sql, index_type_display) in sqls.iter().zip(index_type_display) {
|
||||
match mysql_and_generic().one_statement_parses_to(sql, "") {
|
||||
Statement::CreateTable {
|
||||
name, constraints, ..
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
|
||||
let expected_constraint = table_constraint_unique_primary_ctor(
|
||||
None,
|
||||
Some(Ident::new("index_name")),
|
||||
Some(IndexType::BTree),
|
||||
vec![Ident::new("bar")],
|
||||
vec![IndexOption::Using(IndexType::Hash)],
|
||||
None,
|
||||
index_type_display,
|
||||
);
|
||||
assert_eq!(vec![expected_constraint], constraints);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
mysql_and_generic().verified_stmt(sql);
|
||||
}
|
||||
|
||||
let sql = "CREATE TABLE foo (bar INT, UNIQUE INDEX index_name USING BTREE (bar) USING HASH)";
|
||||
mysql_and_generic().verified_stmt(sql);
|
||||
let sql = "CREATE TABLE foo (bar INT, PRIMARY KEY index_name USING BTREE (bar) USING HASH)";
|
||||
mysql_and_generic().verified_stmt(sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_primary_and_unique_key_characteristic_test() {
|
||||
let sqls = ["UNIQUE INDEX", "PRIMARY KEY"]
|
||||
.map(|key_ty|format!("CREATE TABLE x (y INT, CONSTRAINT constr {key_ty} (y) NOT DEFERRABLE INITIALLY IMMEDIATE)"));
|
||||
for sql in &sqls {
|
||||
mysql_and_generic().verified_stmt(sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2333,6 +2456,15 @@ fn parse_create_table_with_index_definition() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_unallow_constraint_then_index() {
|
||||
let sql = "CREATE TABLE foo (bar INT, CONSTRAINT constr INDEX index (bar))";
|
||||
assert!(mysql_and_generic().parse_sql_statements(sql).is_err());
|
||||
|
||||
let sql = "CREATE TABLE foo (bar INT, INDEX index (bar))";
|
||||
assert!(mysql_and_generic().parse_sql_statements(sql).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_with_fulltext_definition() {
|
||||
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT (id))");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue