Repository: apache/datafusion-sqlparser-rs (Rust)

Issue:
Hive dialect cannot parse WHERE nested in FILTER
I am dealing with some view code I do not control, trying to parse this gem:

```
   SELECT
     "array_agg"("status") FILTER (WHERE ("badge_type" = 'AW'))[1] "aw_badge"
   FROM
     some_table
```

which is valid on Athena/Hive in practice but not parsing here. It yields the parse error:

```
        let select = r#"SELECT
   "array_agg"("status") FILTER (WHERE ("badge_type" = 'AW'))[1] "aw_badge"
FROM
  table1
"#;
        sqlparser::parser::Parser::parse_sql(&sqlparser::dialect::HiveDialect {}, select)
            .unwrap();
```

```
called `Result::unwrap()` on an `Err` value: ParserError("Expected end of statement, found: (")
thread 'graph::tests::test_big_view' panicked at 'called `Result::unwrap()` on an `Err` value: ParserError("Expected end of statement, found: (")', sparkle-view-analyzer/src/graph/mod.rs:322:14
```


Discussion:
Sounds like the technique being used is "filtering during aggregation" and the trino docs have more to say: https://trino.io/docs/current/functions/aggregate.html#filtering-during-aggregation . Not sure why they don't mention this in the presto docs.
I am now debugging this as a new test case for the Hive dialect:
```
#[test]
fn filtering_during_aggregation() {
    env_logger::init();
    let rename =
        "SELECT array_agg(name) FILTER (WHERE name IS NOT NULL) FROM region";
    println!("{}", hive().verified_stmt(rename));
}
```

```
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] Parsing sql 'SELECT array_agg(name) FILTER (WHERE name IS NOT NULL) FROM region'...
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] parsing expr
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] parsing expr
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] prefix: Identifier(Ident { value: "name", quote_style: None })
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] get_next_precedence() RParen
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] 0: ) 1: FILTER 2: (
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] next precedence: 0
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] prefix: Function(Function { name: ObjectName([Ident { value: "array_agg", quote_style: None }]), args: [Unnamed(Expr(Identifier(Ident { value: "name", quote_style: None })))], over: None, distinct: false, special: false })
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] get_next_precedence() Word(Word { value: "FILTER", quote_style: None, keyword: FILTER })
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] 0: FILTER 1: ( 2: WHERE
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] next precedence: 0
[2022-08-18T19:09:28Z DEBUG sqlparser::parser] pushing statement: Query(Query { with: None, body: Select(Select { distinct: false, top: None, projection: [ExprWithAlias { expr: Function(Function { name: ObjectName([Ident { value: "array_agg", quote_style: None }]), args: [Unnamed(Expr(Identifier(Ident { value: "name", quote_style: None })))], over: None, distinct: false, special: false }), alias: Ident { value: "FILTER", quote_style: None } }], into: None, from: [], lateral_views: [], selection: None, group_by: [], cluster_by: [], distribute_by: [], sort_by: [], having: None, qualify: None }), order_by: [], limit: None, offset: None, fetch: None, lock: None })
```

how can I make it so `FILTER` is not treated like an alias for the `array_agg` function? Do I need to register `FILTER` an infix keyword to keep the parser going?
Looking at how Expr are built:

```
    /// Parse a comma-delimited list of projections after SELECT
    pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
        match self.parse_wildcard_expr()? {
            WildcardExpr::Expr(expr) => self
                .parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS)
                .map(|alias| match alias {
                    Some(alias) => SelectItem::ExprWithAlias { expr, alias },
                    None => SelectItem::UnnamedExpr(expr),
                }),
            WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(prefix)),
            WildcardExpr::Wildcard => Ok(SelectItem::Wildcard),
        }
    }
```

I see the `keywords::RESERVED_FOR_COLUMN_ALIAS` but I can't just put `Keyword::FILTER` in that list. Filter is allowed as an alias when standalone, for example this valid query:

```
SELECT id filter FROM items
```

but if they do something with an inline filter, that's not an alias:

```
SELECT id filter (where id = 1) FROM items
```
@xrl I filed https://github.com/sqlparser-rs/sqlparser-rs/pull/585 .. let me know what you think



