Skip to content

Commit 69888cc

Browse files
committed
Use IndexColumn in all index definitions
Index column lists generally allow for more flexibility than just column names: i.e. `ASC`/`DESC` modifiers, Postgres opclasses, MySQL column prefix length, and generic expressions/"functional key parts". This change uses the existing support for these constructs added in #1707 for `CREATE INDEX` and changes the AST for `ALTER TABLE <table> ADD KEY` (and associated variants), and constraints present in `CREATE TABLE` statements to support these constructs in those location. Note that, as was already the case with existing `CREATE INDEX` support, there is no special representation for MySQL column prefix length (`INDEX (textcol(10))`), nor do we require the enclosing parentheses for MySQL functional key parts (`INDEX ((col1 + col2))`; for Postgres, this is optional if it doesn't introduce ambiguity). Instead, these are parsed generically as expressions, so the former is parsed as a function call and the latter is wrapped in `Expr::Nested`. Also note that, as far as I can tell, no dialect supports these more general column expressions in the case of `FOREIGN KEY` constraints, so the parsing and AST for foreign keys are left unchanged.
1 parent 185a490 commit 69888cc

File tree

5 files changed

+185
-35
lines changed

5 files changed

+185
-35
lines changed

src/ast/ddl.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ use crate::ast::value::escape_single_quote_string;
3232
use crate::ast::{
3333
display_comma_separated, display_separated, CommentDef, CreateFunctionBody,
3434
CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
35-
FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName,
36-
OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
37-
ValueWithSpan,
35+
FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexColumn, MySQLColumnPosition,
36+
ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag,
37+
Value, ValueWithSpan,
3838
};
3939
use crate::keywords::Keyword;
4040
use crate::tokenizer::Token;
@@ -979,7 +979,7 @@ pub enum TableConstraint {
979979
/// [1]: IndexType
980980
index_type: Option<IndexType>,
981981
/// Identifiers of the columns that are unique.
982-
columns: Vec<Ident>,
982+
columns: Vec<IndexColumn>,
983983
index_options: Vec<IndexOption>,
984984
characteristics: Option<ConstraintCharacteristics>,
985985
/// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
@@ -1015,7 +1015,7 @@ pub enum TableConstraint {
10151015
/// [1]: IndexType
10161016
index_type: Option<IndexType>,
10171017
/// Identifiers of the columns that form the primary key.
1018-
columns: Vec<Ident>,
1018+
columns: Vec<IndexColumn>,
10191019
index_options: Vec<IndexOption>,
10201020
characteristics: Option<ConstraintCharacteristics>,
10211021
},
@@ -1060,7 +1060,7 @@ pub enum TableConstraint {
10601060
/// [1]: IndexType
10611061
index_type: Option<IndexType>,
10621062
/// Referred column identifier list.
1063-
columns: Vec<Ident>,
1063+
columns: Vec<IndexColumn>,
10641064
},
10651065
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
10661066
/// and MySQL displays both the same way, it is part of this definition as well.
@@ -1083,7 +1083,7 @@ pub enum TableConstraint {
10831083
/// Optional index name.
10841084
opt_index_name: Option<Ident>,
10851085
/// Referred column identifier list.
1086-
columns: Vec<Ident>,
1086+
columns: Vec<IndexColumn>,
10871087
},
10881088
}
10891089

src/ast/spans.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@ use super::{
2828
ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte,
2929
Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable,
3030
Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList,
31-
FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, Insert, Interpolate,
32-
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
33-
LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList, NamedWindowDefinition,
34-
ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement,
35-
OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, ProjectionSelect, Query,
36-
RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
37-
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
38-
SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
39-
TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef,
40-
WhileStatement, WildcardAdditionalOptions, With, WithFill,
31+
FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, IndexColumn, Insert,
32+
Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem,
33+
LateralView, LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList,
34+
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
35+
OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource,
36+
ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction,
37+
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
38+
SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
39+
TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins,
40+
UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement,
41+
WildcardAdditionalOptions, With, WithFill,
4142
};
4243

