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/18 10:17:35 UTC

(datafusion-sqlparser-rs) 01/01: Merge the `IN` expression variants into `Expr::In`

This is an automated email from the ASF dual-hosted git repository.

LucaCappelletti94 pushed a commit to branch in-expr-refactor
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 653d1668fade74d2302f395e1fd492ee76e89c7b
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Sun Oct 18 12:17:23 2026 +0200

    Merge the `IN` expression variants into `Expr::In`
---
 src/ast/mod.rs                | 84 +++++++++++++++++--------------------------
 src/ast/spans.rs              | 31 +++++++---------
 src/parser/mod.rs             | 48 +++++++++++--------------
 tests/sqlparser_clickhouse.rs | 14 ++++----
 tests/sqlparser_common.rs     | 52 ++++++++++++++-------------
 tests/sqlparser_sqlite.rs     |  6 +++-
 6 files changed, 105 insertions(+), 130 deletions(-)

diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index f585121ad..8a1b7e305 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -871,6 +871,30 @@ impl fmt::Display for CaseWhen {
     }
 }
 
+/// The right-hand side of an `IN` predicate, see [`Expr::In`].
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum InTarget {
+    /// `(val1, val2, ...)`
+    List(Vec<Expr>),
+    /// `(SELECT ...)`
+    Subquery(Box<Query>),
+    /// `UNNEST(array_expression)`, see
+    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/operators#in_operators)
+    Unnest(Box<Expr>),
+}
+
+impl fmt::Display for InTarget {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            InTarget::List(list) => write!(f, "({})", display_comma_separated(list)),
+            InTarget::Subquery(subquery) => write!(f, "({subquery})"),
+            InTarget::Unnest(array_expr) => write!(f, "UNNEST({array_expr})"),
+        }
+    }
+}
+
 /// An SQL expression of any type.
 ///
 /// # Semantics / Type Checking
@@ -975,37 +999,17 @@ pub enum Expr {
         /// `true` when `NOT` is present.
         negated: bool,
     },