Patch:
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 4f5fdb2eb..6c279518e 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -375,6 +375,8 @@ pub enum Expr {
     MapAccess { column: Box<Expr>, keys: Vec<Expr> },
     /// Scalar function call e.g. `LEFT(foo, 5)`
     Function(Function),
+    /// Aggregate function with filter
+    AggregateExpressionWithFilter { expr: Box<Expr>, filter: Box<Expr> },
     /// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
     ///
     /// Note we only recognize a complete single expression as `<condition>`,
@@ -571,6 +573,9 @@ impl fmt::Display for Expr {
                 write!(f, " '{}'", &value::escape_single_quote_string(value))
             }
             Expr::Function(fun) => write!(f, "{}", fun),
+            Expr::AggregateExpressionWithFilter { expr, filter } => {
+                write!(f, "{} FILTER (WHERE {})", expr, filter)
+            }
             Expr::Case {
                 operand,
                 conditions,
diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs
index 9b42857ec..ceb5488ef 100644
--- a/src/dialect/hive.rs
+++ b/src/dialect/hive.rs
@@ -36,4 +36,8 @@ impl Dialect for HiveDialect {
             || ch == '{'
             || ch == '}'
     }
+
+    fn supports_filter_during_aggregation(&self) -> bool {
+        true
+    }
 }
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 46e8dda2c..1d3c9cf5f 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -67,6 +67,10 @@ pub trait Dialect: Debug + Any {
     fn is_identifier_start(&self, ch: char) -> bool;
     /// Determine if a character is a valid unquoted identifier character
     fn is_identifier_part(&self, ch: char) -> bool;
+    /// Does the dialect support `FILTER (WHERE expr)` for aggregate queries?
+    fn supports_filter_during_aggregation(&self) -> bool {
+        false
+    }
     /// Dialect-specific prefix parser override
     fn parse_prefix(&self, _parser: &mut Parser) -> Option<Result<Expr, ParserError>> {
         // return None to fall back to the default behavior
diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index 04d64b9bf..b1f261b2e 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -42,6 +42,10 @@ impl Dialect for PostgreSqlDialect {
             None
         }
     }
+
+    fn supports_filter_during_aggregation(&self) -> bool {
+        true
+    }
 }
 
 pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {
diff --git a/src/parser.rs b/src/parser.rs
index 877c47303..894bb84f1 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -4542,12 +4542,31 @@ impl<'a> Parser<'a> {
     /// Parse a comma-delimited list of projections after SELECT
     pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
         match self.parse_wildcard_expr()? {
-            WildcardExpr::Expr(expr) => self
-                .parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS)
-                .map(|alias| match alias {
-                    Some(alias) => SelectItem::ExprWithAlias { expr, alias },
-                    None => SelectItem::UnnamedExpr(expr),
-                }),
+            WildcardExpr::Expr(expr) => {
+                let expr: Expr = if self.dialect.supports_filter_during_aggregation()
+                    && self.parse_keyword(Keyword::FILTER)
+                {
+                    let i = self.index - 1;
+                    if self.consume_token(&Token::LParen) && self.parse_keyword(Keyword::WHERE) {
+                        let filter = self.parse_expr()?;
+                        self.expect_token(&Token::RParen)?;
+                        Expr::AggregateExpressionWithFilter {
+                            expr: Box::new(expr),
+                            filter: Box::new(filter),
+                        }
+                    } else {
+                        self.index = i;
+                        expr
+                    }
+                } else {
+                    expr
+                };
+                self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS)
+                    .map(|alias| match alias {
+                        Some(alias) => SelectItem::ExprWithAlias { expr, alias },
+                        None => SelectItem::UnnamedExpr(expr),
+                    })
+            }
             WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard(prefix)),
             WildcardExpr::Wildcard => Ok(SelectItem::Wildcard),
         }


Test patch:
diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs
index 4223ad5fa..8839cea2b 100644
--- a/tests/sqlparser_hive.rs
+++ b/tests/sqlparser_hive.rs
@@ -276,6 +276,31 @@ fn parse_create_function() {
     );
 }
 
+#[test]
+fn filtering_during_aggregation() {
+    let rename = "SELECT \
+        array_agg(name) FILTER (WHERE name IS NOT NULL), \
+        array_agg(name) FILTER (WHERE name LIKE 'a%') \
+        FROM region";
+    println!("{}", hive().verified_stmt(rename));
+}
+
+#[test]
+fn filtering_during_aggregation_aliased() {
+    let rename = "SELECT \
+        array_agg(name) FILTER (WHERE name IS NOT NULL) AS agg1, \
+        array_agg(name) FILTER (WHERE name LIKE 'a%') AS agg2 \
+        FROM region";
+    println!("{}", hive().verified_stmt(rename));
+}
+
+#[test]
+fn filter_as_alias() {
+    let sql = "SELECT name filter FROM region";
+    let expected = "SELECT name AS filter FROM region";
+    println!("{}", hive().one_statement_parses_to(sql, expected));
+}
+
 fn hive() -> TestedDialects {
     TestedDialects {
         dialects: vec![Box::new(HiveDialect {})],