Skip to content

Commit 67fca82

Browse files
authored
Improve MySQL option parsing in index definitions (#1997)
1 parent 18b4a14 commit 67fca82

File tree

9 files changed

+628
-498
lines changed

9 files changed

+628
-498
lines changed

src/ast/ddl.rs

Lines changed: 492 additions & 10 deletions
Large diffs are not rendered by default.

src/ast/dml.rs

Lines changed: 4 additions & 466 deletions
Large diffs are not rendered by default.

src/ast/helpers/stmt_create_table.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ use serde::{Deserialize, Serialize};
2424
#[cfg(feature = "visitor")]
2525
use sqlparser_derive::{Visit, VisitMut};
2626

27-
use super::super::dml::CreateTable;
2827
use crate::ast::{
29-
ClusteredBy, ColumnDef, CommentDef, CreateTableOptions, Expr, FileFormat,
28+
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableOptions, Expr, FileFormat,
3029
HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
3130
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, Tag,
3231
WrappedCollection,

src/ast/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ pub use self::ddl::{
6363
AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
6464
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy,
6565
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction,
66-
Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode,
67-
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
68-
IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner,
69-
Partition, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity,
70-
TableConstraint, TagsColumnOption, UserDefinedTypeCompositeAttributeDef,
71-
UserDefinedTypeRepresentation, ViewColumnDef,
66+
CreateIndex, CreateTable, Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs,
67+
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
68+
IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType,
69+
KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction,
70+
RenameTableNameKind, ReplicaIdentity, TableConstraint, TagsColumnOption,
71+
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
7272
};
73-
pub use self::dml::{CreateIndex, CreateTable, Delete, IndexColumn, Insert};
73+
pub use self::dml::{Delete, Insert};
7474
pub use self::operator::{BinaryOperator, UnaryOperator};
7575
pub use self::query::{
7676
AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,

src/ast/spans.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ impl Spanned for TableConstraint {
713713
name,
714714
index_type: _,
715715
columns,
716+
index_options: _,
716717
} => union_spans(
717718
name.iter()
718719
.map(|i| i.span)
@@ -747,6 +748,8 @@ impl Spanned for CreateIndex {
747748
nulls_distinct: _, // bool
748749
with,
749750
predicate,
751+
index_options: _,
752+
alter_options,
750753
} = self;
751754

752755
union_spans(
@@ -756,7 +759,8 @@ impl Spanned for CreateIndex {
756759
.chain(columns.iter().map(|i| i.column.span()))
757760
.chain(include.iter().map(|i| i.span))
758761
.chain(with.iter().map(|i| i.span()))
759-
.chain(predicate.iter().map(|i| i.span())),
762+
.chain(predicate.iter().map(|i| i.span()))
763+
.chain(alter_options.iter().map(|i| i.span())),
760764
)
761765
}
762766
}

src/parser/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7083,6 +7083,22 @@ impl<'a> Parser<'a> {
70837083
None
70847084
};
70857085

7086+
// MySQL options (including the modern style of `USING` after the column list instead of
7087+
// before, which is deprecated) shouldn't conflict with other preceding options (e.g. `WITH
7088+
// PARSER` won't be caught by the above `WITH` clause parsing because MySQL doesn't set that
7089+
// support flag). This is probably invalid syntax for other dialects, but it is simpler to
7090+
// parse it anyway (as we do inside `ALTER TABLE` and `CREATE TABLE` parsing).
7091+
let index_options = self.parse_index_options()?;
7092+
7093+
// MySQL allows `ALGORITHM` and `LOCK` options. Unlike in `ALTER TABLE`, they need not be comma separated.
7094+
let mut alter_options = Vec::new();
7095+
while self
7096+
.peek_one_of_keywords(&[Keyword::ALGORITHM, Keyword::LOCK])
7097+
.is_some()
7098+
{
7099+
alter_options.push(self.parse_alter_table_operation()?)
7100+
}
7101+
70867102
Ok(Statement::CreateIndex(CreateIndex {
70877103
name: index_name,
70887104
table_name,
@@ -7095,6 +7111,8 @@ impl<'a> Parser<'a> {
70957111
nulls_distinct,
70967112
with,
70977113
predicate,
7114+
index_options,
7115+
alter_options,
70987116
}))
70997117
}
71007118

@@ -8407,12 +8425,14 @@ impl<'a> Parser<'a> {
84078425

84088426
let index_type = self.parse_optional_using_then_index_type()?;
84098427
let columns = self.parse_parenthesized_index_column_list()?;
8428+
let index_options = self.parse_index_options()?;
84108429

84118430
Ok(Some(TableConstraint::Index {
84128431
display_as_key,
84138432
name,
84148433
index_type,
84158434
columns,
8435+
index_options,
84168436
}))
84178437
}
84188438
Token::Word(w)
@@ -17475,6 +17495,7 @@ mod tests {
1747517495
name: None,
1747617496
index_type: None,
1747717497
columns: vec![mk_expected_col("c1")],
17498+
index_options: vec![],
1747817499
}
1747917500
);
1748017501

@@ -17486,6 +17507,7 @@ mod tests {
1748617507
name: None,
1748717508
index_type: None,
1748817509
columns: vec![mk_expected_col("c1")],
17510+
index_options: vec![],
1748917511
}
1749017512
);
1749117513

@@ -17497,6 +17519,7 @@ mod tests {
1749717519
name: Some(Ident::with_quote('\'', "index")),
1749817520
index_type: None,
1749917521
columns: vec![mk_expected_col("c1"), mk_expected_col("c2")],
17522+
index_options: vec![],
1750017523
}
1750117524
);
1750217525

