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 08:58:07 UTC
(datafusion-sqlparser-rs) 01/01: Report the furthest IN subquery error when the list fails
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a commit to branch upstream/in-subquery-error-diagnostics
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 35e2178305f2a7c917faf9766e0241552e0389b4
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Fri Oct 2 10:57:57 2026 +0200
Report the furthest IN subquery error when the list fails
---
src/parser/mod.rs | 56 ++++++++++++++++++++++++++++++-----------------
tests/sqlparser_common.rs | 32 +++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 20 deletions(-)
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 185b3344..7bc9b879 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -357,9 +357,12 @@ pub struct Parser<'a> {
failed_derived_table_factor_positions: BTreeSet<usize>,
/// Cached failures from the speculative subquery arm of [`Parser::parse_in`],
/// which `IN` lists nested in a leading subquery would otherwise retry at
- /// every level, taking 2^N time. Maps the arm's start to where a parsed
- /// query lacked its `)`.
- failed_in_subquery_positions: BTreeMap<usize, Option<usize>>,
+ /// every level, taking 2^N time. Maps the arm's start to the index where it
+ /// failed.
+ failed_in_subquery_positions: BTreeMap<usize, usize>,
+ /// The furthest-reaching failure of that arm, the one error a cached retry
+ /// can replay without the cache holding strings.
+ furthest_in_subquery_error: Option<(usize, ParserError)>,
}
/// Copy marker for a [`ParserError`] cached by the `parse_prefix` failure
@@ -407,6 +410,7 @@ impl<'a> Parser<'a> {
failed_reserved_word_prefix_positions: BTreeMap::new(),
failed_derived_table_factor_positions: BTreeSet::new(),
failed_in_subquery_positions: BTreeMap::new(),
+ furthest_in_subquery_error: None,
}
}
@@ -476,6 +480,7 @@ impl<'a> Parser<'a> {
self.failed_reserved_word_prefix_positions.clear();
self.failed_derived_table_factor_positions.clear();
self.failed_in_subquery_positions.clear();
+ self.furthest_in_subquery_error = None;
self
}
@@ -4468,26 +4473,40 @@ impl<'a> Parser<'a> {
// A leading query is the whole subquery only when `)` follows it,
// otherwise it is the first item of a list, as in `IN ((SELECT 1), 2)`.
let start = self.index;
- // Where a parsed query lacked its `)`. Reported if the list fails no
- // further in, since that error would sit inside the query.
- let missing_rparen = match self.failed_in_subquery_positions.get(&start) {
- Some(&cached) => cached,
+ // The arm's error is reported if the list fails at an earlier token,
+ // since the list error would then sit inside the query.
+ let query_failed_at = match self.failed_in_subquery_positions.get(&start) {
+ Some(&at) => at,
None => {
- let mut missing_rparen = None;
- if let Some(subquery) = self.maybe_parse(|p| p.parse_query())? {
- if self.consume_token(&Token::RParen) {
+ let query = self.parse_query().and_then(|subquery| {
+ self.expect_token(&Token::RParen)?;
+ Ok(subquery)
+ });
+ match query {
+ Ok(subquery) => {
return Ok(Expr::InSubquery {
expr: Box::new(expr),
subquery,
negated,
});
}
- missing_rparen = Some(self.index);
- self.index = start;
+ Err(ParserError::RecursionLimitExceeded) => {
+ return Err(ParserError::RecursionLimitExceeded);
+ }
+ Err(e) => {
+ let at = self.index;
+ self.index = start;
+ self.failed_in_subquery_positions.insert(start, at);
+ if self
+ .furthest_in_subquery_error
+ .as_ref()
+ .is_none_or(|(furthest, _)| *furthest <= at)
+ {
+ self.furthest_in_subquery_error = Some((at, e));
+ }
+ at
+ }
}
- self.failed_in_subquery_positions
- .insert(start, missing_rparen);
- missing_rparen
}
};
let list = if self.dialect.supports_in_empty_list() {
@@ -4502,11 +4521,8 @@ impl<'a> Parser<'a> {
negated,
}),
Err(ParserError::RecursionLimitExceeded) => Err(ParserError::RecursionLimitExceeded),
- Err(e) => match missing_rparen {
- Some(index) if self.index <= index => {
- self.index = index;
- self.expected_ref(")", self.peek_token_ref())
- }
+ Err(e) => match &self.furthest_in_subquery_error {
+ Some((_, furthest)) if self.index < query_failed_at => Err(furthest.clone()),
_ => Err(e),
},
}
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 3f31c547..bdffd83e 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -20396,3 +20396,35 @@ fn parse_bang_not_renders_apart_from_operand() {
dialects.verified_stmt("SET eaac_cion = ! !o");
dialects.one_statement_parses_to("SET eaac_cion = ! ! o", "SET eaac_cion = ! !o");
}
+
+#[test]
+fn parse_in_reports_furthest_failure() {
+ for (sql, expected) in [
+ (
+ "SELECT * FROM t WHERE x IN (SELECT y FROM u WHERE",
+ "Expected: an expression, found: EOF",
+ ),
+ (
+ "SELECT * FROM t WHERE x IN (SELECT y FROM u ORDER BY",
+ "Expected: an expression, found: EOF",
+ ),
+ (
+ "SELECT * FROM t WHERE x IN (SELECT 1 FROM u WHERE y IN (SELECT 2 FROM v",
+ "Expected: ), found: EOF",
+ ),
+ // The inner `IN` is read again for the outer list after its subquery
+ // arm was cached as failed.
+ (
+ "SELECT * FROM t WHERE x IN ((SELECT 1 FROM u WHERE y IN (SELECT 2 FROM v WHERE",
+ "Expected: an expression, found: EOF",
+ ),
+ // The list gets further than the query.
+ ("SELECT * FROM t WHERE x IN (a b", "Expected: ), found: b"),
+ ] {
+ assert_eq!(
+ ParserError::ParserError(expected.to_string()),
+ parse_sql_statements(sql).unwrap_err(),
+ "{sql}"
+ );
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org