diff --git a/src/ast/mod.rs b/src/ast/mod.rs index e494553ce..20ba194ef 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8265,6 +8265,12 @@ pub enum FunctionArgumentClause { /// /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#first_value IgnoreOrRespectNulls(NullTreatment), + /// The inline `WHERE` filter clause on a GoogleSQL aggregate, e.g. + /// `COUNT(* WHERE cond)` / `SUM(x WHERE cond)` / `ARRAY_AGG(x WHERE cond ORDER BY ..)`. + /// Equivalent to the standard `AGG(x) FILTER (WHERE cond)`. + /// + /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate_functions#grouping_and_filtering + Where(Expr), /// Specifies the the ordering for some ordered set aggregates, e.g. `ARRAY_AGG` on [BigQuery]. /// /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate_functions#array_agg @@ -8306,6 +8312,7 @@ impl fmt::Display for FunctionArgumentClause { FunctionArgumentClause::IgnoreOrRespectNulls(null_treatment) => { write!(f, "{null_treatment}") } + FunctionArgumentClause::Where(expr) => write!(f, "WHERE {expr}"), FunctionArgumentClause::OrderBy(order_by) => { write!(f, "ORDER BY {}", display_comma_separated(order_by)) } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 360d7707f..c85c49c69 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1794,6 +1794,7 @@ impl Spanned for FunctionArgumentClause { fn span(&self) -> Span { match self { FunctionArgumentClause::IgnoreOrRespectNulls(_) => Span::empty(), + FunctionArgumentClause::Where(expr) => expr.span(), FunctionArgumentClause::OrderBy(vec) => union_spans(vec.iter().map(|i| i.expr.span())), FunctionArgumentClause::Limit(expr) => expr.span(), FunctionArgumentClause::OnOverflow(_) => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e0c2dc269..ebc16588f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18502,6 +18502,14 @@ impl<'a> Parser<'a> { let duplicate_treatment = self.parse_duplicate_treatment()?; let args = self.parse_comma_separated(Parser::parse_function_args)?; + // GoogleSQL inline aggregate filter: `AGG(expr WHERE cond [ORDER BY ..])`. + // Precedes ORDER BY / LIMIT / HAVING; equivalent to `AGG(expr) FILTER (WHERE cond)`. + if dialect_of!(self is GenericDialect | BigQueryDialect) + && self.parse_keyword(Keyword::WHERE) + { + clauses.push(FunctionArgumentClause::Where(self.parse_expr()?)); + } + if self.dialect.supports_window_function_null_treatment_arg() { if let Some(null_treatment) = self.parse_null_treatment()? { clauses.push(FunctionArgumentClause::IgnoreOrRespectNulls(null_treatment)); diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index f6d4483c2..1610e44e0 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -2950,3 +2950,28 @@ fn test_create_snapshot_table() { "CREATE SNAPSHOT TABLE IF NOT EXISTS dataset_id.table1 CLONE dataset_id.table2 FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR) OPTIONS(expiration_timestamp = TIMESTAMP '2025-01-01 00:00:00 UTC')", ); } + +#[test] +fn parse_aggregate_with_where_filter() { + // GoogleSQL allows an inline `WHERE` filter inside an aggregate call, + // e.g. `COUNT(* WHERE cond)` / `SUM(x WHERE cond)`. It is the in-argument + // spelling of the standard `AGG(x) FILTER (WHERE cond)`. Round-trips via + // the `FunctionArgumentClause::Where` clause. + bigquery().verified_stmt("SELECT COUNT(* WHERE x > 1) FROM t"); + bigquery().verified_stmt("SELECT SUM(x WHERE y > 0) FROM t"); + // Co-occurs with (and precedes) an in-argument ORDER BY. + bigquery().verified_stmt("SELECT ARRAY_AGG(x WHERE x > 100 ORDER BY x DESC) FROM t"); + + // The filter is captured as a FunctionArgumentClause::Where. + let select = bigquery().verified_only_select("SELECT SUM(salary WHERE dept = 1) FROM emp"); + let SelectItem::UnnamedExpr(Expr::Function(func)) = &select.projection[0] else { + panic!("expected a function projection"); + }; + let FunctionArguments::List(list) = &func.args else { + panic!("expected a function argument list"); + }; + assert!(list + .clauses + .iter() + .any(|c| matches!(c, FunctionArgumentClause::Where(_)))); +}