Skip to content

Commit 76574e4

Browse files
committed
MySQL: Allow optional constraint name after CONSTRAINT keyword
MySQL allows `CONSTRAINT CHECK (expr)` without a name - the database will auto-generate a constraint name. This is a MySQL extension; the SQL standard requires a name after the CONSTRAINT keyword. See [docs]. Previously, we required an identifier after CONSTRAINT, causing us to interpret CONSTRAINT as a column name in cases like: ```sql CREATE TABLE t (x INT, CONSTRAINT CHECK (x > 1)) ``` Now we check if the token after CONSTRAINT is a constraint type keyword (CHECK, PRIMARY, UNIQUE, FOREIGN) and treat the name as optional for dialects that support this. This is just MySQL as far as I know (and Generic), but we introduce a new `Dialect` flag even though it's a minor feature. We could remove the flag and allow the more permissive optional name syntax across dialects if desired. [docs]: https://dev.mysql.com/doc/refman/8.4/en/create-table.html
1 parent 802c7d3 commit 76574e4

5 files changed

Lines changed: 43 additions & 1 deletion

File tree

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,8 @@ impl Dialect for GenericDialect {
271271
fn supports_select_format(&self) -> bool {
272272
true
273273
}
274+
275+
fn supports_constraint_keyword_without_name(&self) -> bool {
276+
true
277+
}
274278
}

src/dialect/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,23 @@ pub trait Dialect: Debug + Any {
10681068
false
10691069
}
10701070

1071+
/// Returns true if the dialect supports the `CONSTRAINT` keyword without a name
1072+
/// in table constraint definitions.
1073+
///
1074+
/// Example:
1075+
/// ```sql
1076+
/// CREATE TABLE t (a INT, CONSTRAINT CHECK (a > 0))
1077+
/// ```
1078+
///
1079+
/// This is a MySQL extension; the SQL standard requires a name after `CONSTRAINT`.
1080+
/// When the name is omitted, the output normalizes to just the constraint type
1081+
/// without the `CONSTRAINT` keyword (e.g., `CHECK (a > 0)`).
1082+
///
1083+
/// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
1084+
fn supports_constraint_keyword_without_name(&self) -> bool {
1085+
false
1086+
}
1087+
10711088
/// Returns true if the specified keyword is reserved and cannot be
10721089
/// used as an identifier without special handling like quoting.
10731090
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {

src/dialect/mysql.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ impl Dialect for MySqlDialect {
182182
fn supports_binary_kw_as_cast(&self) -> bool {
183183
true
184184
}
185+
186+
/// See: <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
187+
fn supports_constraint_keyword_without_name(&self) -> bool {
188+
true
189+
}
185190
}
186191

187192
/// `LOCK TABLES`

src/parser/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9252,7 +9252,16 @@ impl<'a> Parser<'a> {
92529252
&mut self,
92539253
) -> Result<Option<TableConstraint>, ParserError> {
92549254
let name = if self.parse_keyword(Keyword::CONSTRAINT) {
9255-
Some(self.parse_identifier()?)
9255+
if self.dialect.supports_constraint_keyword_without_name()
9256+
&& (self.peek_keyword(Keyword::CHECK)
9257+
|| self.peek_keyword(Keyword::PRIMARY)
9258+
|| self.peek_keyword(Keyword::UNIQUE)
9259+
|| self.peek_keyword(Keyword::FOREIGN))
9260+
{
9261+
None
9262+
} else {
9263+
Some(self.parse_identifier()?)
9264+
}
92569265
} else {
92579266
None
92589267
};

tests/sqlparser_mysql.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3457,6 +3457,13 @@ fn parse_create_table_unallow_constraint_then_index() {
34573457
assert!(mysql_and_generic().parse_sql_statements(sql).is_ok());
34583458
}
34593459

3460+
#[test]
3461+
fn parse_create_table_constraint_check_without_name() {
3462+
let sql = "CREATE TABLE t (x INT, CONSTRAINT CHECK (x > 1))";
3463+
let normalized = "CREATE TABLE t (x INT, CHECK (x > 1))";
3464+
mysql_and_generic().one_statement_parses_to(sql, normalized);
3465+
}
3466+
34603467
#[test]
34613468
fn parse_create_table_with_fulltext_definition() {
34623469
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT (id))");

0 commit comments

Comments
 (0)