Skip to content

Commit 3dbdadc

Browse files
committed
support intersect
1 parent 365f7f7 commit 3dbdadc

3 files changed

Lines changed: 67 additions & 3 deletions

File tree

src/ast/query.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2695,7 +2695,16 @@ pub enum PipeOperator {
26952695
/// Syntax: `|> UNION [ALL|DISTINCT] (<query>), (<query>), ...`
26962696
///
26972697
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#union_pipe_operator>
2698-
Union {
2698+
Union {
2699+
set_quantifier: SetQuantifier,
2700+
queries: Vec<Box<Query>>,
2701+
},
2702+
/// Returns only the rows that are present in both the input table and the specified tables.
2703+
///
2704+
/// Syntax: `|> INTERSECT [DISTINCT] (<query>), (<query>), ...`
2705+
///
2706+
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#intersect_pipe_operator>
2707+
Intersect {
26992708
set_quantifier: SetQuantifier,
27002709
queries: Vec<Box<Query>>,
27012710
},
@@ -2757,12 +2766,37 @@ impl fmt::Display for PipeOperator {
27572766
PipeOperator::Rename { mappings } => {
27582767
write!(f, "RENAME {}", display_comma_separated(mappings))
27592768
}
2760-
PipeOperator::Union { set_quantifier, queries } => {
2769+
PipeOperator::Union {
2770+
set_quantifier,
2771+
queries,
2772+
} => {
27612773
write!(f, "UNION")?;
27622774
match set_quantifier {
27632775
SetQuantifier::All => write!(f, " ALL")?,
27642776
SetQuantifier::Distinct => write!(f, " DISTINCT")?,
2765-
SetQuantifier::None => {},
2777+
SetQuantifier::None => {}
2778+
_ => {
2779+
write!(f, " {}", set_quantifier)?;
2780+
}
2781+
}
2782+
write!(f, " ")?;
2783+
for (i, query) in queries.iter().enumerate() {
2784+
if i > 0 {
2785+
write!(f, ", ")?;
2786+
}
2787+
write!(f, "({})", query)?;
2788+
}
2789+
Ok(())
2790+
}
2791+
PipeOperator::Intersect {
2792+
set_quantifier,
2793+
queries,
2794+
} => {
2795+
write!(f, "INTERSECT")?;
2796+
match set_quantifier {
2797+
SetQuantifier::All => write!(f, " ALL")?,
2798+
SetQuantifier::Distinct => write!(f, " DISTINCT")?,
2799+
SetQuantifier::None => {}
27662800
_ => {
27672801
write!(f, " {}", set_quantifier)?;
27682802
}

src/parser/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11085,6 +11085,7 @@ impl<'a> Parser<'a> {
1108511085
Keyword::TABLESAMPLE,
1108611086
Keyword::RENAME,
1108711087
Keyword::UNION,
11088+
Keyword::INTERSECT,
1108811089
])?;
1108911090
match kw {
1109011091
Keyword::SELECT => {
@@ -11168,6 +11169,19 @@ impl<'a> Parser<'a> {
1116811169
})?;
1116911170
pipe_operators.push(PipeOperator::Union { set_quantifier, queries });
1117011171
}
11172+
Keyword::INTERSECT => {
11173+
// Reuse existing set quantifier parser for consistent modifier support
11174+
let set_quantifier = self.parse_set_quantifier(&Some(SetOperator::Intersect));
11175+
// BigQuery INTERSECT pipe operator requires parentheses around queries
11176+
// Parse comma-separated list of parenthesized queries
11177+
let queries = self.parse_comma_separated(|parser| {
11178+
parser.expect_token(&Token::LParen)?;
11179+
let query = parser.parse_query()?;
11180+
parser.expect_token(&Token::RParen)?;
11181+
Ok(query)
11182+
})?;
11183+
pipe_operators.push(PipeOperator::Intersect { set_quantifier, queries });
11184+
}
1117111185
unhandled => {
1117211186
return Err(ParserError::ParserError(format!(
1117311187
"`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}"

tests/sqlparser_common.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15219,6 +15219,22 @@ fn parse_pipeline_operator() {
1521915219
// union pipe operator with BY NAME and multiple queries
1522015220
dialects.verified_stmt("SELECT * FROM users |> UNION BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
1522115221

15222+
// intersect pipe operator (BigQuery does not support ALL modifier for INTERSECT)
15223+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT (SELECT * FROM admins)");
15224+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT (SELECT * FROM admins)");
15225+
15226+
// intersect pipe operator with BY NAME modifier
15227+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT BY NAME (SELECT * FROM admins)");
15228+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins)");
15229+
15230+
// intersect pipe operator with multiple queries
15231+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT (SELECT * FROM admins), (SELECT * FROM guests)");
15232+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT (SELECT * FROM admins), (SELECT * FROM guests)");
15233+
15234+
// intersect pipe operator with BY NAME and multiple queries
15235+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
15236+
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
15237+
1522215238
// many pipes
1522315239
dialects.verified_stmt(
1522415240
"SELECT * FROM CustomerOrders |> AGGREGATE SUM(cost) AS total_cost GROUP BY customer_id, state, item_type |> EXTEND COUNT(*) OVER (PARTITION BY customer_id) AS num_orders |> WHERE num_orders > 1 |> AGGREGATE AVG(total_cost) AS average GROUP BY state DESC, item_type ASC",

0 commit comments

Comments
 (0)