4344
/// Given an iterator of spans, return the [Span::union] of all spans.
@@ -650,7 +651,7 @@ impl Spanned for TableConstraint {
650651
name.iter()
651652
.map(|i| i.span)
652653
.chain(index_name.iter().map(|i| i.span))
653-
.chain(columns.iter().map(|i| i.span))
654+
.chain(columns.iter().map(|i| i.span()))
654655
.chain(characteristics.iter().map(|i| i.span())),
655656
),
656657
TableConstraint::PrimaryKey {
@@ -664,7 +665,7 @@ impl Spanned for TableConstraint {
664665
name.iter()
665666
.map(|i| i.span)
666667
.chain(index_name.iter().map(|i| i.span))
667-
.chain(columns.iter().map(|i| i.span))
668+
.chain(columns.iter().map(|i| i.span()))
668669
.chain(characteristics.iter().map(|i| i.span())),
669670
),
670671
TableConstraint::ForeignKey {
@@ -700,7 +701,7 @@ impl Spanned for TableConstraint {
700701
} => union_spans(
701702
name.iter()
702703
.map(|i| i.span)
703-
.chain(columns.iter().map(|i| i.span)),
704+
.chain(columns.iter().map(|i| i.span())),
704705
),
705706
TableConstraint::FulltextOrSpatial {
706707
fulltext: _,
@@ -711,7 +712,7 @@ impl Spanned for TableConstraint {
711712
opt_index_name
712713
.iter()
713714
.map(|i| i.span)
714-
.chain(columns.iter().map(|i| i.span)),
715+
.chain(columns.iter().map(|i| i.span())),
715716
),
716717
}
717718
}
@@ -745,6 +746,12 @@ impl Spanned for CreateIndex {
745746
}
746747
}
747748

