Skip to content

Commit affe850

Browse files
committed
support except
1 parent 3dbdadc commit affe850

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

src/ast/query.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,15 @@ pub enum PipeOperator {
27082708
set_quantifier: SetQuantifier,
27092709
queries: Vec<Box<Query>>,
27102710
},
2711+
/// Returns only the rows that are present in the input table but not in the specified tables.
2712+
///
2713+
/// Syntax: `|> EXCEPT DISTINCT (<query>), (<query>), ...`
2714+
///
2715+
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#except_pipe_operator>
2716+
Except {
2717+
set_quantifier: SetQuantifier,
2718+
queries: Vec<Box<Query>>,
2719+
},
27112720
}
27122721

27132722
impl fmt::Display for PipeOperator {
@@ -2810,6 +2819,28 @@ impl fmt::Display for PipeOperator {
28102819
}
28112820
Ok(())
28122821
}
2822+
PipeOperator::Except {
2823+
set_quantifier,
2824+
queries,
2825+
} => {
2826+
write!(f, "EXCEPT")?;
2827+
match set_quantifier {
2828+
SetQuantifier::All => write!(f, " ALL")?,
2829+
SetQuantifier::Distinct => write!(f, " DISTINCT")?,
2830+
SetQuantifier::None => {}
2831+
_ => {
2832+
write!(f, " {}", set_quantifier)?;
2833+
}
2834+
}
2835+
write!(f, " ")?;
2836+
for (i, query) in queries.iter().enumerate() {
2837+
if i > 0 {
2838+
write!(f, ", ")?;
2839+
}
2840+
write!(f, "({})", query)?;
2841+
}
2842+
Ok(())
2843+
}
28132844
}
28142845
}
28152846
}

src/parser/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11086,6 +11086,7 @@ impl<'a> Parser<'a> {
1108611086
Keyword::RENAME,
1108711087
Keyword::UNION,
1108811088
Keyword::INTERSECT,
11089+
Keyword::EXCEPT,
1108911090
])?;
1109011091
match kw {
1109111092
Keyword::SELECT => {
@@ -11182,6 +11183,27 @@ impl<'a> Parser<'a> {
1118211183
})?;
1118311184
pipe_operators.push(PipeOperator::Intersect { set_quantifier, queries });
1118411185
}
11186+
Keyword::EXCEPT => {
11187+
// BigQuery EXCEPT pipe operator requires DISTINCT modifier
11188+
let set_quantifier = if self.parse_keywords(&[Keyword::DISTINCT, Keyword::BY, Keyword::NAME]) {
11189+
SetQuantifier::DistinctByName
11190+
} else if self.parse_keyword(Keyword::DISTINCT) {
11191+
SetQuantifier::Distinct
11192+
} else {
11193+
return Err(ParserError::ParserError(
11194+
"EXCEPT pipe operator requires DISTINCT modifier".to_string()
11195+
));
11196+
};
11197+
// BigQuery EXCEPT pipe operator requires parentheses around queries
11198+
// Parse comma-separated list of parenthesized queries
11199+
let queries = self.parse_comma_separated(|parser| {
11200+
parser.expect_token(&Token::LParen)?;
11201+
let query = parser.parse_query()?;
11202+
parser.expect_token(&Token::RParen)?;
11203+
Ok(query)
11204+
})?;
11205+
pipe_operators.push(PipeOperator::Except { set_quantifier, queries });
11206+
}
1118511207
unhandled => {
1118611208
return Err(ParserError::ParserError(format!(
1118711209
"`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}"

tests/sqlparser_common.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15235,12 +15235,53 @@ fn parse_pipeline_operator() {
1523515235
dialects.verified_stmt("SELECT * FROM users |> INTERSECT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
1523615236
dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
1523715237

15238+
// except pipe operator (BigQuery requires DISTINCT modifier for EXCEPT)
15239+
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT (SELECT * FROM admins)");
15240+
15241+
// except pipe operator with BY NAME modifier
15242+
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT BY NAME (SELECT * FROM admins)");
15243+
15244+
// except pipe operator with multiple queries
15245+
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT (SELECT * FROM admins), (SELECT * FROM guests)");
15246+
15247+
// except pipe operator with BY NAME and multiple queries
15248+
dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)");
15249+
1523815250
// many pipes
1523915251
dialects.verified_stmt(
1524015252
"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",
1524115253
);
1524215254
}
1524315255

15256+
#[test]
15257+
fn parse_pipeline_operator_negative_tests() {
15258+
let dialects = all_dialects_where(|d| d.supports_pipe_operator());
15259+
15260+
// Test that plain EXCEPT without DISTINCT fails
15261+
assert_eq!(
15262+
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
15263+
dialects.parse_sql_statements("SELECT * FROM users |> EXCEPT (SELECT * FROM admins)").unwrap_err()
15264+
);
15265+
15266+
// Test that EXCEPT ALL fails
15267+
assert_eq!(
15268+
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
15269+
dialects.parse_sql_statements("SELECT * FROM users |> EXCEPT ALL (SELECT * FROM admins)").unwrap_err()
15270+
);
15271+
15272+
// Test that EXCEPT BY NAME without DISTINCT fails
15273+
assert_eq!(
15274+
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
15275+
dialects.parse_sql_statements("SELECT * FROM users |> EXCEPT BY NAME (SELECT * FROM admins)").unwrap_err()
15276+
);
15277+
15278+
// Test that EXCEPT ALL BY NAME fails
15279+
assert_eq!(
15280+
ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()),
15281+
dialects.parse_sql_statements("SELECT * FROM users |> EXCEPT ALL BY NAME (SELECT * FROM admins)").unwrap_err()
15282+
);
15283+
}
15284+
1524415285
#[test]
1524515286
fn parse_multiple_set_statements() -> Result<(), ParserError> {
1524615287
let dialects = all_dialects_where(|d| d.supports_comma_separated_set_assignments());

0 commit comments

Comments
 (0)