From 0dd08919915c1936d1f2e2cde584905e49140b7c Mon Sep 17 00:00:00 2001 From: "Alex.Mo" Date: Thu, 20 Feb 2025 11:40:09 +0800 Subject: [PATCH 1/5] Add support column prefix index for MySQL --- src/ast/ddl.rs | 40 +++++++------ src/ast/mod.rs | 55 +++++++++++++++++ src/ast/spans.rs | 23 ++----- src/parser/mod.rs | 125 ++++++++++++++++++++++++++++++++++----- tests/sqlparser_mysql.rs | 80 +++++++++++++++++++++++-- 5 files changed, 267 insertions(+), 56 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1fbc45603..cdf6ea9ab 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -32,8 +32,9 @@ use crate::ast::value::escape_single_quote_string; use crate::ast::{ display_comma_separated, display_separated, CommentDef, CreateFunctionBody, CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull, - FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName, - OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value, + FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexField, MySQLColumnPosition, + ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, + Value, }; use crate::keywords::Keyword; use crate::tokenizer::Token; @@ -832,8 +833,8 @@ pub enum TableConstraint { /// /// [1]: IndexType index_type: Option, - /// Identifiers of the columns that are unique. - columns: Vec, + /// Index field list. + index_fields: Vec, index_options: Vec, characteristics: Option, /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` @@ -868,8 +869,8 @@ pub enum TableConstraint { /// /// [1]: IndexType index_type: Option, - /// Identifiers of the columns that form the primary key. - columns: Vec, + /// Index field list that form the primary key. + index_fields: Vec, index_options: Vec, characteristics: Option, }, @@ -907,8 +908,10 @@ pub enum TableConstraint { /// /// [1]: IndexType index_type: Option, - /// Referred column identifier list. - columns: Vec, + /// [Index field list][1]. + /// + /// [1]: IndexField + index_fields: Vec, }, /// 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. @@ -930,8 +933,8 @@ pub enum TableConstraint { index_type_display: KeyOrIndexDisplay, /// Optional index name. opt_index_name: Option, - /// Referred column identifier list. - columns: Vec, + /// Index field list. + index_fields: Vec, }, } @@ -943,7 +946,7 @@ impl fmt::Display for TableConstraint { index_name, index_type_display, index_type, - columns, + index_fields, index_options, characteristics, nulls_distinct, @@ -954,7 +957,7 @@ impl fmt::Display for TableConstraint { display_constraint_name(name), display_option_spaced(index_name), display_option(" USING ", "", index_type), - display_comma_separated(columns), + display_comma_separated(index_fields), )?; if !index_options.is_empty() { @@ -968,7 +971,7 @@ impl fmt::Display for TableConstraint { name, index_name, index_type, - columns, + index_fields, index_options, characteristics, } => { @@ -978,7 +981,7 @@ impl fmt::Display for TableConstraint { display_constraint_name(name), display_option_spaced(index_name), display_option(" USING ", "", index_type), - display_comma_separated(columns), + display_comma_separated(index_fields), )?; if !index_options.is_empty() { @@ -1025,7 +1028,7 @@ impl fmt::Display for TableConstraint { display_as_key, name, index_type, - columns, + index_fields, } => { write!(f, "{}", if *display_as_key { "KEY" } else { "INDEX" })?; if let Some(name) = name { @@ -1034,7 +1037,8 @@ impl fmt::Display for TableConstraint { if let Some(index_type) = index_type { write!(f, " USING {index_type}")?; } - write!(f, " ({})", display_comma_separated(columns))?; + + write!(f, " ({})", display_comma_separated(index_fields))?; Ok(()) } @@ -1042,7 +1046,7 @@ impl fmt::Display for TableConstraint { fulltext, index_type_display, opt_index_name, - columns, + index_fields, } => { if *fulltext { write!(f, "FULLTEXT")?; @@ -1056,7 +1060,7 @@ impl fmt::Display for TableConstraint { write!(f, " {name}")?; } - write!(f, " ({})", display_comma_separated(columns))?; + write!(f, " ({})", display_comma_separated(index_fields))?; Ok(()) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index aad122fbf..184031b9b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8690,6 +8690,61 @@ pub enum CopyIntoSnowflakeKind { Location, } +/// Index Field +/// +/// This structure used here [`MySQL` CREATE INDEX][1], [`PostgreSQL` CREATE INDEX][2], [`MySQL` CREATE TABLE][3]. +/// +/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html +/// [2]: https://www.postgresql.org/docs/17/sql-createindex.html +/// [3]: https://dev.mysql.com/doc/refman/8.3/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 IndexField { + pub expr: IndexExpr, + /// Optional `ASC` or `DESC` + pub asc: Option, +} + +impl fmt::Display for IndexField { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.expr)?; + match self.asc { + Some(true) => write!(f, " ASC")?, + Some(false) => write!(f, " DESC")?, + None => (), + } + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum IndexExpr { + Column(Ident), + /// Mysql specific syntax + /// + /// See [Mysql documentation](https://dev.mysql.com/doc/refman/8.3/en/create-index.html) + /// for more details. + ColumnPrefix { + column: Ident, + length: u64, + }, + Functional(Box), +} + +impl fmt::Display for IndexExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + IndexExpr::Column(column) => write!(f, "{}", column), + IndexExpr::ColumnPrefix { column, length } => write!(f, "{column}({length})"), + IndexExpr::Functional(expr) => write!(f, "({expr})"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index a036271cb..36092b188 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -628,7 +628,7 @@ impl Spanned for TableConstraint { index_name, index_type_display: _, index_type: _, - columns, + index_fields: _, index_options: _, characteristics, nulls_distinct: _, @@ -636,21 +636,19 @@ impl Spanned for TableConstraint { 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_fields: _, 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 { @@ -678,23 +676,14 @@ impl Spanned for TableConstraint { display_as_key: _, name, index_type: _, - columns, - } => union_spans( - name.iter() - .map(|i| i.span) - .chain(columns.iter().map(|i| i.span)), - ), + index_fields: _, + } => union_spans(name.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)), - ), + index_fields: _, + } => union_spans(opt_index_name.iter().map(|i| i.span)), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c08c7049d..81b58dd99 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7440,7 +7440,7 @@ impl<'a> Parser<'a> { 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_fields = self.parse_index_fields()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::Unique { @@ -7448,7 +7448,7 @@ impl<'a> Parser<'a> { index_name, index_type_display, index_type, - columns, + index_fields, index_options, characteristics, nulls_distinct, @@ -7462,14 +7462,14 @@ impl<'a> Parser<'a> { 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_fields = self.parse_index_fields()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::PrimaryKey { name, index_name, index_type, - columns, + index_fields, index_options, characteristics, })) @@ -7525,13 +7525,13 @@ impl<'a> Parser<'a> { }; let index_type = self.parse_optional_using_then_index_type()?; - let columns = self.parse_parenthesized_column_list(Mandatory, false)?; + let index_fields = self.parse_index_fields()?; Ok(Some(TableConstraint::Index { display_as_key, name, index_type, - columns, + index_fields, })) } Token::Word(w) @@ -7554,13 +7554,13 @@ impl<'a> Parser<'a> { let opt_index_name = self.parse_optional_indent()?; - let columns = self.parse_parenthesized_column_list(Mandatory, false)?; + let index_fields = self.parse_index_fields()?; Ok(Some(TableConstraint::FulltextOrSpatial { fulltext, index_type_display, opt_index_name, - columns, + index_fields, })) } _ => { @@ -7646,6 +7646,34 @@ impl<'a> Parser<'a> { } } + pub fn parse_index_fields(&mut self) -> Result, ParserError> { + self.parse_parenthesized(|p| p.parse_comma_separated(Parser::parse_index_field)) + } + + pub fn parse_index_field(&mut self) -> Result { + let expr = self.parse_index_expr()?; + let asc = self.parse_asc_desc(); + + Ok(IndexField { expr, asc }) + } + + pub fn parse_index_expr(&mut self) -> Result { + if self.peek_token() == Token::LParen { + let expr = self.parse_parenthesized(|p| p.parse_expr())?; + return Ok(IndexExpr::Functional(Box::new(expr))); + } + + let column = self.parse_identifier()?; + + if dialect_of!(self is MySqlDialect | GenericDialect) && self.peek_token() == Token::LParen + { + let length = self.parse_parenthesized(Parser::parse_literal_uint)?; + return Ok(IndexExpr::ColumnPrefix { column, length }); + } + + Ok(IndexExpr::Column(column)) + } + /// Parse `[ident]`, mostly `ident` is name, like: /// `window_name`, `index_name`, ... pub fn parse_optional_indent(&mut self) -> Result, ParserError> { @@ -14688,6 +14716,8 @@ impl Word { #[cfg(test)] mod tests { + use std::vec; + use crate::test_utils::{all_dialects, TestedDialects}; use super::*; @@ -15138,7 +15168,10 @@ mod tests { display_as_key: false, name: None, index_type: None, - columns: vec![Ident::new("c1")], + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }], } ); @@ -15149,7 +15182,10 @@ mod tests { display_as_key: true, name: None, index_type: None, - columns: vec![Ident::new("c1")], + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }], } ); @@ -15160,7 +15196,54 @@ mod tests { display_as_key: false, name: Some(Ident::with_quote('\'', "index")), index_type: None, - columns: vec![Ident::new("c1"), Ident::new("c2")], + index_fields: vec![ + IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }, + IndexField { + expr: IndexExpr::Column(Ident::new("c2")), + asc: None, + } + ], + } + ); + + test_parse_table_constraint!( + dialect, + "KEY (c1(10), (LOWER(c2)) DESC)", + TableConstraint::Index { + display_as_key: true, + name: None, + index_type: None, + index_fields: vec![ + IndexField { + expr: IndexExpr::ColumnPrefix { + column: Ident::new("c1"), + length: 10, + }, + asc: None, + }, + IndexField { + expr: IndexExpr::Functional(Box::new(Expr::Function(Function { + name: ObjectName::from(vec![Ident::new("LOWER")]), + uses_odbc_syntax: false, + parameters: FunctionArguments::None, + args: FunctionArguments::List(FunctionArgumentList { + duplicate_treatment: None, + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( + Expr::Identifier(Ident::new("c2")) + )),], + clauses: vec![], + }), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + }))), + asc: Some(false), + } + ], } ); @@ -15171,7 +15254,10 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::BTree), - columns: vec![Ident::new("c1")], + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }], } ); @@ -15182,7 +15268,10 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::Hash), - columns: vec![Ident::new("c1")], + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }], } ); @@ -15193,7 +15282,10 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::BTree), - columns: vec![Ident::new("c1")], + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }], } ); @@ -15204,7 +15296,10 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::Hash), - columns: vec![Ident::new("c1")], + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("c1")), + asc: None, + }], } ); } diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 030710743..d4160c61d 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -665,7 +665,7 @@ fn table_constraint_unique_primary_ctor( name: Option, index_name: Option, index_type: Option, - columns: Vec, + index_fields: Vec, index_options: Vec, characteristics: Option, unique_index_type_display: Option, @@ -676,7 +676,7 @@ fn table_constraint_unique_primary_ctor( index_name, index_type_display, index_type, - columns, + index_fields, index_options, characteristics, nulls_distinct: NullsDistinctOption::None, @@ -685,7 +685,7 @@ fn table_constraint_unique_primary_ctor( name, index_name, index_type, - columns, + index_fields, index_options, characteristics, }, @@ -713,7 +713,10 @@ fn parse_create_table_primary_and_unique_key() { Some(Ident::new("bar_key")), None, None, - vec![Ident::new("bar")], + vec![IndexField { + expr: IndexExpr::Column(Ident::new("bar")), + asc: None, + }], vec![], None, index_type_display, @@ -776,7 +779,16 @@ fn parse_create_table_primary_and_unique_key_with_index_options() { Some(Ident::new("constr")), Some(Ident::new("index_name")), None, - vec![Ident::new("bar"), Ident::new("var")], + vec![ + IndexField { + expr: IndexExpr::Column(Ident::new("bar")), + asc: None, + }, + IndexField { + expr: IndexExpr::Column(Ident::new("var")), + asc: None, + }, + ], vec![ IndexOption::Using(IndexType::Hash), IndexOption::Comment("yes, ".into()), @@ -814,7 +826,10 @@ fn parse_create_table_primary_and_unique_key_with_index_type() { None, Some(Ident::new("index_name")), Some(IndexType::BTree), - vec![Ident::new("bar")], + vec![IndexField { + expr: IndexExpr::Column(Ident::new("bar")), + asc: None, + }], vec![IndexOption::Using(IndexType::Hash)], None, index_type_display, @@ -2235,6 +2250,59 @@ fn parse_alter_table_add_columns() { } } +#[test] +fn parse_alter_table_add_keys() { + match mysql().verified_stmt("ALTER TABLE tab ADD PRIMARY KEY (a), ADD KEY b (b(20), c)") { + Statement::AlterTable { + name, + if_exists, + only, + operations, + location: _, + on_cluster: _, + } => { + assert_eq!(name.to_string(), "tab"); + assert!(!if_exists); + assert!(!only); + assert_eq!( + operations, + vec![ + AlterTableOperation::AddConstraint(TableConstraint::PrimaryKey { + name: None, + index_name: None, + index_fields: vec![IndexField { + expr: IndexExpr::Column(Ident::new("a")), + asc: None + }], + index_type: None, + index_options: vec![], + characteristics: None, + }), + AlterTableOperation::AddConstraint(TableConstraint::Index { + display_as_key: true, + name: Some(Ident::new("b")), + index_type: None, + index_fields: vec![ + IndexField { + expr: IndexExpr::ColumnPrefix { + column: Ident::new("b"), + length: 20, + }, + asc: None, + }, + IndexField { + expr: IndexExpr::Column(Ident::new("c")), + asc: None, + } + ] + }) + ] + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_alter_table_drop_primary_key() { assert_matches!( From 8ee76287281302c37ec5b379441a9140da41689d Mon Sep 17 00:00:00 2001 From: "Alex.Mo" Date: Mon, 24 Feb 2025 11:21:11 +0800 Subject: [PATCH 2/5] use OrderByExpr instead of IndexField --- src/ast/ddl.rs | 39 ++++---- src/ast/mod.rs | 66 ++---------- src/ast/spans.rs | 24 +++-- src/parser/mod.rs | 210 ++++++++++++++++++++++++--------------- tests/sqlparser_mysql.rs | 82 ++++++++++----- 5 files changed, 233 insertions(+), 188 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index cdf6ea9ab..62fbe400e 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -32,9 +32,8 @@ use crate::ast::value::escape_single_quote_string; use crate::ast::{ display_comma_separated, display_separated, CommentDef, CreateFunctionBody, CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull, - FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexField, MySQLColumnPosition, - ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, - Value, + FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName, + OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value, }; use crate::keywords::Keyword; use crate::tokenizer::Token; @@ -833,8 +832,8 @@ pub enum TableConstraint { /// /// [1]: IndexType index_type: Option, - /// Index field list. - index_fields: Vec, + /// Index expr list. + index_exprs: Vec, index_options: Vec, characteristics: Option, /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` @@ -869,8 +868,8 @@ pub enum TableConstraint { /// /// [1]: IndexType index_type: Option, - /// Index field list that form the primary key. - index_fields: Vec, + /// Index expr list. + index_exprs: Vec, index_options: Vec, characteristics: Option, }, @@ -908,10 +907,8 @@ pub enum TableConstraint { /// /// [1]: IndexType index_type: Option, - /// [Index field list][1]. - /// - /// [1]: IndexField - index_fields: Vec, + /// Index expr list. + index_exprs: Vec, }, /// 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. @@ -933,8 +930,8 @@ pub enum TableConstraint { index_type_display: KeyOrIndexDisplay, /// Optional index name. opt_index_name: Option, - /// Index field list. - index_fields: Vec, + /// Index expr list. + index_exprs: Vec, }, } @@ -946,7 +943,7 @@ impl fmt::Display for TableConstraint { index_name, index_type_display, index_type, - index_fields, + index_exprs, index_options, characteristics, nulls_distinct, @@ -957,7 +954,7 @@ impl fmt::Display for TableConstraint { display_constraint_name(name), display_option_spaced(index_name), display_option(" USING ", "", index_type), - display_comma_separated(index_fields), + display_comma_separated(index_exprs), )?; if !index_options.is_empty() { @@ -971,7 +968,7 @@ impl fmt::Display for TableConstraint { name, index_name, index_type, - index_fields, + index_exprs, index_options, characteristics, } => { @@ -981,7 +978,7 @@ impl fmt::Display for TableConstraint { display_constraint_name(name), display_option_spaced(index_name), display_option(" USING ", "", index_type), - display_comma_separated(index_fields), + display_comma_separated(index_exprs), )?; if !index_options.is_empty() { @@ -1028,7 +1025,7 @@ impl fmt::Display for TableConstraint { display_as_key, name, index_type, - index_fields, + index_exprs, } => { write!(f, "{}", if *display_as_key { "KEY" } else { "INDEX" })?; if let Some(name) = name { @@ -1038,7 +1035,7 @@ impl fmt::Display for TableConstraint { write!(f, " USING {index_type}")?; } - write!(f, " ({})", display_comma_separated(index_fields))?; + write!(f, " ({})", display_comma_separated(index_exprs))?; Ok(()) } @@ -1046,7 +1043,7 @@ impl fmt::Display for TableConstraint { fulltext, index_type_display, opt_index_name, - index_fields, + index_exprs, } => { if *fulltext { write!(f, "FULLTEXT")?; @@ -1060,7 +1057,7 @@ impl fmt::Display for TableConstraint { write!(f, " {name}")?; } - write!(f, " ({})", display_comma_separated(index_fields))?; + write!(f, " ({})", display_comma_separated(index_exprs))?; Ok(()) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 184031b9b..b65ad17bd 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1049,6 +1049,16 @@ pub enum Expr { /// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html) /// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html) Lambda(LambdaFunction), + /// A ColumnPrefix used in MySQL indexes. + /// ```sql + /// CREATE INDEX ON tbl (col(10)); + /// ``` + /// + /// [MySQL](https://dev.mysql.com/doc/refman/8.0/en/create-index.html) + ColumnPrefix { + column: Ident, + length: u64, + }, } /// The contents inside the `[` and `]` in a subscript expression. @@ -1817,6 +1827,7 @@ impl fmt::Display for Expr { } Expr::Prior(expr) => write!(f, "PRIOR {expr}"), Expr::Lambda(lambda) => write!(f, "{lambda}"), + Expr::ColumnPrefix { column, length } => write!(f, "{column}({length})"), } } } @@ -8690,61 +8701,6 @@ pub enum CopyIntoSnowflakeKind { Location, } -/// Index Field -/// -/// This structure used here [`MySQL` CREATE INDEX][1], [`PostgreSQL` CREATE INDEX][2], [`MySQL` CREATE TABLE][3]. -/// -/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html -/// [2]: https://www.postgresql.org/docs/17/sql-createindex.html -/// [3]: https://dev.mysql.com/doc/refman/8.3/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 IndexField { - pub expr: IndexExpr, - /// Optional `ASC` or `DESC` - pub asc: Option, -} - -impl fmt::Display for IndexField { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.expr)?; - match self.asc { - Some(true) => write!(f, " ASC")?, - Some(false) => write!(f, " DESC")?, - None => (), - } - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub enum IndexExpr { - Column(Ident), - /// Mysql specific syntax - /// - /// See [Mysql documentation](https://dev.mysql.com/doc/refman/8.3/en/create-index.html) - /// for more details. - ColumnPrefix { - column: Ident, - length: u64, - }, - Functional(Box), -} - -impl fmt::Display for IndexExpr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - IndexExpr::Column(column) => write!(f, "{}", column), - IndexExpr::ColumnPrefix { column, length } => write!(f, "{column}({length})"), - IndexExpr::Functional(expr) => write!(f, "({expr})"), - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 36092b188..2c850bfae 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -628,7 +628,7 @@ impl Spanned for TableConstraint { index_name, index_type_display: _, index_type: _, - index_fields: _, + index_exprs, index_options: _, characteristics, nulls_distinct: _, @@ -636,19 +636,21 @@ impl Spanned for TableConstraint { name.iter() .map(|i| i.span) .chain(index_name.iter().map(|i| i.span)) + .chain(index_exprs.iter().map(|i| i.span())) .chain(characteristics.iter().map(|i| i.span())), ), TableConstraint::PrimaryKey { name, index_name, index_type: _, - index_fields: _, + index_exprs, index_options: _, characteristics, } => union_spans( name.iter() .map(|i| i.span) .chain(index_name.iter().map(|i| i.span)) + .chain(index_exprs.iter().map(|i| i.span())) .chain(characteristics.iter().map(|i| i.span())), ), TableConstraint::ForeignKey { @@ -676,14 +678,23 @@ impl Spanned for TableConstraint { display_as_key: _, name, index_type: _, - index_fields: _, - } => union_spans(name.iter().map(|i| i.span)), + index_exprs, + } => union_spans( + name.iter() + .map(|i| i.span) + .chain(index_exprs.iter().map(|i| i.span())), + ), TableConstraint::FulltextOrSpatial { fulltext: _, index_type_display: _, opt_index_name, - index_fields: _, - } => union_spans(opt_index_name.iter().map(|i| i.span)), + index_exprs, + } => union_spans( + opt_index_name + .iter() + .map(|i| i.span) + .chain(index_exprs.iter().map(|i| i.span())), + ), } } } @@ -1468,6 +1479,7 @@ impl Spanned for Expr { Expr::OuterJoin(expr) => expr.span(), Expr::Prior(expr) => expr.span(), Expr::Lambda(_) => Span::empty(), + Expr::ColumnPrefix { .. } => Span::empty(), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 81b58dd99..951976504 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7440,7 +7440,7 @@ impl<'a> Parser<'a> { let index_name = self.parse_optional_indent()?; let index_type = self.parse_optional_using_then_index_type()?; - let index_fields = self.parse_index_fields()?; + let index_fields = self.parse_index_exprs()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::Unique { @@ -7448,7 +7448,7 @@ impl<'a> Parser<'a> { index_name, index_type_display, index_type, - index_fields, + index_exprs: index_fields, index_options, characteristics, nulls_distinct, @@ -7462,14 +7462,14 @@ impl<'a> Parser<'a> { let index_name = self.parse_optional_indent()?; let index_type = self.parse_optional_using_then_index_type()?; - let index_fields = self.parse_index_fields()?; + let index_exprs = self.parse_index_exprs()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::PrimaryKey { name, index_name, index_type, - index_fields, + index_exprs, index_options, characteristics, })) @@ -7525,13 +7525,13 @@ impl<'a> Parser<'a> { }; let index_type = self.parse_optional_using_then_index_type()?; - let index_fields = self.parse_index_fields()?; + let index_fields = self.parse_index_exprs()?; Ok(Some(TableConstraint::Index { display_as_key, name, index_type, - index_fields, + index_exprs: index_fields, })) } Token::Word(w) @@ -7554,13 +7554,13 @@ impl<'a> Parser<'a> { let opt_index_name = self.parse_optional_indent()?; - let index_fields = self.parse_index_fields()?; + let index_exprs = self.parse_index_exprs()?; Ok(Some(TableConstraint::FulltextOrSpatial { fulltext, index_type_display, opt_index_name, - index_fields, + index_exprs, })) } _ => { @@ -7646,21 +7646,33 @@ impl<'a> Parser<'a> { } } - pub fn parse_index_fields(&mut self) -> Result, ParserError> { + pub fn parse_index_exprs(&mut self) -> Result, ParserError> { self.parse_parenthesized(|p| p.parse_comma_separated(Parser::parse_index_field)) } - pub fn parse_index_field(&mut self) -> Result { + pub fn parse_index_field(&mut self) -> Result { let expr = self.parse_index_expr()?; - let asc = self.parse_asc_desc(); + let options = self.parse_order_by_options()?; + + let with_fill = if dialect_of!(self is ClickHouseDialect | GenericDialect) + && self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) + { + Some(self.parse_with_fill()?) + } else { + None + }; - Ok(IndexField { expr, asc }) + Ok(OrderByExpr { + expr, + options, + with_fill, + }) } - pub fn parse_index_expr(&mut self) -> Result { + pub fn parse_index_expr(&mut self) -> Result { if self.peek_token() == Token::LParen { - let expr = self.parse_parenthesized(|p| p.parse_expr())?; - return Ok(IndexExpr::Functional(Box::new(expr))); + let expr = self.parse_expr()?; + return Ok(expr); } let column = self.parse_identifier()?; @@ -7668,10 +7680,10 @@ impl<'a> Parser<'a> { if dialect_of!(self is MySqlDialect | GenericDialect) && self.peek_token() == Token::LParen { let length = self.parse_parenthesized(Parser::parse_literal_uint)?; - return Ok(IndexExpr::ColumnPrefix { column, length }); + return Ok(Expr::ColumnPrefix { column, length }); } - Ok(IndexExpr::Column(column)) + Ok(Expr::Identifier(column)) } /// Parse `[ident]`, mostly `ident` is name, like: @@ -15168,9 +15180,13 @@ mod tests { display_as_key: false, name: None, index_type: None, - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], } ); @@ -15182,9 +15198,13 @@ mod tests { display_as_key: true, name: None, index_type: None, - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], } ); @@ -15196,52 +15216,22 @@ mod tests { display_as_key: false, name: Some(Ident::with_quote('\'', "index")), index_type: None, - index_fields: vec![ - IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, - }, - IndexField { - expr: IndexExpr::Column(Ident::new("c2")), - asc: None, - } - ], - } - ); - - test_parse_table_constraint!( - dialect, - "KEY (c1(10), (LOWER(c2)) DESC)", - TableConstraint::Index { - display_as_key: true, - name: None, - index_type: None, - index_fields: vec![ - IndexField { - expr: IndexExpr::ColumnPrefix { - column: Ident::new("c1"), - length: 10, + index_exprs: vec![ + OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, }, - asc: None, + with_fill: None, }, - IndexField { - expr: IndexExpr::Functional(Box::new(Expr::Function(Function { - name: ObjectName::from(vec![Ident::new("LOWER")]), - uses_odbc_syntax: false, - parameters: FunctionArguments::None, - args: FunctionArguments::List(FunctionArgumentList { - duplicate_treatment: None, - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( - Expr::Identifier(Ident::new("c2")) - )),], - clauses: vec![], - }), - filter: None, - null_treatment: None, - over: None, - within_group: vec![], - }))), - asc: Some(false), + OrderByExpr { + expr: Expr::Identifier(Ident::new("c2")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, } ], } @@ -15254,9 +15244,13 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::BTree), - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], } ); @@ -15268,9 +15262,13 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::Hash), - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], } ); @@ -15282,9 +15280,13 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::BTree), - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], } ); @@ -15296,12 +15298,62 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::Hash), - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("c1")), - asc: None, + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("c1")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], } ); + + test_parse_table_constraint!( + dialect, + "KEY (c1(10), (LOWER(c2)) DESC)", + TableConstraint::Index { + display_as_key: true, + name: None, + index_type: None, + index_exprs: vec![ + OrderByExpr { + expr: Expr::ColumnPrefix { + column: Ident::new("c1"), + length: 10 + }, + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, + }, + OrderByExpr { + expr: Expr::Nested(Box::new(Expr::Function(Function { + name: ObjectName::from(vec![Ident::new("LOWER")]), + uses_odbc_syntax: false, + parameters: FunctionArguments::None, + args: FunctionArguments::List(FunctionArgumentList { + duplicate_treatment: None, + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( + Expr::Identifier(Ident::new("c2")) + )),], + clauses: vec![], + }), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + }))), + options: OrderByOptions { + asc: Some(false), + nulls_first: None, + }, + with_fill: None, + } + ], + } + ); } #[test] diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index d4160c61d..34cd15039 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -665,7 +665,7 @@ fn table_constraint_unique_primary_ctor( name: Option, index_name: Option, index_type: Option, - index_fields: Vec, + index_exprs: Vec, index_options: Vec, characteristics: Option, unique_index_type_display: Option, @@ -676,7 +676,7 @@ fn table_constraint_unique_primary_ctor( index_name, index_type_display, index_type, - index_fields, + index_exprs, index_options, characteristics, nulls_distinct: NullsDistinctOption::None, @@ -685,7 +685,7 @@ fn table_constraint_unique_primary_ctor( name, index_name, index_type, - index_fields, + index_exprs, index_options, characteristics, }, @@ -713,9 +713,13 @@ fn parse_create_table_primary_and_unique_key() { Some(Ident::new("bar_key")), None, None, - vec![IndexField { - expr: IndexExpr::Column(Ident::new("bar")), - asc: None, + vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("bar")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], vec![], None, @@ -780,13 +784,21 @@ fn parse_create_table_primary_and_unique_key_with_index_options() { Some(Ident::new("index_name")), None, vec![ - IndexField { - expr: IndexExpr::Column(Ident::new("bar")), - asc: None, + OrderByExpr { + expr: Expr::Identifier(Ident::new("bar")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }, - IndexField { - expr: IndexExpr::Column(Ident::new("var")), - asc: None, + OrderByExpr { + expr: Expr::Identifier(Ident::new("var")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }, ], vec![ @@ -826,9 +838,13 @@ fn parse_create_table_primary_and_unique_key_with_index_type() { None, Some(Ident::new("index_name")), Some(IndexType::BTree), - vec![IndexField { - expr: IndexExpr::Column(Ident::new("bar")), - asc: None, + vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("bar")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], vec![IndexOption::Using(IndexType::Hash)], None, @@ -2270,9 +2286,13 @@ fn parse_alter_table_add_keys() { AlterTableOperation::AddConstraint(TableConstraint::PrimaryKey { name: None, index_name: None, - index_fields: vec![IndexField { - expr: IndexExpr::Column(Ident::new("a")), - asc: None + index_exprs: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new("a")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }], index_type: None, index_options: vec![], @@ -2282,18 +2302,26 @@ fn parse_alter_table_add_keys() { display_as_key: true, name: Some(Ident::new("b")), index_type: None, - index_fields: vec![ - IndexField { - expr: IndexExpr::ColumnPrefix { + index_exprs: vec![ + OrderByExpr { + expr: Expr::ColumnPrefix { column: Ident::new("b"), - length: 20, + length: 20 }, - asc: None, + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, + }, + OrderByExpr { + expr: Expr::Identifier(Ident::new("c")), + options: OrderByOptions { + asc: None, + nulls_first: None, + }, + with_fill: None, }, - IndexField { - expr: IndexExpr::Column(Ident::new("c")), - asc: None, - } ] }) ] From 9991ea168a9d7235fdeeedb32ece6fd1fdb5a850 Mon Sep 17 00:00:00 2001 From: "Alex.Mo" Date: Mon, 24 Feb 2025 11:43:13 +0800 Subject: [PATCH 3/5] index expr donot has `with_fill` --- src/parser/mod.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 951976504..79033457f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7654,18 +7654,10 @@ impl<'a> Parser<'a> { let expr = self.parse_index_expr()?; let options = self.parse_order_by_options()?; - let with_fill = if dialect_of!(self is ClickHouseDialect | GenericDialect) - && self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) - { - Some(self.parse_with_fill()?) - } else { - None - }; - Ok(OrderByExpr { expr, options, - with_fill, + with_fill: None, }) } From d2092d6c4e6846cc89acfbcb637ed812e96ede64 Mon Sep 17 00:00:00 2001 From: "Alex.Mo" Date: Tue, 25 Feb 2025 10:31:20 +0800 Subject: [PATCH 4/5] introduce IndexExpr --- src/ast/ddl.rs | 13 ++-- src/ast/mod.rs | 31 ++++++++ src/ast/spans.rs | 17 ++++- src/parser/mod.rs | 151 ++++++++++++++++++++++----------------- tests/sqlparser_mysql.rs | 71 +++++++++++------- 5 files changed, 185 insertions(+), 98 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 62fbe400e..99801d247 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -32,8 +32,9 @@ use crate::ast::value::escape_single_quote_string; use crate::ast::{ display_comma_separated, display_separated, CommentDef, CreateFunctionBody, CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull, - FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName, - OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value, + FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexExpr, MySQLColumnPosition, + ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, + Value, }; use crate::keywords::Keyword; use crate::tokenizer::Token; @@ -833,7 +834,7 @@ pub enum TableConstraint { /// [1]: IndexType index_type: Option, /// Index expr list. - index_exprs: Vec, + index_exprs: Vec, index_options: Vec, characteristics: Option, /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` @@ -869,7 +870,7 @@ pub enum TableConstraint { /// [1]: IndexType index_type: Option, /// Index expr list. - index_exprs: Vec, + index_exprs: Vec, index_options: Vec, characteristics: Option, }, @@ -908,7 +909,7 @@ pub enum TableConstraint { /// [1]: IndexType index_type: Option, /// Index expr list. - index_exprs: Vec, + index_exprs: Vec, }, /// 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. @@ -931,7 +932,7 @@ pub enum TableConstraint { /// Optional index name. opt_index_name: Option, /// Index expr list. - index_exprs: Vec, + index_exprs: Vec, }, } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b65ad17bd..d9512388b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8701,6 +8701,37 @@ pub enum CopyIntoSnowflakeKind { Location, } +/// Index Field +/// +/// This structure used here [`MySQL` CREATE INDEX][1], [`PostgreSQL` CREATE INDEX][2], [`MySQL` CREATE TABLE][3]. +/// +/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html +/// [2]: https://www.postgresql.org/docs/17/sql-createindex.html +/// [3]: https://dev.mysql.com/doc/refman/8.3/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 IndexExpr { + pub expr: Expr, + pub collation: Option, + pub operator_class: Option, + pub order_options: OrderByOptions, +} + +impl fmt::Display for IndexExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.expr)?; + if let Some(collation) = &self.collation { + write!(f, "{collation}")?; + } + if let Some(operator) = &self.operator_class { + write!(f, "{operator}")?; + } + write!(f, "{}", self.order_options) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 2c850bfae..aa1f13690 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -27,8 +27,8 @@ use super::{ CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, FunctionArguments, - GroupByExpr, HavingBound, IlikeSelectItem, Insert, Interpolate, InterpolateExpr, Join, - JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, MatchRecognizePattern, + GroupByExpr, HavingBound, IlikeSelectItem, IndexExpr, Insert, Interpolate, InterpolateExpr, + Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, ProjectionSelect, Query, ReferentialAction, RenameSelectItem, ReplaceSelectElement, @@ -2179,6 +2179,19 @@ impl Spanned for TableObject { } } +impl Spanned for IndexExpr { + fn span(&self) -> Span { + let IndexExpr { + expr, + collation: _, + operator_class: _, + order_options: _, + } = self; + + union_spans(core::iter::once(expr.span())) + } +} + #[cfg(test)] pub mod tests { use crate::dialect::{Dialect, GenericDialect, SnowflakeDialect}; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 79033457f..b2634dfd7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7440,7 +7440,7 @@ impl<'a> Parser<'a> { let index_name = self.parse_optional_indent()?; let index_type = self.parse_optional_using_then_index_type()?; - let index_fields = self.parse_index_exprs()?; + let index_exprs = self.parse_index_exprs()?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::Unique { @@ -7448,7 +7448,7 @@ impl<'a> Parser<'a> { index_name, index_type_display, index_type, - index_exprs: index_fields, + index_exprs, index_options, characteristics, nulls_distinct, @@ -7525,13 +7525,13 @@ impl<'a> Parser<'a> { }; let index_type = self.parse_optional_using_then_index_type()?; - let index_fields = self.parse_index_exprs()?; + let index_exprs = self.parse_index_exprs()?; Ok(Some(TableConstraint::Index { display_as_key, name, index_type, - index_exprs: index_fields, + index_exprs, })) } Token::Word(w) @@ -7646,36 +7646,37 @@ impl<'a> Parser<'a> { } } - pub fn parse_index_exprs(&mut self) -> Result, ParserError> { - self.parse_parenthesized(|p| p.parse_comma_separated(Parser::parse_index_field)) - } - - pub fn parse_index_field(&mut self) -> Result { - let expr = self.parse_index_expr()?; - let options = self.parse_order_by_options()?; - - Ok(OrderByExpr { - expr, - options, - with_fill: None, - }) + pub fn parse_index_exprs(&mut self) -> Result, ParserError> { + self.parse_parenthesized(|p| p.parse_comma_separated(Parser::parse_index_expr)) } - pub fn parse_index_expr(&mut self) -> Result { - if self.peek_token() == Token::LParen { - let expr = self.parse_expr()?; - return Ok(expr); - } - - let column = self.parse_identifier()?; + pub fn parse_index_expr(&mut self) -> Result { + let expr = self.parse_expr()?; + let collation = if self.parse_keyword(Keyword::COLLATE) { + Some(self.parse_object_name(false)?) + } else { + None + }; - if dialect_of!(self is MySqlDialect | GenericDialect) && self.peek_token() == Token::LParen + let (operator_class, order_options) = if self.peek_keyword(Keyword::ASC) + || self.peek_keyword(Keyword::DESC) + || self.peek_keyword(Keyword::NULLS) { - let length = self.parse_parenthesized(Parser::parse_literal_uint)?; - return Ok(Expr::ColumnPrefix { column, length }); - } + let order_options = self.parse_order_by_options()?; + (None, order_options) + } else { + let operator_class = self.maybe_parse(|p| p.parse_expr())?; + + let order_options = self.parse_order_by_options()?; + (operator_class, order_options) + }; - Ok(Expr::Identifier(column)) + Ok(IndexExpr { + expr, + collation, + operator_class, + order_options, + }) } /// Parse `[ident]`, mostly `ident` is name, like: @@ -15172,13 +15173,14 @@ mod tests { display_as_key: false, name: None, index_type: None, - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], } ); @@ -15190,13 +15192,14 @@ mod tests { display_as_key: true, name: None, index_type: None, - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], } ); @@ -15209,21 +15212,23 @@ mod tests { name: Some(Ident::with_quote('\'', "index")), index_type: None, index_exprs: vec![ - OrderByExpr { + IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }, - OrderByExpr { + IndexExpr { expr: Expr::Identifier(Ident::new("c2")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, } ], } @@ -15236,13 +15241,14 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::BTree), - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], } ); @@ -15254,13 +15260,14 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::Hash), - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], } ); @@ -15272,13 +15279,14 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::BTree), - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], } ); @@ -15290,13 +15298,14 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::Hash), - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("c1")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], } ); @@ -15309,18 +15318,31 @@ mod tests { name: None, index_type: None, index_exprs: vec![ - OrderByExpr { - expr: Expr::ColumnPrefix { - column: Ident::new("c1"), - length: 10 - }, - options: OrderByOptions { + IndexExpr { + expr: Expr::Function(Function { + name: ObjectName::from(vec![Ident::new("c1")]), + uses_odbc_syntax: false, + parameters: FunctionArguments::None, + args: FunctionArguments::List(FunctionArgumentList { + duplicate_treatment: None, + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( + Expr::Value(crate::test_utils::number("10")) + )),], + clauses: vec![], + }), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + }), + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }, - OrderByExpr { + IndexExpr { expr: Expr::Nested(Box::new(Expr::Function(Function { name: ObjectName::from(vec![Ident::new("LOWER")]), uses_odbc_syntax: false, @@ -15337,11 +15359,12 @@ mod tests { over: None, within_group: vec![], }))), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: Some(false), nulls_first: None, - }, - with_fill: None, + } } ], } diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 34cd15039..d782c8afb 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -665,7 +665,7 @@ fn table_constraint_unique_primary_ctor( name: Option, index_name: Option, index_type: Option, - index_exprs: Vec, + index_exprs: Vec, index_options: Vec, characteristics: Option, unique_index_type_display: Option, @@ -713,13 +713,14 @@ fn parse_create_table_primary_and_unique_key() { Some(Ident::new("bar_key")), None, None, - vec![OrderByExpr { + vec![IndexExpr { expr: Expr::Identifier(Ident::new("bar")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], vec![], None, @@ -784,21 +785,23 @@ fn parse_create_table_primary_and_unique_key_with_index_options() { Some(Ident::new("index_name")), None, vec![ - OrderByExpr { + IndexExpr { expr: Expr::Identifier(Ident::new("bar")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }, - OrderByExpr { + IndexExpr { expr: Expr::Identifier(Ident::new("var")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }, ], vec![ @@ -838,13 +841,14 @@ fn parse_create_table_primary_and_unique_key_with_index_type() { None, Some(Ident::new("index_name")), Some(IndexType::BTree), - vec![OrderByExpr { + vec![IndexExpr { expr: Expr::Identifier(Ident::new("bar")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], vec![IndexOption::Using(IndexType::Hash)], None, @@ -2286,13 +2290,14 @@ fn parse_alter_table_add_keys() { AlterTableOperation::AddConstraint(TableConstraint::PrimaryKey { name: None, index_name: None, - index_exprs: vec![OrderByExpr { + index_exprs: vec![IndexExpr { expr: Expr::Identifier(Ident::new("a")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }], index_type: None, index_options: vec![], @@ -2303,24 +2308,38 @@ fn parse_alter_table_add_keys() { name: Some(Ident::new("b")), index_type: None, index_exprs: vec![ - OrderByExpr { - expr: Expr::ColumnPrefix { - column: Ident::new("b"), - length: 20 - }, - options: OrderByOptions { + IndexExpr { + expr: Expr::Function(Function { + name: ObjectName::from(vec![Ident::new("b")]), + uses_odbc_syntax: false, + parameters: FunctionArguments::None, + args: FunctionArguments::List(FunctionArgumentList { + duplicate_treatment: None, + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( + Expr::Value(crate::test_utils::number("20")) + )),], + clauses: vec![], + }), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + }), + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }, - OrderByExpr { + IndexExpr { expr: Expr::Identifier(Ident::new("c")), - options: OrderByOptions { + collation: None, + operator_class: None, + order_options: OrderByOptions { asc: None, nulls_first: None, }, - with_fill: None, }, ] }) From 5d6ff6a89a3a2d06aff6ac867d023631a70efecf Mon Sep 17 00:00:00 2001 From: "Alex.Mo" Date: Tue, 25 Feb 2025 10:44:14 +0800 Subject: [PATCH 5/5] remove Expr::ColumnPrefix --- src/ast/mod.rs | 11 ----------- src/ast/spans.rs | 1 - 2 files changed, 12 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d9512388b..5699ce66b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1049,16 +1049,6 @@ pub enum Expr { /// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html) /// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html) Lambda(LambdaFunction), - /// A ColumnPrefix used in MySQL indexes. - /// ```sql - /// CREATE INDEX ON tbl (col(10)); - /// ``` - /// - /// [MySQL](https://dev.mysql.com/doc/refman/8.0/en/create-index.html) - ColumnPrefix { - column: Ident, - length: u64, - }, } /// The contents inside the `[` and `]` in a subscript expression. @@ -1827,7 +1817,6 @@ impl fmt::Display for Expr { } Expr::Prior(expr) => write!(f, "PRIOR {expr}"), Expr::Lambda(lambda) => write!(f, "{lambda}"), - Expr::ColumnPrefix { column, length } => write!(f, "{column}({length})"), } } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index aa1f13690..9e7c0589d 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1479,7 +1479,6 @@ impl Spanned for Expr { Expr::OuterJoin(expr) => expr.span(), Expr::Prior(expr) => expr.span(), Expr::Lambda(_) => Span::empty(), - Expr::ColumnPrefix { .. } => Span::empty(), } } }