From d3fda446a991b314b6ee9deaff92d95902585490 Mon Sep 17 00:00:00 2001 From: MohamedAbdeen21 Date: Tue, 3 Jun 2025 19:52:39 +0100 Subject: [PATCH 1/3] MySQL: `[[NOT] ENFORCED]` in CHECK constraint Add support for MySQL's `[[NOT] ENFORCED]` option for CHECK constraints. docs: https://dev.mysql.com/doc/refman/8.4/en/create-table.html --- src/ast/ddl.rs | 18 +++++++++++++++--- src/ast/spans.rs | 8 +++++--- src/parser/mod.rs | 14 +++++++++++++- tests/sqlparser_mysql.rs | 7 +++++++ tests/sqlparser_postgres.rs | 5 +++++ 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index bbc15704e..e32a21796 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1029,10 +1029,13 @@ pub enum TableConstraint { on_update: Option, characteristics: Option, }, - /// `[ CONSTRAINT ] CHECK ()` + /// `[ CONSTRAINT ] CHECK () [[NOT] ENFORCED]` Check { name: Option, expr: Box, + /// MySQL-specific syntax + /// https://dev.mysql.com/doc/refman/8.4/en/create-table.html + enforced: Option, }, /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage /// is restricted to MySQL, as no other dialects that support this syntax were found. @@ -1162,8 +1165,17 @@ impl fmt::Display for TableConstraint { } Ok(()) } - TableConstraint::Check { name, expr } => { - write!(f, "{}CHECK ({})", display_constraint_name(name), expr) + TableConstraint::Check { + name, + expr, + enforced, + } => { + write!(f, "{}CHECK ({})", display_constraint_name(name), expr)?; + if let Some(b) = enforced { + write!(f, " {}", if *b { "ENFORCED" } else { "NOT ENFORCED" }) + } else { + Ok(()) + } } TableConstraint::Index { display_as_key, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index f957194ae..39d30df95 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -687,9 +687,11 @@ impl Spanned for TableConstraint { .chain(on_update.iter().map(|i| i.span())) .chain(characteristics.iter().map(|i| i.span())), ), - TableConstraint::Check { name, expr } => { - expr.span().union_opt(&name.as_ref().map(|i| i.span)) - } + TableConstraint::Check { + name, + expr, + enforced: _, + } => expr.span().union_opt(&name.as_ref().map(|i| i.span)), TableConstraint::Index { display_as_key: _, name, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f8c307dc9..d96debdb9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8139,7 +8139,19 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let expr = Box::new(self.parse_expr()?); self.expect_token(&Token::RParen)?; - Ok(Some(TableConstraint::Check { name, expr })) + + let enforced = match dialect_of!(self is GenericDialect | MySqlDialect) { + true if self.parse_keywords(&[Keyword::NOT, Keyword::ENFORCED]) => Some(false), + true if self.parse_keyword(Keyword::ENFORCED) => Some(true), + // not MySQL, or is MySQL but not specified + _ => None, + }; + + Ok(Some(TableConstraint::Check { + name, + expr, + enforced, + })) } Token::Word(w) if (w.keyword == Keyword::INDEX || w.keyword == Keyword::KEY) diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index b1b7d539e..c6f60037e 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -4025,3 +4025,10 @@ fn parse_drop_index() { _ => unreachable!(), } } + +#[test] +fn check_enforced() { + mysql().verified_stmt( + "CREATE TABLE t (a INT, b INT, c INT, CHECK (a > 0) NOT ENFORCED, CHECK (b > 0) ENFORCED, CHECK (c > 0))", + ); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index c50f066a6..6f0ba9c69 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5378,6 +5378,7 @@ fn parse_create_domain() { op: BinaryOperator::Gt, right: Box::new(Expr::Value(test_utils::number("0").into())), }), + enforced: None, }], }); @@ -5396,6 +5397,7 @@ fn parse_create_domain() { op: BinaryOperator::Gt, right: Box::new(Expr::Value(test_utils::number("0").into())), }), + enforced: None, }], }); @@ -5414,6 +5416,7 @@ fn parse_create_domain() { op: BinaryOperator::Gt, right: Box::new(Expr::Value(test_utils::number("0").into())), }), + enforced: None, }], }); @@ -5432,6 +5435,7 @@ fn parse_create_domain() { op: BinaryOperator::Gt, right: Box::new(Expr::Value(test_utils::number("0").into())), }), + enforced: None, }], }); @@ -5450,6 +5454,7 @@ fn parse_create_domain() { op: BinaryOperator::Gt, right: Box::new(Expr::Value(test_utils::number("0").into())), }), + enforced: None, }], }); From 05591ed3c9863996601eeb8eee330c99b57ef2ec Mon Sep 17 00:00:00 2001 From: MohamedAbdeen21 Date: Tue, 3 Jun 2025 20:20:03 +0100 Subject: [PATCH 2/3] properly link the docs --- src/ast/ddl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index e32a21796..b0a3708c1 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1034,7 +1034,7 @@ pub enum TableConstraint { name: Option, expr: Box, /// MySQL-specific syntax - /// https://dev.mysql.com/doc/refman/8.4/en/create-table.html + /// enforced: Option, }, /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage From 20646fc0f5d139f70648a818a06f4a7e08d8f5ae Mon Sep 17 00:00:00 2001 From: MohamedAbdeen21 Date: Sat, 7 Jun 2025 01:49:38 +0100 Subject: [PATCH 3/3] Make [[NOT] ENFORCED] common between dialects --- src/parser/mod.rs | 11 ++++++----- tests/sqlparser_common.rs | 7 +++++++ tests/sqlparser_mysql.rs | 7 ------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d96debdb9..9cef22edb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8140,11 +8140,12 @@ impl<'a> Parser<'a> { let expr = Box::new(self.parse_expr()?); self.expect_token(&Token::RParen)?; - let enforced = match dialect_of!(self is GenericDialect | MySqlDialect) { - true if self.parse_keywords(&[Keyword::NOT, Keyword::ENFORCED]) => Some(false), - true if self.parse_keyword(Keyword::ENFORCED) => Some(true), - // not MySQL, or is MySQL but not specified - _ => None, + let enforced = if self.parse_keyword(Keyword::ENFORCED) { + Some(true) + } else if self.parse_keywords(&[Keyword::NOT, Keyword::ENFORCED]) { + Some(false) + } else { + None }; Ok(Some(TableConstraint::Check { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 5b96dcd7f..399fdb3dc 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15336,3 +15336,10 @@ fn parse_truncate_only() { truncate ); } + +#[test] +fn check_enforced() { + all_dialects().verified_stmt( + "CREATE TABLE t (a INT, b INT, c INT, CHECK (a > 0) NOT ENFORCED, CHECK (b > 0) ENFORCED, CHECK (c > 0))", + ); +} diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index c6f60037e..b1b7d539e 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -4025,10 +4025,3 @@ fn parse_drop_index() { _ => unreachable!(), } } - -#[test] -fn check_enforced() { - mysql().verified_stmt( - "CREATE TABLE t (a INT, b INT, c INT, CHECK (a > 0) NOT ENFORCED, CHECK (b > 0) ENFORCED, CHECK (c > 0))", - ); -}