You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by lu...@apache.org on 2026/10/02 19:59:26 UTC
(datafusion-sqlparser-rs) 01/01: SQLite: Reject DML in nested queries
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a commit to branch upstream/sqlite-insert-value-and-nested-dml
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 86e0bf868395828942cf61315a8c4c4ff364964f
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Fri Oct 2 21:59:09 2026 +0200
SQLite: Reject DML in nested queries
---
src/dialect/mod.rs | 5 +++++
src/dialect/sqlite.rs | 5 +++++
src/parser/mod.rs | 17 +++++++++++------
tests/sqlparser_common.rs | 2 +-
tests/sqlparser_sqlite.rs | 25 +++++++++++++++++++++++++
5 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 2beb5655..4ad996e0 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1457,6 +1457,11 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the dialect accepts `INSERT`, `UPDATE`, `DELETE` or `MERGE` in a subquery, CTE or `INSERT` source.
+ fn supports_nested_dml_query(&self) -> bool {
+ true
+ }
+
/// Returns true if this dialect supports `SET` statements without an explicit
/// assignment operator such as `=`. For example: `SET SHOWPLAN_XML ON`.
fn supports_set_stmt_without_operator(&self) -> bool {
diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs
index 6f9ec0d2..e679a340 100644
--- a/src/dialect/sqlite.rs
+++ b/src/dialect/sqlite.rs
@@ -142,4 +142,9 @@ impl Dialect for SQLiteDialect {
fn supports_national_string_literal(&self) -> bool {
false
}
+
+ /// See <https://sqlite.org/lang_select.html>
+ fn supports_nested_dml_query(&self) -> bool {
+ false
+ }
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1a46c6e5..2c5a48b7 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -650,7 +650,7 @@ impl<'a> Parser<'a> {
}
Keyword::SELECT | Keyword::WITH | Keyword::VALUES | Keyword::FROM => {
self.prev_token();
- self.parse_query().map(Into::into)
+ self.parse_query_allowing_dml(true).map(Into::into)
}
Keyword::TRUNCATE => self.parse_truncate().map(Into::into),
Keyword::ATTACH => {
@@ -14728,8 +14728,13 @@ impl<'a> Parser<'a> {
/// preceded with some `WITH` CTE declarations and optionally followed
/// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't
/// expect the initial keyword to be already consumed
- #[cfg_attr(feature = "recursive-protection", recursive::recursive)]
pub fn parse_query(&mut self) -> Result<Box<Query>, ParserError> {
+ self.parse_query_allowing_dml(self.dialect.supports_nested_dml_query())
+ }
+
+ /// Parses a query whose body may be `INSERT`, `UPDATE`, `DELETE` or `MERGE` when `allow_dml` is set.
+ #[cfg_attr(feature = "recursive-protection", recursive::recursive)]
+ fn parse_query_allowing_dml(&mut self, allow_dml: bool) -> Result<Box<Query>, ParserError> {
let _guard = self.recursion_counter.try_decrease()?;
let with = if self.parse_keyword(Keyword::WITH) {
let with_token = self.get_current_token();
@@ -14741,7 +14746,7 @@ impl<'a> Parser<'a> {
} else {
None
};
- if self.parse_keyword(Keyword::INSERT) {
+ if allow_dml && self.parse_keyword(Keyword::INSERT) {
Ok(Query {
with,
body: self.parse_insert_setexpr_boxed(self.get_current_token().clone())?,
@@ -14755,7 +14760,7 @@ impl<'a> Parser<'a> {
pipe_operators: vec![],
}
.into())
- } else if self.parse_keyword(Keyword::UPDATE) {
+ } else if allow_dml && self.parse_keyword(Keyword::UPDATE) {
Ok(Query {
with,
body: self.parse_update_setexpr_boxed(self.get_current_token().clone())?,
@@ -14769,7 +14774,7 @@ impl<'a> Parser<'a> {
pipe_operators: vec![],
}
.into())
- } else if self.parse_keyword(Keyword::DELETE) {
+ } else if allow_dml && self.parse_keyword(Keyword::DELETE) {
Ok(Query {
with,
body: self.parse_delete_setexpr_boxed(self.get_current_token().clone())?,
@@ -14783,7 +14788,7 @@ impl<'a> Parser<'a> {
pipe_operators: vec![],
}
.into())
- } else if self.parse_keyword(Keyword::MERGE) {
+ } else if allow_dml && self.parse_keyword(Keyword::MERGE) {
Ok(Query {
with,
body: self.parse_merge_setexpr_boxed(self.get_current_token().clone())?,
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 8e08c657..73865a4d 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -10710,7 +10710,7 @@ VALUES (1, 'abc')";
#[test]
fn test_merge_in_cte() {
- verified_only_select(
+ all_dialects_where(|d| d.supports_nested_dml_query()).verified_only_select(
"WITH x AS (\
MERGE INTO t USING (VALUES (1)) ON 1 = 1 \
WHEN MATCHED THEN DELETE \
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index b8fad7e9..4753331d 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -1024,6 +1024,31 @@ fn parse_n_prefix_not_national_string() {
all_dialects_where(|d| d.supports_national_string_literal()).verified_stmt("SELECT N'hello'");
}
+#[test]
+fn parse_nested_dml_rejected() {
+ for sql in [
+ "INSERT INTO u INSERT INTO u SELECT 1",
+ "INSERT INTO u (a) INSERT INTO u SELECT 1",
+ "INSERT INTO u WITH x AS (SELECT 1) INSERT INTO u SELECT 1",
+ "SELECT * FROM (INSERT INTO u SELECT 1)",
+ "WITH x AS (INSERT INTO u SELECT 1) SELECT 1",
+ "WITH x AS (UPDATE u SET a = 1) SELECT 1",
+ "WITH x AS (DELETE FROM u) SELECT 1",
+ ] {
+ assert!(sqlite().parse_sql_statements(sql).is_err(), "{sql}");
+ }
+
+ let nested_dml = all_dialects_where(|d| d.supports_nested_dml_query());
+ nested_dml.verified_stmt("INSERT INTO u (a) INSERT INTO u SELECT 1");
+ nested_dml.verified_stmt("SELECT * FROM (INSERT INTO u SELECT 1)");
+ nested_dml.verified_stmt("WITH x AS (INSERT INTO u SELECT 1) SELECT 1");
+
+ sqlite().verified_stmt("WITH x AS (SELECT 1) INSERT INTO u SELECT * FROM x");
+ sqlite().verified_stmt("WITH x AS (SELECT 1) UPDATE u SET a = 2");
+ sqlite().verified_stmt("WITH x AS (SELECT 1) DELETE FROM u");
+ sqlite().verified_stmt("INSERT INTO u (a) WITH x AS (SELECT 1) SELECT * FROM x");
+}
+
fn sqlite() -> TestedDialects {
TestedDialects::new(vec![Box::new(SQLiteDialect {})])
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org