You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/10/21 06:27:24 UTC
(datafusion-sqlparser-rs) branch gh-readonly-queue/main/pr-2679-b79a5ab19c35a3f3971b4a3bbd6c4d86a22ba039 created (now eda940d40)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-2679-b79a5ab19c35a3f3971b4a3bbd6c4d86a22ba039
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
at eda940d40 Refactoring: Merge the `IN` expression variants into `Expr::In` (#2679)
This branch includes the following new commits:
new eda940d40 Refactoring: Merge the `IN` expression variants into `Expr::In` (#2679)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org
(datafusion-sqlparser-rs) 01/01: Refactoring: Merge the `IN` expression variants into `Expr::In` (#2679)
Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-2679-b79a5ab19c35a3f3971b4a3bbd6c4d86a22ba039
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit eda940d40c301a5fcf96ae8f22d1bfd94c49d5bd
Author: Luca Cappelletti <ca...@gmail.com>
AuthorDate: Wed Oct 21 06:27:07 2026 +0000
Refactoring: Merge the `IN` expression variants into `Expr::In` (#2679)
---
src/ast/mod.rs | 84 +++++++++++++++++--------------------------
src/ast/spans.rs | 47 ++++++++++++++----------
src/parser/mod.rs | 48 +++++++++++--------------
tests/sqlparser_clickhouse.rs | 14 ++++----
tests/sqlparser_common.rs | 52 ++++++++++++++-------------
tests/sqlparser_sqlite.rs | 6 +++-
6 files changed, 121 insertions(+), 130 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index f38ff0f17..e9b7cdbd1 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 eab9f71a3..8267c7179 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: _,
@@ -3249,4 +3242,20 @@ SETTINGS update_parallel_mode = 'auto'"#;
stmt => panic!("expected ALTER TABLE; got {stmt:?}"),
}
}
+
+ #[test]
+ fn test_in_expression_spans() {
+ for (sql, expected) in [
+ (" needle IN (first, last)", "needle IN (first, last"),
+ (
+ " needle IN (SELECT last FROM items)",
+ "needle IN (SELECT last FROM items",
+ ),
+ (" needle IN UNNEST(items)", "needle IN UNNEST(items"),
+ ] {
+ let mut test = SpanTest::new(&GenericDialect, sql);
+ let expr = test.0.parse_expr().unwrap();
+ assert_eq!(test.get_source(expr.span()), expected, "{sql}");
+ }
+ }
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 867816ba3..3c9a4a616 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -4503,26 +4503,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)`.
@@ -4535,12 +4539,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;
@@ -4556,12 +4555,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 bd216f940..45b7649d1 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 13f5968b4..3800c9595 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -2264,11 +2264,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,
}),
@@ -2546,12 +2546,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,
},
@@ -2567,9 +2567,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,
},
@@ -2582,11 +2582,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,
},
@@ -2603,10 +2603,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()
);
@@ -20599,9 +20600,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,
},
@@ -20610,9 +20611,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,
},
@@ -20621,16 +20625,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,
},
@@ -20640,16 +20644,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 9ac16f904..1752e976c 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