From a8e93b072865d8822174ef4795be48ee6c5dc723 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 28 Oct 2026 21:22:04 +0800 Subject: [PATCH] MySQL: Gate DELETE LIMIT behind supports_delete_limit() GenericDialect (and other dialects that do not support DELETE+LIMIT) previously accepted `DELETE ... LIMIT n`, so engines like DataFusion that use GenericDialect silently ignored the LIMIT. Add Dialect::supports_delete_limit() (default false), enable it for MySQL and SQLite, and only parse LIMIT in DELETE when the dialect opts in. Closes #2615. Signed-off-by: Vincent --- fuzz/fuzz_seeds/mysql_2615_delete_limit.sql | 1 + src/dialect/mod.rs | 11 +++++++++++ src/dialect/mysql.rs | 5 +++++ src/dialect/sqlite.rs | 5 +++++ src/parser/mod.rs | 2 +- tests/sqlparser_common.rs | 13 +++++++++++++ 6 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 fuzz/fuzz_seeds/mysql_2615_delete_limit.sql diff --git a/fuzz/fuzz_seeds/mysql_2615_delete_limit.sql b/fuzz/fuzz_seeds/mysql_2615_delete_limit.sql new file mode 100644 index 0000000000..20517468d7 --- /dev/null +++ b/fuzz/fuzz_seeds/mysql_2615_delete_limit.sql @@ -0,0 +1 @@ +DELETE FROM customers LIMIT 100 diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 8d92f0b57d..14c57fd445 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -699,6 +699,17 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports `LIMIT` in `DELETE` statements. + /// + /// ```sql + /// DELETE FROM foo LIMIT 10; + /// ``` + /// See + /// and + fn supports_delete_limit(&self) -> bool { + false + } + /// Returns true if the dialect supports an `EXCEPT` clause following a /// wildcard in a select list. /// diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index 95bd27168e..6bc722f3e7 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -195,6 +195,11 @@ impl Dialect for MySqlDialect { true } + /// See: + fn supports_delete_limit(&self) -> bool { + true + } + fn supports_data_type_signed_suffix(&self) -> bool { true } diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 6a3d3edfa9..260979b1bf 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -149,6 +149,11 @@ impl Dialect for SQLiteDialect { true } + /// See: + fn supports_delete_limit(&self) -> bool { + true + } + fn supports_asc_desc_in_column_definition(&self) -> bool { true } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 90296755ab..65e83905f6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15090,7 +15090,7 @@ impl<'a> Parser<'a> { } else { vec![] }; - let limit = if self.parse_keyword(Keyword::LIMIT) { + let limit = if self.dialect.supports_delete_limit() && self.parse_keyword(Keyword::LIMIT) { self.parse_limit()? } else { None diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 379e37a660..fbb8f4849a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -22744,3 +22744,16 @@ fn display_create_procedure_parameter_list_propagates_write_errors() { let mut writer = RejectingWriter(" ("); assert_eq!(write!(writer, "{statement}"), Err(fmt::Error)); } + +#[test] +fn parse_delete_limit_dialect_gated() { + let sql = "DELETE FROM customers LIMIT 100"; + all_dialects_where(|d| d.supports_delete_limit()).verified_stmt(sql); + + assert_eq!( + all_dialects_where(|d| !d.supports_delete_limit()).parse_sql_statements(sql), + Err(ParserError::ParserError( + "Expected: end of statement, found: LIMIT".to_string() + )) + ); +}