Skip to content

Commit 041efad

Browse files
committed
Don't fail parsing a column definition with unexpected tokens
Since PR apache#93 `parse_column_def` parses a set of column options in a loop, e.g. given: ``` _______ column_def _______ CREATE TABLE foo (bar INT NOT NULL DEFAULT 1, ) -------- --------- option 1 option 2 ```` it parses column options until it encounters one of the delimiter tokens First when we only supported `CREATE TABLE`, the set of delimiters that stopped the parsing used to be `Token::Comma | Token::RParen`. Then we added support for `ALTER TABLE ADD COLUMN <column_def>`. Turns out the parser started to bail if the statement ended with a semicolon, while attempting to parse the semicolon as a column option, as we forgot to add it to the set of delimiter tokens. This was recently fixed in apache#246 by including Token::SemiColon to the list, but it felt wrong to have to update this list, and to have a common list of delimiters for two different contexts (CREATE TABLE with parens vs ALTER TABLE ADD COLUMN without parens). Also our current approach cannot handle multiple statements NOT separated by a semicolon, as is common in MS SQL DDL. We don't explicitly support it in `parse_statements`, but that's a use-case like to keep in mind nevertheless.
1 parent f57f314 commit 041efad

File tree

2 files changed

+38
-27
lines changed

2 files changed

+38
-27
lines changed

src/parser.rs

+30-26
Original file line numberDiff line numberDiff line change
@@ -1225,10 +1225,18 @@ impl Parser {
12251225
};
12261226
let mut options = vec![];
12271227
loop {
1228-
match self.peek_token() {
1229-
Token::EOF | Token::Comma | Token::RParen | Token::SemiColon => break,
1230-
_ => options.push(self.parse_column_option_def()?),
1231-
}
1228+
if self.parse_keyword(Keyword::CONSTRAINT) {
1229+
let name = Some(self.parse_identifier()?);
1230+
if let Some(option) = self.parse_optional_column_option()? {
1231+
options.push(ColumnOptionDef { name, option });
1232+
} else {
1233+
return self.expected("constraint details after CONSTRAINT <name>", self.peek_token());
1234+
}
1235+
} else if let Some(option) = self.parse_optional_column_option()? {
1236+
options.push(ColumnOptionDef { name: None, option });
1237+
} else {
1238+
break;
1239+
};
12321240
}
12331241
Ok(ColumnDef {
12341242
name,
@@ -1238,23 +1246,17 @@ impl Parser {
12381246
})
12391247
}
12401248

1241-
pub fn parse_column_option_def(&mut self) -> Result<ColumnOptionDef, ParserError> {
1242-
let name = if self.parse_keyword(Keyword::CONSTRAINT) {
1243-
Some(self.parse_identifier()?)
1244-
} else {
1245-
None
1246-
};
1247-
1248-
let option = if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
1249-
ColumnOption::NotNull
1249+
pub fn parse_optional_column_option(&mut self) -> Result<Option<ColumnOption>, ParserError> {
1250+
if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
1251+
Ok(Some(ColumnOption::NotNull))
12501252
} else if self.parse_keyword(Keyword::NULL) {
1251-
ColumnOption::Null
1253+
Ok(Some(ColumnOption::Null))
12521254
} else if self.parse_keyword(Keyword::DEFAULT) {
1253-
ColumnOption::Default(self.parse_expr()?)
1255+
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
12541256
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
1255-
ColumnOption::Unique { is_primary: true }
1257+
Ok(Some(ColumnOption::Unique { is_primary: true }))
12561258
} else if self.parse_keyword(Keyword::UNIQUE) {
1257-
ColumnOption::Unique { is_primary: false }
1259+
Ok(Some(ColumnOption::Unique { is_primary: false }))
12581260
} else if self.parse_keyword(Keyword::REFERENCES) {
12591261
let foreign_table = self.parse_object_name()?;
12601262
// PostgreSQL allows omitting the column list and
@@ -1273,28 +1275,30 @@ impl Parser {
12731275
break;
12741276
}
12751277
}
1276-
ColumnOption::ForeignKey {
1278+
Ok(Some(ColumnOption::ForeignKey {
12771279
foreign_table,
12781280
referred_columns,
12791281
on_delete,
12801282
on_update,
1281-
}
1283+
}))
12821284
} else if self.parse_keyword(Keyword::CHECK) {
12831285
self.expect_token(&Token::LParen)?;
12841286
let expr = self.parse_expr()?;
12851287
self.expect_token(&Token::RParen)?;
1286-
ColumnOption::Check(expr)
1288+
Ok(Some(ColumnOption::Check(expr)))
12871289
} else if self.parse_keyword(Keyword::AUTO_INCREMENT) {
12881290
// Support AUTO_INCREMENT for MySQL
1289-
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTO_INCREMENT")])
1291+
Ok(Some(ColumnOption::DialectSpecific(vec![
1292+
Token::make_keyword("AUTO_INCREMENT"),
1293+
])))
12901294
} else if self.parse_keyword(Keyword::AUTOINCREMENT) {
12911295
// Support AUTOINCREMENT for SQLite
1292-
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTOINCREMENT")])
1296+
Ok(Some(ColumnOption::DialectSpecific(vec![
1297+
Token::make_keyword("AUTOINCREMENT"),
1298+
])))
12931299
} else {
1294-
return self.expected("column option", self.peek_token());
1295-
};
1296-
1297-
Ok(ColumnOptionDef { name, option })
1300+
Ok(None)
1301+
}
12981302
}
12991303

13001304
pub fn parse_referential_action(&mut self) -> Result<ReferentialAction, ParserError> {

tests/sqlparser_common.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1141,7 +1141,14 @@ fn parse_create_table() {
11411141
assert!(res
11421142
.unwrap_err()
11431143
.to_string()
1144-
.contains("Expected column option, found: GARBAGE"));
1144+
.contains("Expected \',\' or \')\' after column definition, found: GARBAGE"));
1145+
1146+
1147+
let res = parse_sql_statements("CREATE TABLE t (a int NOT NULL CONSTRAINT foo)");
1148+
assert!(res
1149+
.unwrap_err()
1150+
.to_string()
1151+
.contains("Expected constraint details after CONSTRAINT <name>"));
11451152
}
11461153

11471154
#[test]

0 commit comments

Comments
 (0)