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
7 changes: 7 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8294,6 +8294,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
Expand Down Expand Up @@ -8335,6 +8341,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)?;

// 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)
Comment on lines +18875 to +18876

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we always have this syntax be accepted by the parser?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - but it is not a standard syntax, it is really a BigQuery's shortcut for standard FILTER(WHERE ...) clause. So I don't think it should be enabled for all dialects. Let me know whether you still want to have it unconditionally

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think as long as its not ambiguous we can have it be accepted unconditionally

{
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
25 changes: 25 additions & 0 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_))));
}
Loading