@@ -17508,6 +17531,7 @@ mod tests {
1750817531
name: None,
1750917532
index_type: Some(IndexType::BTree),
1751017533
columns: vec![mk_expected_col("c1")],
17534+
index_options: vec![],
1751117535
}
1751217536
);
1751317537

@@ -17519,6 +17543,7 @@ mod tests {
1751917543
name: None,
1752017544
index_type: Some(IndexType::Hash),
1752117545
columns: vec![mk_expected_col("c1")],
17546+
index_options: vec![],
1752217547
}
1752317548
);
1752417549

@@ -17530,6 +17555,7 @@ mod tests {
1753017555
name: Some(Ident::new("idx_name")),
1753117556
index_type: Some(IndexType::BTree),
1753217557
columns: vec![mk_expected_col("c1")],
17558+
index_options: vec![],
1753317559
}
1753417560
);
1753517561

@@ -17541,6 +17567,7 @@ mod tests {
1754117567
name: Some(Ident::new("idx_name")),
1754217568
index_type: Some(IndexType::Hash),
1754317569
columns: vec![mk_expected_col("c1")],
17570+
index_options: vec![],
1754417571
}
1754517572
);
1754617573
}

tests/sqlparser_common.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9189,7 +9189,7 @@ fn ensure_multiple_dialects_are_tested() {
91899189

91909190
#[test]
91919191
fn parse_create_index() {
9192-
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test(name,age DESC)";
9192+
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test(name, age DESC)";
91939193
let indexed_columns: Vec<IndexColumn> = vec![
91949194
IndexColumn {
91959195
operator_class: None,
@@ -9235,7 +9235,7 @@ fn parse_create_index() {
92359235

92369236
#[test]
92379237
fn test_create_index_with_using_function() {
9238-
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test USING BTREE (name,age DESC)";
9238+
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test USING BTREE (name, age DESC)";
92399239
let indexed_columns: Vec<IndexColumn> = vec![
92409240
IndexColumn {
92419241
operator_class: None,
@@ -9273,6 +9273,8 @@ fn test_create_index_with_using_function() {
92739273
nulls_distinct: None,
92749274
with,
92759275
predicate: None,
9276+
index_options,
9277+
alter_options,
92769278
}) => {
92779279
assert_eq!("idx_name", name.to_string());
92789280
assert_eq!("test", table_name.to_string());
@@ -9283,6 +9285,8 @@ fn test_create_index_with_using_function() {
92839285
assert!(if_not_exists);
92849286
assert!(include.is_empty());
92859287
assert!(with.is_empty());
9288+
assert!(index_options.is_empty());
9289+
assert!(alter_options.is_empty());
92869290
}
92879291
_ => unreachable!(),
92889292
}
@@ -9324,6 +9328,8 @@ fn test_create_index_with_with_clause() {
93249328
nulls_distinct: None,
93259329
with,
93269330
predicate: None,
9331+
index_options,
9332+
alter_options,
93279333
}) => {
93289334
pretty_assertions::assert_eq!("title_idx", name.to_string());
93299335
pretty_assertions::assert_eq!("films", table_name.to_string());
@@ -9333,6 +9339,8 @@ fn test_create_index_with_with_clause() {
93339339
assert!(!if_not_exists);
93349340
assert!(include.is_empty());
93359341
pretty_assertions::assert_eq!(with_parameters, with);
9342+
assert!(index_options.is_empty());
9343+
assert!(alter_options.is_empty());
93369344
}
93379345
_ => unreachable!(),
93389346
}

tests/sqlparser_mysql.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4215,3 +4215,31 @@ fn parse_show_charset() {
42154215
mysql().verified_stmt("SHOW CHARSET WHERE charset = 'utf8mb4%'");
42164216
mysql().verified_stmt("SHOW CHARSET LIKE 'utf8mb4%'");
42174217
}
4218+
4219+
#[test]
4220+
fn test_ddl_with_index_using() {
4221+
let columns = "(name, age DESC)";
4222+
let using = "USING BTREE";
4223+
4224+
for sql in [
4225+
format!("CREATE INDEX idx_name ON test {using} {columns}"),
4226+
format!("CREATE TABLE foo (name VARCHAR(255), age INT, KEY idx_name {using} {columns})"),
4227+
format!("ALTER TABLE foo ADD KEY idx_name {using} {columns}"),
4228+
format!("CREATE INDEX idx_name ON test{columns} {using}"),
4229+
format!("CREATE TABLE foo (name VARCHAR(255), age INT, KEY idx_name {columns} {using})"),
4230+
format!("ALTER TABLE foo ADD KEY idx_name {columns} {using}"),
4231+
] {
4232+
mysql_and_generic().verified_stmt(&sql);
4233+
}
4234+
}
4235+
4236+
#[test]
4237+
fn test_create_index_options() {
4238+
mysql_and_generic()
4239+
.verified_stmt("CREATE INDEX idx_name ON t(c1, c2) USING HASH LOCK = SHARED");
4240+
mysql_and_generic()
4241+
.verified_stmt("CREATE INDEX idx_name ON t(c1, c2) USING BTREE ALGORITHM = INPLACE");
4242+
mysql_and_generic().verified_stmt(
4243+
"CREATE INDEX idx_name ON t(c1, c2) USING BTREE LOCK = EXCLUSIVE ALGORITHM = DEFAULT",
4244+
);
4245+
}

0 commit comments

Comments
 (0)