Skip to content

Commit 66505eb

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 23f5c7e commit 66505eb

File tree

2 files changed

+40
-27
lines changed

2 files changed

+40
-27
lines changed

src/parser.rs

+33-26
Original file line numberDiff line numberDiff line change
@@ -1228,10 +1228,21 @@ impl<'a> Parser<'a> {
12281228
};
12291229
let mut options = vec![];
12301230
loop {
1231-
match self.peek_token() {
1232-
Token::EOF | Token::Comma | Token::RParen | Token::SemiColon => break,
1233-
_ => options.push(self.parse_column_option_def()?),
1234-
}
1231+
if self.parse_keyword(Keyword::CONSTRAINT) {
1232+
let name = Some(self.parse_identifier()?);
1233+
if let Some(option) = self.parse_optional_column_option()? {
1234+
options.push(ColumnOptionDef { name, option });
1235+
} else {
1236+
return self.expected(
1237+
"constraint details after CONSTRAINT <name>",
1238+
self.peek_token(),
1239+
);
1240+
}
1241+
} else if let Some(option) = self.parse_optional_column_option()? {
1242+
options.push(ColumnOptionDef { name: None, option });
1243+
} else {
1244+
break;
1245+
};
12351246
}
12361247
Ok(ColumnDef {
12371248
name,
@@ -1241,23 +1252,17 @@ impl<'a> Parser<'a> {
12411252
})
12421253
}
12431254

1244-
pub fn parse_column_option_def(&mut self) -> Result<ColumnOptionDef, ParserError> {
1245-
let name = if self.parse_keyword(Keyword::CONSTRAINT) {
1246-
Some(self.parse_identifier()?)
1247-
} else {
1248-
None
1249-
};
1250-
1251-
let option = if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
1252-
ColumnOption::NotNull
1255+
pub fn parse_optional_column_option(&mut self) -> Result<Option<ColumnOption>, ParserError> {
1256+
if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
1257+
Ok(Some(ColumnOption::NotNull))
12531258
} else if self.parse_keyword(Keyword::NULL) {
1254-
ColumnOption::Null
1259+
Ok(Some(ColumnOption::Null))
12551260
} else if self.parse_keyword(Keyword::DEFAULT) {
1256-
ColumnOption::Default(self.parse_expr()?)
1261+
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
12571262
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
1258-
ColumnOption::Unique { is_primary: true }
1263+
Ok(Some(ColumnOption::Unique { is_primary: true }))
12591264
} else if self.parse_keyword(Keyword::UNIQUE) {
1260-
ColumnOption::Unique { is_primary: false }
1265+
Ok(Some(ColumnOption::Unique { is_primary: false }))
12611266
} else if self.parse_keyword(Keyword::REFERENCES) {
12621267
let foreign_table = self.parse_object_name()?;
12631268
// PostgreSQL allows omitting the column list and
@@ -1276,32 +1281,34 @@ impl<'a> Parser<'a> {
12761281
break;
12771282
}
12781283
}
1279-
ColumnOption::ForeignKey {
1284+
Ok(Some(ColumnOption::ForeignKey {
12801285
foreign_table,
12811286
referred_columns,
12821287
on_delete,
12831288
on_update,
1284-
}
1289+
}))
12851290
} else if self.parse_keyword(Keyword::CHECK) {
12861291
self.expect_token(&Token::LParen)?;
12871292
let expr = self.parse_expr()?;
12881293
self.expect_token(&Token::RParen)?;
1289-
ColumnOption::Check(expr)
1294+
Ok(Some(ColumnOption::Check(expr)))
12901295
} else if self.parse_keyword(Keyword::AUTO_INCREMENT)
12911296
&& dialect_of!(self is MySqlDialect | GenericDialect)
12921297
{
12931298
// Support AUTO_INCREMENT for MySQL
1294-
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTO_INCREMENT")])
1299+
Ok(Some(ColumnOption::DialectSpecific(vec![
1300+
Token::make_keyword("AUTO_INCREMENT"),
1301+
])))
12951302
} else if self.parse_keyword(Keyword::AUTOINCREMENT)
12961303
&& dialect_of!(self is SQLiteDialect | GenericDialect)
12971304
{
12981305
// Support AUTOINCREMENT for SQLite
1299-
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTOINCREMENT")])
1306+
Ok(Some(ColumnOption::DialectSpecific(vec![
1307+
Token::make_keyword("AUTOINCREMENT"),
1308+
])))
13001309
} else {
1301-
return self.expected("column option", self.peek_token());
1302-
};
1303-
1304-
Ok(ColumnOptionDef { name, option })
1310+
Ok(None)
1311+
}
13051312
}
13061313

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

tests/sqlparser_common.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,13 @@ fn parse_create_table() {
11421142
assert!(res
11431143
.unwrap_err()
11441144
.to_string()
1145-
.contains("Expected column option, found: GARBAGE"));
1145+
.contains("Expected \',\' or \')\' after column definition, found: GARBAGE"));
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>"));
11461152
}
11471153

11481154
#[test]

0 commit comments

Comments
 (0)