-    /// `[ GLOBAL ] [ NOT ] IN (val1, val2, ...)`
-    InList {
+    /// `<expr> [ GLOBAL ] [ NOT ] IN <target>`
+    In {
         /// Left-hand expression to test for membership.
         expr: Box<Expr>,
-        /// Literal list of expressions to check against.
-        list: Vec<Expr>,
+        /// What `expr` is tested against.
+        target: InTarget,
         /// `true` when the `NOT` modifier is present.
         negated: bool,
         /// `true` when the ClickHouse `GLOBAL` modifier is present.
         global: bool,
     },
-    /// `[ GLOBAL ] [ NOT ] IN (SELECT ...)`
-    InSubquery {
-        /// Left-hand expression to test for membership.
-        expr: Box<Expr>,
-        /// The subquery providing the candidate values.
-        subquery: Box<Query>,
-        /// `true` when the `NOT` modifier is present.
-        negated: bool,
-        /// `true` when the ClickHouse `GLOBAL` modifier is present.
-        global: bool,
-    },
-    /// `[ NOT ] IN UNNEST(array_expression)`
-    InUnnest {
-        /// Left-hand expression to test for membership.
-        expr: Box<Expr>,
-        /// Array expression being unnested.
-        array_expr: Box<Expr>,
-        /// `true` when the `NOT` modifier is present.
-        negated: bool,
-    },
     /// `<expr> [ NOT ] BETWEEN <low> AND <high>`
     Between {
         /// Expression being compared.
@@ -1801,42 +1805,18 @@ impl fmt::Display for Expr {
                 }
                 Ok(())
             }
-            Expr::InList {
+            Expr::In {
                 expr,
-                list,
-                negated,
-                global,
-            } => write!(
-                f,
-                "{} {}{}IN ({})",
-                expr,
-                if *global { "GLOBAL " } else { "" },
-                if *negated { "NOT " } else { "" },
-                display_comma_separated(list)
-            ),
-            Expr::InSubquery {
-                expr,
-                subquery,
+                target,
                 negated,
                 global,
             } => write!(
                 f,
-                "{} {}{}IN ({})",
+                "{} {}{}IN {}",
                 expr,
                 if *global { "GLOBAL " } else { "" },
                 if *negated { "NOT " } else { "" },
-                subquery
-            ),
-            Expr::InUnnest {
-                expr,
-                array_expr,
-                negated,
-            } => write!(
-                f,
-                "{} {}IN UNNEST({})",
-                expr,
-                if *negated { "NOT " } else { "" },
-                array_expr
+                target
             ),
             Expr::Between {
                 expr,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index a17eba20c..8e7084fb1 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -36,9 +36,9 @@ use super::{
     CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeConstraintElement,
     ExcludeSelectItem, Expr, ExprWithAlias, Fetch, ForValues, FromTable, Function, FunctionArg,
     FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr,
-    HavingBound, IfStatement, IlikeSelectItem, IndexColumn, Insert, Interpolate, InterpolateExpr,
-    Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
-    MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
+    HavingBound, IfStatement, IlikeSelectItem, InTarget, IndexColumn, Insert, Interpolate,
+    InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
+    LimitClause, MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
     MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, NamedParenthesizedList,
     NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
     OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, Parens, Partition,
@@ -1525,25 +1525,18 @@ impl Spanned for Expr {
             } => expr.span(),
             Expr::IsDistinctFrom(lhs, rhs) => lhs.span().union(&rhs.span()),
             Expr::IsNotDistinctFrom(lhs, rhs) => lhs.span().union(&rhs.span()),
-            Expr::InList {
+            Expr::In {
                 expr,
-                list,
+                target,
                 negated: _,
                 global: _,
-            } => union_spans(
-                core::iter::once(expr.span()).chain(list.iter().map(|item| item.span())),
-            ),
-            Expr::InSubquery {
-                expr,
-                subquery,
-                negated: _,
-                global: _,
-            } => expr.span().union(&subquery.span()),
-            Expr::InUnnest {
-                expr,
-                array_expr,
-                negated: _,
-            } => expr.span().union(&array_expr.span()),
+            } => match target {
+                InTarget::List(list) => union_spans(
+                    core::iter::once(expr.span()).chain(list.iter().map(|item| item.span())),
+                ),
+                InTarget::Subquery(subquery) => expr.span().union(&subquery.span()),
+                InTarget::Unnest(array_expr) => expr.span().union(&array_expr.span()),
+            },
             Expr::Between {
                 expr,
                 negated: _,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 39a3a754f..fd781d95b 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -4476,26 +4476,30 @@ impl<'a> Parser<'a> {
     ) -> Result<Expr, ParserError> {
         // BigQuery allows `IN UNNEST(array_expression)`
         // https://cloud.google.com/bigquery/docs/reference/standard-sql/operators#in_operators
-        if !global && self.parse_keyword(Keyword::UNNEST) {
+        let target = if !global && self.parse_keyword(Keyword::UNNEST) {
             self.expect_token(&Token::LParen)?;
             let array_expr = self.parse_expr()?;
             self.expect_token(&Token::RParen)?;
-            return Ok(Expr::InUnnest {
-                expr: Box::new(expr),
-                array_expr: Box::new(array_expr),
-                negated,
-            });
-        }
-        if self.dialect.supports_in_unparenthesized_expr()
+            InTarget::Unnest(Box::new(array_expr))
+        } else if self.dialect.supports_in_unparenthesized_expr()
             && self.peek_token_ref().token != Token::LParen
         {
-            return Ok(Expr::InList {
-                expr: Box::new(expr),
-                list: vec![self.parse_subexpr(self.dialect.prec_value(Precedence::Eq))?],
-                negated,
-                global,
-            });
-        }
+            InTarget::List(vec![
+                self.parse_subexpr(self.dialect.prec_value(Precedence::Eq))?
+            ])
+        } else {
+            self.parse_in_parenthesized_target()?
+        };
+        Ok(Expr::In {
+            expr: Box::new(expr),
+            target,
+            negated,
+            global,
+        })
+    }
+
+    /// Parses the parenthesized subquery or value list of an `IN` predicate.
+    fn parse_in_parenthesized_target(&mut self) -> Result<InTarget, ParserError> {
         self.expect_token(&Token::LParen)?;
         // 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)`.
@@ -4508,12 +4512,7 @@ impl<'a> Parser<'a> {
                 let mut missing_rparen = None;
                 if let Some(subquery) = self.maybe_parse(|p| p.parse_query())? {
                     if self.consume_token(&Token::RParen) {
-                        return Ok(Expr::InSubquery {
-                            expr: Box::new(expr),
-                            subquery,
-                            negated,
-                            global,
-                        });
+                        return Ok(InTarget::Subquery(subquery));
                     }
                     missing_rparen = Some(self.index);
                     self.index = start;
@@ -4529,12 +4528,7 @@ impl<'a> Parser<'a> {
             self.parse_comma_separated(Parser::parse_expr)
         };
         match list.and_then(|list| self.expect_token(&Token::RParen).map(|_| list)) {
-            Ok(list) => Ok(Expr::InList {
-                expr: Box::new(expr),
-                list,
-                negated,
-                global,
-            }),
+            Ok(list) => Ok(InTarget::List(list)),
             Err(ParserError::RecursionLimitExceeded) => Err(ParserError::RecursionLimitExceeded),
             Err(e) => match missing_rparen {
                 Some(index) if self.index <= index => {
diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs
index 189300d87..5e5ab7e00 100644
--- a/tests/sqlparser_clickhouse.rs
+++ b/tests/sqlparser_clickhouse.rs
@@ -2120,12 +2120,12 @@ fn parse_global_in() {
     let select = clickhouse().verified_only_select("SELECT * FROM t WHERE x GLOBAL IN (1, 2)");
     assert_eq!(
         select.selection.unwrap(),
-        Expr::InList {
+        Expr::In {
             expr: Box::new(Identifier(Ident::new("x"))),
-            list: vec![
+            target: InTarget::List(vec![
                 Expr::Value(number("1").with_empty_span()),
                 Expr::Value(number("2").with_empty_span()),
-            ],
+            ]),
             negated: false,
             global: true,
         }
@@ -2135,9 +2135,9 @@ fn parse_global_in() {
         .verified_only_select("SELECT * FROM t WHERE x GLOBAL NOT IN (SELECT y FROM t2)");
     assert_eq!(
         select.selection.unwrap(),
-        Expr::InSubquery {
+        Expr::In {
             expr: Box::new(Identifier(Ident::new("x"))),
-            subquery: Box::new(clickhouse().verified_query("SELECT y FROM t2")),
+            target: InTarget::Subquery(Box::new(clickhouse().verified_query("SELECT y FROM t2"))),
             negated: true,
             global: true,
         }
@@ -2222,7 +2222,7 @@ fn global_in_bare_table_binds_before_and() {
             left,
             op: BinaryOperator::And,
             ..
-        } if matches!(*left, Expr::InList { global: true, .. })
+        } if matches!(*left, Expr::In { target: InTarget::List(_), global: true, .. })
     ));
 }
 
@@ -2235,7 +2235,7 @@ fn global_in_bare_table_binds_before_equality() {
             left,
             op: BinaryOperator::Eq,
             ..
-        } if matches!(*left, Expr::InList { global: true, .. })
+        } if matches!(*left, Expr::In { target: InTarget::List(_), global: true, .. })
     ));
 }
 
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 1959bcd34..30b4dafbe 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -2265,11 +2265,11 @@ fn parse_not_precedence() {
         verified_expr(sql),
         Expr::UnaryOp {
             op: UnaryOperator::Not,
-            expr: Box::new(Expr::InList {
+            expr: Box::new(Expr::In {
                 expr: Box::new(Expr::Identifier("a".into())),
-                list: vec![Expr::Value(
+                target: InTarget::List(vec![Expr::Value(
                     (Value::SingleQuotedString("a".into())).with_empty_span()
-                )],
+                )]),
                 negated: true,
                 global: false,
             }),
@@ -2547,12 +2547,12 @@ fn parse_in_list() {
         );
         let select = verified_only_select(sql);
         assert_eq!(
-            Expr::InList {
+            Expr::In {
                 expr: Box::new(Expr::Identifier(Ident::new("segment"))),
-                list: vec![
+                target: InTarget::List(vec![
                     Expr::Value((Value::SingleQuotedString("HIGH".to_string())).with_empty_span()),
                     Expr::Value((Value::SingleQuotedString("MED".to_string())).with_empty_span()),
-                ],
+                ]),
                 negated,
                 global: false,
             },
@@ -2568,9 +2568,9 @@ fn parse_in_subquery() {
     let sql = "SELECT * FROM customers WHERE segment IN (SELECT segm FROM bar)";
     let select = verified_only_select(sql);
     assert_eq!(
-        Expr::InSubquery {
+        Expr::In {
             expr: Box::new(Expr::Identifier(Ident::new("segment"))),
-            subquery: Box::new(verified_query("SELECT segm FROM bar")),
+            target: InTarget::Subquery(Box::new(verified_query("SELECT segm FROM bar"))),
             negated: false,
             global: false,
         },
@@ -2583,11 +2583,11 @@ fn parse_in_union() {
     let sql = "SELECT * FROM customers WHERE segment IN ((SELECT segm FROM bar) UNION (SELECT segm FROM bar2))";
     let select = verified_only_select(sql);
     assert_eq!(
-        Expr::InSubquery {
+        Expr::In {
             expr: Box::new(Expr::Identifier(Ident::new("segment"))),
-            subquery: Box::new(verified_query(
+            target: InTarget::Subquery(Box::new(verified_query(
                 "(SELECT segm FROM bar) UNION (SELECT segm FROM bar2)"
-            )),
+            ))),
             negated: false,
             global: false,
         },
@@ -2604,10 +2604,11 @@ fn parse_in_unnest() {
         );
         let select = verified_only_select(sql);
         assert_eq!(
-            Expr::InUnnest {
+            Expr::In {
                 expr: Box::new(Expr::Identifier(Ident::new("segment"))),
-                array_expr: Box::new(verified_expr("expr")),
+                target: InTarget::Unnest(Box::new(verified_expr("expr"))),
                 negated,
+                global: false,
             },
             select.selection.unwrap()
         );
@@ -20442,9 +20443,9 @@ fn parse_in_list_leading_subquery() {
 
     let select = verified_only_select("SELECT * FROM t WHERE x IN ((SELECT a FROM u), 1)");
     assert_eq!(
-        Expr::InList {
+        Expr::In {
             expr: x(),
-            list: vec![subquery(), Expr::value(number("1"))],
+            target: InTarget::List(vec![subquery(), Expr::value(number("1"))]),
             negated: false,
             global: false,
         },
@@ -20453,9 +20454,12 @@ fn parse_in_list_leading_subquery() {
 
     let select = verified_only_select("SELECT * FROM t WHERE x NOT IN (((SELECT a FROM u)), 1)");
     assert_eq!(
-        Expr::InList {
+        Expr::In {
             expr: x(),
-            list: vec![Expr::Nested(Box::new(subquery())), Expr::value(number("1"))],
+            target: InTarget::List(vec![
+                Expr::Nested(Box::new(subquery())),
+                Expr::value(number("1"))
+            ]),
             negated: true,
             global: false,
         },
@@ -20464,16 +20468,16 @@ fn parse_in_list_leading_subquery() {
 
     let select = verified_only_select("SELECT * FROM t WHERE x IN ((SELECT a FROM u) + 1, 2)");
     assert_eq!(
-        Expr::InList {
+        Expr::In {
             expr: x(),
-            list: vec![
+            target: InTarget::List(vec![
                 Expr::BinaryOp {
                     left: Box::new(subquery()),
                     op: BinaryOperator::Plus,
                     right: Box::new(Expr::value(number("1"))),
                 },
                 Expr::value(number("2"))
-            ],
+            ]),
             negated: false,
             global: false,
         },
@@ -20483,16 +20487,16 @@ fn parse_in_list_leading_subquery() {
     let select = pg_and_generic()
         .verified_only_select("SELECT * FROM t WHERE x IN ((SELECT a FROM u)[1], 2)");
     assert_matches!(
-        select.selection,
-        Some(Expr::InList { ref list, .. })
+        select.selection.as_ref(),
+        Some(Expr::In { target: InTarget::List(list), .. })
             if matches!(&list[0], Expr::CompoundFieldAccess { root, .. } if **root == subquery())
     );
 
     let select = verified_only_select("SELECT * FROM t WHERE x IN ((SELECT a FROM u))");
     assert_eq!(
-        Expr::InSubquery {
+        Expr::In {
             expr: x(),
-            subquery: Box::new(verified_query("(SELECT a FROM u)")),
+            target: InTarget::Subquery(Box::new(verified_query("(SELECT a FROM u)"))),
             negated: false,
             global: false,
         },
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index 2e60c75ef..3e3104947 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -524,7 +524,11 @@ fn parse_update_tuple_row_values() {
 fn parse_where_in_empty_list() {
     let sql = "SELECT * FROM t1 WHERE a IN ()";
     let select = sqlite().verified_only_select(sql);
-    if let Expr::InList { list, .. } = select.selection.as_ref().unwrap() {
+    if let Expr::In {
+        target: InTarget::List(list),
+        ..
+    } = select.selection.as_ref().unwrap()
+    {
         assert_eq!(list.len(), 0);
     } else {
         unreachable!()


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org