Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8294,6 +8294,13 @@ 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 an aggregate call, e.g.
/// `COUNT(* WHERE cond)` / `SUM(x WHERE cond)` / `ARRAY_AGG(x WHERE cond ORDER BY ..)`.
/// Popularized by [GoogleSQL]; equivalent to the standard `AGG(x) FILTER (WHERE cond)`.
/// Accepted for all dialects since `WHERE` cannot otherwise begin a function argument.
///
/// [GoogleSQL]: 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
Expand Down Expand Up @@ -8335,6 +8342,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))
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1806,6 +1806,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(),
Expand Down
8 changes: 8 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18870,6 +18870,14 @@ impl<'a> Parser<'a> {
let duplicate_treatment = self.parse_duplicate_treatment()?;
let args = self.parse_comma_separated(Parser::parse_function_args)?;

// Inline aggregate filter: `AGG(expr WHERE cond [ORDER BY ..])`, popularized by
// GoogleSQL. Precedes ORDER BY / LIMIT / HAVING; equivalent to the standard
// `AGG(expr) FILTER (WHERE cond)`. Accepted for all dialects: `WHERE` is a reserved
// keyword that cannot begin a function argument, so the syntax is unambiguous.
if 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));
Expand Down
27 changes: 27 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6148,6 +6148,33 @@ fn parse_aggregate_with_group_by() {
//TODO: assertions
}

#[test]
fn parse_aggregate_with_where_filter() {
// The inline `WHERE` filter inside an aggregate call, e.g. `COUNT(* WHERE cond)` /
// `SUM(x WHERE cond)`, is the in-argument spelling of the standard
// `AGG(x) FILTER (WHERE cond)`. Popularized by GoogleSQL, it is accepted for all
// dialects (`verified_stmt` round-trips through every dialect).
verified_stmt("SELECT COUNT(* WHERE x > 1) FROM t");
verified_stmt("SELECT SUM(x WHERE y > 0) FROM t");
// Co-occurs with (and precedes) an in-argument ORDER BY.
verified_stmt("SELECT ARRAY_AGG(x WHERE x > 100 ORDER BY x DESC) FROM t");
// A compound predicate referencing multiple columns round-trips intact.
verified_stmt("SELECT ARRAY_AGG(a WHERE b > 0 AND c < 10) FROM t");

// The filter is captured as a FunctionArgumentClause::Where holding the predicate.
let select = verified_only_select("SELECT SUM(salary WHERE dept = 1) FROM emp");
let Expr::Function(func) = expr_from_projection(&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(Expr::BinaryOp { .. }))));
}

#[test]
fn parse_literal_integer() {
let sql = "SELECT 1, -10, +20";
Expand Down
Loading