mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-27 17:34:05 +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"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{boxed::Box, string::String, vec::Vec};
|
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||||
use core::fmt;
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -397,12 +397,68 @@ impl fmt::Display for AlterColumnOperation {
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub enum TableConstraint {
|
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 {
|
Unique {
|
||||||
|
/// Constraint name.
|
||||||
|
///
|
||||||
|
/// Can be not the same as `index_name`
|
||||||
name: Option<Ident>,
|
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>,
|
columns: Vec<Ident>,
|
||||||
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
|
index_options: Vec<IndexOption>,
|
||||||
is_primary: bool,
|
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>,
|
characteristics: Option<ConstraintCharacteristics>,
|
||||||
},
|
},
|
||||||
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
|
||||||
|
@ -472,22 +528,51 @@ impl fmt::Display for TableConstraint {
|
||||||
match self {
|
match self {
|
||||||
TableConstraint::Unique {
|
TableConstraint::Unique {
|
||||||
name,
|
name,
|
||||||
|
index_name,
|
||||||
|
index_type_display,
|
||||||
|
index_type,
|
||||||
columns,
|
columns,
|
||||||
is_primary,
|
index_options,
|
||||||
characteristics,
|
characteristics,
|
||||||
} => {
|
} => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}{} ({})",
|
"{}UNIQUE{index_type_display:>}{}{} ({})",
|
||||||
display_constraint_name(name),
|
display_constraint_name(name),
|
||||||
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
|
display_option_spaced(index_name),
|
||||||
display_comma_separated(columns)
|
display_option(" USING ", "", index_type),
|
||||||
|
display_comma_separated(columns),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(characteristics) = characteristics {
|
if !index_options.is_empty() {
|
||||||
write!(f, " {}", characteristics)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
TableConstraint::ForeignKey {
|
TableConstraint::ForeignKey {
|
||||||
|
@ -550,9 +635,7 @@ impl fmt::Display for TableConstraint {
|
||||||
write!(f, "SPATIAL")?;
|
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 {
|
if let Some(name) = opt_index_name {
|
||||||
write!(f, " {name}")?;
|
write!(f, " {name}")?;
|
||||||
|
@ -585,8 +668,20 @@ pub enum KeyOrIndexDisplay {
|
||||||
Index,
|
Index,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KeyOrIndexDisplay {
|
||||||
|
pub fn is_none(self) -> bool {
|
||||||
|
matches!(self, Self::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for KeyOrIndexDisplay {
|
impl fmt::Display for KeyOrIndexDisplay {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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 {
|
match self {
|
||||||
KeyOrIndexDisplay::None => {
|
KeyOrIndexDisplay::None => {
|
||||||
write!(f, "")
|
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)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
@ -909,6 +1028,7 @@ pub enum GeneratedExpressionMode {
|
||||||
Stored,
|
Stored,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
||||||
struct ConstraintName<'a>(&'a Option<Ident>);
|
struct ConstraintName<'a>(&'a Option<Ident>);
|
||||||
impl<'a> fmt::Display for ConstraintName<'a> {
|
impl<'a> fmt::Display for ConstraintName<'a> {
|
||||||
|
@ -922,6 +1042,36 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
||||||
ConstraintName(name)
|
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 ]`
|
/// `<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.
|
/// 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::{
|
pub use self::ddl::{
|
||||||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
||||||
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
|
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
|
||||||
GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
|
GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
|
||||||
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
||||||
UserDefinedTypeRepresentation, ViewColumnDef,
|
UserDefinedTypeRepresentation, ViewColumnDef,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5149,23 +5149,49 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
let next_token = self.next_token();
|
let next_token = self.next_token();
|
||||||
match next_token.token {
|
match next_token.token {
|
||||||
Token::Word(w) if w.keyword == Keyword::PRIMARY || w.keyword == Keyword::UNIQUE => {
|
Token::Word(w) if w.keyword == Keyword::UNIQUE => {
|
||||||
let is_primary = w.keyword == Keyword::PRIMARY;
|
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]
|
// optional index name
|
||||||
let _ = self.parse_keyword(Keyword::KEY);
|
let index_name = self.parse_optional_indent();
|
||||||
|
let index_type = self.parse_optional_using_then_index_type()?;
|
||||||
// optional constraint name
|
|
||||||
let name = self
|
|
||||||
.maybe_parse(|parser| parser.parse_identifier(false))
|
|
||||||
.or(name);
|
|
||||||
|
|
||||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||||
|
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(TableConstraint::Unique {
|
||||||
name,
|
name,
|
||||||
|
index_name,
|
||||||
|
index_type_display,
|
||||||
|
index_type,
|
||||||
columns,
|
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,
|
characteristics,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -5209,20 +5235,17 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
Token::Word(w)
|
Token::Word(w)
|
||||||
if (w.keyword == Keyword::INDEX || w.keyword == Keyword::KEY)
|
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 display_as_key = w.keyword == Keyword::KEY;
|
||||||
|
|
||||||
let name = match self.peek_token().token {
|
let name = match self.peek_token().token {
|
||||||
Token::Word(word) if word.keyword == Keyword::USING => None,
|
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) {
|
let index_type = self.parse_optional_using_then_index_type()?;
|
||||||
Some(self.parse_index_type()?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
|
||||||
|
|
||||||
Ok(Some(TableConstraint::Index {
|
Ok(Some(TableConstraint::Index {
|
||||||
|
@ -5248,15 +5271,9 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
let fulltext = w.keyword == Keyword::FULLTEXT;
|
let fulltext = w.keyword == Keyword::FULLTEXT;
|
||||||
|
|
||||||
let index_type_display = if self.parse_keyword(Keyword::KEY) {
|
let index_type_display = self.parse_index_type_display();
|
||||||
KeyOrIndexDisplay::Key
|
|
||||||
} else if self.parse_keyword(Keyword::INDEX) {
|
|
||||||
KeyOrIndexDisplay::Index
|
|
||||||
} else {
|
|
||||||
KeyOrIndexDisplay::None
|
|
||||||
};
|
|
||||||
|
|
||||||
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)?;
|
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> {
|
pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
|
||||||
let name = self.parse_identifier(false)?;
|
let name = self.parse_identifier(false)?;
|
||||||
self.expect_token(&Token::Eq)?;
|
self.expect_token(&Token::Eq)?;
|
||||||
|
@ -9537,9 +9604,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
|
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
|
||||||
let window_name = match self.peek_token().token {
|
let window_name = match self.peek_token().token {
|
||||||
Token::Word(word) if word.keyword == Keyword::NoKeyword => {
|
Token::Word(word) if word.keyword == Keyword::NoKeyword => self.parse_optional_indent(),
|
||||||
self.maybe_parse(|parser| parser.parse_identifier(false))
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -500,63 +500,186 @@ fn parse_create_table_auto_increment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
/// if `unique_index_type_display` is `Some` create `TableConstraint::Unique`
|
||||||
fn parse_create_table_unique_key() {
|
/// otherwise create `TableConstraint::Primary`
|
||||||
let sql = "CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, UNIQUE KEY bar_key (bar))";
|
fn table_constraint_unique_primary_ctor(
|
||||||
let canonical = "CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key UNIQUE (bar))";
|
name: Option<Ident>,
|
||||||
match mysql().one_statement_parses_to(sql, canonical) {
|
index_name: Option<Ident>,
|
||||||
Statement::CreateTable {
|
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,
|
name,
|
||||||
|
index_name,
|
||||||
|
index_type_display,
|
||||||
|
index_type,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
index_options,
|
||||||
..
|
characteristics,
|
||||||
} => {
|
},
|
||||||
assert_eq!(name.to_string(), "foo");
|
None => TableConstraint::PrimaryKey {
|
||||||
assert_eq!(
|
name,
|
||||||
vec![TableConstraint::Unique {
|
index_name,
|
||||||
name: Some(Ident::new("bar_key")),
|
index_type,
|
||||||
columns: vec![Ident::new("bar")],
|
columns,
|
||||||
is_primary: false,
|
index_options,
|
||||||
characteristics: None,
|
characteristics,
|
||||||
}],
|
},
|
||||||
constraints
|
}
|
||||||
);
|
}
|
||||||
assert_eq!(
|
|
||||||
vec![
|
#[test]
|
||||||
ColumnDef {
|
fn parse_create_table_primary_and_unique_key() {
|
||||||
name: Ident::new("id"),
|
let sqls = ["UNIQUE KEY", "PRIMARY KEY"]
|
||||||
data_type: DataType::Int(None),
|
.map(|key_ty|format!("CREATE TABLE foo (id INT PRIMARY KEY AUTO_INCREMENT, bar INT NOT NULL, CONSTRAINT bar_key {key_ty} (bar))"));
|
||||||
collation: None,
|
|
||||||
options: vec![
|
let index_type_display = [Some(KeyOrIndexDisplay::Key), None];
|
||||||
ColumnOptionDef {
|
|
||||||
name: None,
|
for (sql, index_type_display) in sqls.iter().zip(index_type_display) {
|
||||||
option: ColumnOption::Unique {
|
match mysql().one_statement_parses_to(sql, "") {
|
||||||
is_primary: true,
|
Statement::CreateTable {
|
||||||
characteristics: None
|
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,
|
name: None,
|
||||||
option: ColumnOption::DialectSpecific(vec![Token::make_keyword(
|
option: ColumnOption::NotNull,
|
||||||
"AUTO_INCREMENT"
|
},],
|
||||||
)]),
|
},
|
||||||
},
|
],
|
||||||
],
|
columns
|
||||||
},
|
);
|
||||||
ColumnDef {
|
}
|
||||||
name: Ident::new("bar"),
|
_ => unreachable!(),
|
||||||
data_type: DataType::Int(None),
|
|
||||||
collation: None,
|
|
||||||
options: vec![ColumnOptionDef {
|
|
||||||
name: None,
|
|
||||||
option: ColumnOption::NotNull,
|
|
||||||
},],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
columns
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => 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]
|
#[test]
|
||||||
fn parse_create_table_with_fulltext_definition() {
|
fn parse_create_table_with_fulltext_definition() {
|
||||||
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT (id))");
|
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT (id))");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue