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