749+
impl Spanned for IndexColumn {
750+
fn span(&self) -> Span {
751+
self.column.span()
752+
}
753+
}
754+
748755
impl Spanned for CaseStatement {
749756
fn span(&self) -> Span {
750757
let CaseStatement {

src/parser/mod.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6868,9 +6868,7 @@ impl<'a> Parser<'a> {
68686868
None
68696869
};
68706870

6871-
self.expect_token(&Token::LParen)?;
6872-
let columns = self.parse_comma_separated(Parser::parse_create_index_expr)?;
6873-
self.expect_token(&Token::RParen)?;
6871+
let columns = self.parse_parenthesized_index_column_list()?;
68746872

68756873
let include = if self.parse_keyword(Keyword::INCLUDE) {
68766874
self.expect_token(&Token::LParen)?;
@@ -8070,7 +8068,7 @@ impl<'a> Parser<'a> {
80708068
let index_name = self.parse_optional_ident()?;
80718069
let index_type = self.parse_optional_using_then_index_type()?;
80728070

8073-
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
8071+
let columns = self.parse_parenthesized_index_column_list()?;
80748072
let index_options = self.parse_index_options()?;
80758073
let characteristics = self.parse_constraint_characteristics()?;
80768074
Ok(Some(TableConstraint::Unique {
@@ -8092,7 +8090,7 @@ impl<'a> Parser<'a> {
80928090
let index_name = self.parse_optional_ident()?;
80938091
let index_type = self.parse_optional_using_then_index_type()?;
80948092

8095-
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
8093+
let columns = self.parse_parenthesized_index_column_list()?;
80968094
let index_options = self.parse_index_options()?;
80978095
let characteristics = self.parse_constraint_characteristics()?;
80988096
Ok(Some(TableConstraint::PrimaryKey {
@@ -8170,7 +8168,7 @@ impl<'a> Parser<'a> {
81708168
};
81718169

81728170
let index_type = self.parse_optional_using_then_index_type()?;
8173-
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
8171+
let columns = self.parse_parenthesized_index_column_list()?;
81748172

81758173
Ok(Some(TableConstraint::Index {
81768174
display_as_key,
@@ -8199,7 +8197,7 @@ impl<'a> Parser<'a> {
81998197

82008198
let opt_index_name = self.parse_optional_ident()?;
82018199

8202-
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
8200+
let columns = self.parse_parenthesized_index_column_list()?;
82038201

82048202
Ok(Some(TableConstraint::FulltextOrSpatial {
82058203
fulltext,
@@ -10595,6 +10593,16 @@ impl<'a> Parser<'a> {
1059510593
self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| p.parse_identifier())
1059610594
}
1059710595

10596+
/// Parses a parenthesized comma-separated list of index columns, which can be arbitrary
10597+
/// expressions with ordering information (and an opclass in some dialects).
10598+
pub fn parse_parenthesized_index_column_list(
10599+
&mut self,
10600+
) -> Result<Vec<IndexColumn>, ParserError> {
10601+
self.parse_parenthesized_column_list_inner(Mandatory, false, |p| {
10602+
p.parse_create_index_expr()
10603+
})
10604+
}
10605+
1059810606
/// Parses a parenthesized comma-separated list of qualified, possibly quoted identifiers.
1059910607
/// For example: `(db1.sc1.tbl1.col1, db1.sc1.tbl1."col 2", ...)`
1060010608
pub fn parse_parenthesized_qualified_column_list(
@@ -16476,6 +16484,22 @@ mod tests {
1647616484
}};
1647716485
}
1647816486

16487+
macro_rules! mk_expected_col {
16488+
($name:expr) => {
16489+
IndexColumn {
16490+
column: OrderByExpr {
16491+
expr: Expr::Identifier($name.into()),
16492+
options: OrderByOptions {
16493+
asc: None,
16494+
nulls_first: None,
16495+
},
16496+
with_fill: None,
16497+
},
16498+
operator_class: None,
16499+
}
16500+
};
16501+
}
16502+
1647916503
let dialect =
1648016504
TestedDialects::new(vec![Box::new(GenericDialect {}), Box::new(MySqlDialect {})]);
1648116505

@@ -16486,7 +16510,7 @@ mod tests {
1648616510
display_as_key: false,
1648716511
name: None,
1648816512
index_type: None,
16489-
columns: vec![Ident::new("c1")],
16513+
columns: vec![mk_expected_col!("c1")],
1649016514
}
1649116515
);
1649216516

@@ -16497,7 +16521,7 @@ mod tests {
1649716521
display_as_key: true,
1649816522
name: None,
1649916523
index_type: None,
16500-
columns: vec![Ident::new("c1")],
16524+
columns: vec![mk_expected_col!("c1")],
1650116525
}
1650216526
);
1650316527

@@ -16508,7 +16532,7 @@ mod tests {
1650816532
display_as_key: false,
1650916533
name: Some(Ident::with_quote('\'', "index")),
1651016534
index_type: None,
16511-
columns: vec![Ident::new("c1"), Ident::new("c2")],
16535+
columns: vec![mk_expected_col!("c1"), mk_expected_col!("c2")],
1651216536
}
1651316537
);
1651416538

@@ -16519,7 +16543,7 @@ mod tests {
1651916543
display_as_key: false,
1652016544
name: None,
1652116545
index_type: Some(IndexType::BTree),
16522-
columns: vec![Ident::new("c1")],
16546+
columns: vec![mk_expected_col!("c1")],
1652316547
}
1652416548
);
1652516549

@@ -16530,7 +16554,7 @@ mod tests {
1653016554
display_as_key: false,
1653116555
name: None,
1653216556
index_type: Some(IndexType::Hash),
16533-
columns: vec![Ident::new("c1")],
16557+
columns: vec![mk_expected_col!("c1")],
1653416558
}
1653516559
);
1653616560

@@ -16541,7 +16565,7 @@ mod tests {
1654116565
display_as_key: false,
1654216566
name: Some(Ident::new("idx_name")),
1654316567
index_type: Some(IndexType::BTree),
16544-
columns: vec![Ident::new("c1")],
16568+
columns: vec![mk_expected_col!("c1")],
1654516569
}
1654616570
);
1654716571

@@ -16552,7 +16576,7 @@ mod tests {
1655216576
display_as_key: false,
1655316577
name: Some(Ident::new("idx_name")),
1655416578
index_type: Some(IndexType::Hash),
16555-
columns: vec![Ident::new("c1")],
16579+
columns: vec![mk_expected_col!("c1")],
1655616580
}
1655716581
);
1655816582
}

src/test_utils.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,47 @@ pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
448448
within_group: vec![],
449449
})
450450
}
451+
452+
/// Gets the first index column (mysql calls it a key part) of the first index a CREATE INDEX,
453+
/// CREATE TABLE, or ALTER TABLE statement.
454+
pub fn index_column(stmt: Statement) -> Expr {
455+
match stmt {
456+
Statement::CreateIndex(CreateIndex { columns, .. }) => {
457+
columns.first().unwrap().column.expr.clone()
458+
}
459+
Statement::CreateTable(CreateTable { constraints, .. }) => {
460+
match constraints.first().unwrap() {
461+
TableConstraint::Index { columns, .. } => {
462+
columns.first().unwrap().column.expr.clone()
463+
}
464+
TableConstraint::Unique { columns, .. } => {
465+
columns.first().unwrap().column.expr.clone()
466+
}
467+
TableConstraint::PrimaryKey { columns, .. } => {
468+
columns.first().unwrap().column.expr.clone()
469+
}
470+
TableConstraint::FulltextOrSpatial { columns, .. } => {
471+
columns.first().unwrap().column.expr.clone()
472+
}
473+
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
474+
}
475+
}
476+
Statement::AlterTable { operations, .. } => match operations.first().unwrap() {
477+
AlterTableOperation::AddConstraint(TableConstraint::Index { columns, .. }) => {
478+
columns.first().unwrap().column.expr.clone()
479+
}
480+
AlterTableOperation::AddConstraint(TableConstraint::Unique { columns, .. }) => {
481+
columns.first().unwrap().column.expr.clone()
482+
}
483+
AlterTableOperation::AddConstraint(TableConstraint::PrimaryKey { columns, .. }) => {
484+
columns.first().unwrap().column.expr.clone()
485+
}
486+
AlterTableOperation::AddConstraint(TableConstraint::FulltextOrSpatial {
487+
columns,
488+
..
489+
}) => columns.first().unwrap().column.expr.clone(),
490+
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
491+
},
492+
_ => panic!("Expected CREATE INDEX, ALTER TABLE, or CREATE TABLE, got: {stmt:?}"),
493+
}
494+
}

0 commit comments

Comments
 (0)