Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 761
provide LISTAGG implementation#174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
6896953c6216ec48d6a8beba849aa95fa55ee144b21e41d301e952b65736b9dbcc41a3a16c3a4e1ad011158c650b2cb822b5040727e53d37d1ff82587ac2a195c53124e17b477a258370596074f1f8c4a5964599ebe00d7815854f2b7d116f92443bcd471e8File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -224,6 +224,8 @@ pub enum Expr { | ||
| /// A parenthesized subquery `(SELECT ...)`, used in expression like | ||
| /// `SELECT (subquery) AS x` or `WHERE (subquery) = x` | ||
| Subquery(Box<Query>), | ||
| /// The `LISTAGG` function `SELECT LISTAGG(...) WITHIN GROUP (ORDER BY ...)` | ||
| ListAgg(ListAgg), | ||
| } | ||
| impl fmt::Display for Expr { | ||
| @@ -299,6 +301,7 @@ impl fmt::Display for Expr { | ||
| } | ||
| Expr::Exists(s) => write!(f, "EXISTS ({})", s), | ||
| Expr::Subquery(s) => write!(f, "({})", s), | ||
| Expr::ListAgg(listagg) => write!(f, "{}", listagg), | ||
| } | ||
| } | ||
| } | ||
| @@ -850,6 +853,77 @@ impl FromStr for FileFormat { | ||
| } | ||
| } | ||
| /// A `LISTAGG` invocation `LISTAGG( [ DISTINCT ] <expr>[, <separator> ] [ON OVERFLOW <on_overflow>] ) ) | ||
| /// [ WITHIN GROUP (ORDER BY <within_group1>[, ...] ) ]` | ||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
| pub struct ListAgg { | ||
| pub distinct: bool, | ||
| pub expr: Box<Expr>, | ||
| pub separator: Option<Box<Expr>>, | ||
| pub on_overflow: Option<ListAggOnOverflow>, | ||
| pub within_group: Vec<OrderByExpr>, | ||
| } | ||
| impl fmt::Display for ListAgg { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| write!( | ||
| f, | ||
| "LISTAGG({}{}", | ||
| if self.distinct { "DISTINCT " } else { "" }, | ||
| self.expr | ||
| )?; | ||
| if let Some(separator) = &self.separator { | ||
| write!(f, ", {}", separator)?; | ||
| } | ||
| if let Some(on_overflow) = &self.on_overflow { | ||
| write!(f, "{}", on_overflow)?; | ||
| } | ||
| write!(f, ")")?; | ||
| if !self.within_group.is_empty() { | ||
| write!( | ||
| f, | ||
| " WITHIN GROUP (ORDER BY {})", | ||
| display_comma_separated(&self.within_group) | ||
| )?; | ||
| } | ||
| Ok(()) | ||
| } | ||
| } | ||
| /// The `ON OVERFLOW` clause of a LISTAGG invocation | ||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
| pub enum ListAggOnOverflow { | ||
| /// `ON OVERFLOW ERROR` | ||
| Error, | ||
maxcountryman marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// `ON OVERFLOW TRUNCATE [ <filler> ] WITH[OUT] COUNT` | ||
| Truncate { | ||
| filler: Option<Box<Expr>>, | ||
| with_count: bool, | ||
| }, | ||
| } | ||
| impl fmt::Display for ListAggOnOverflow { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| write!(f, " ON OVERFLOW")?; | ||
| match self { | ||
| ListAggOnOverflow::Error => write!(f, " ERROR"), | ||
| ListAggOnOverflow::Truncate { filler, with_count } => { | ||
| write!(f, " TRUNCATE")?; | ||
| if let Some(filler) = filler { | ||
| write!(f, " {}", filler)?; | ||
| } | ||
| if *with_count { | ||
maxcountryman marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| write!(f, " WITH")?; | ||
| } else { | ||
| write!(f, " WITHOUT")?; | ||
| } | ||
| write!(f, " COUNT") | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
| pub enum ObjectType { | ||
| Table, | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -191,6 +191,7 @@ impl Parser { | ||
| "EXISTS" => self.parse_exists_expr(), | ||
| "EXTRACT" => self.parse_extract_expr(), | ||
| "INTERVAL" => self.parse_literal_interval(), | ||
| "LISTAGG" => self.parse_listagg_expr(), | ||
| "NOT" => Ok(Expr::UnaryOp { | ||
| op: UnaryOperator::Not, | ||
| expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?), | ||
| @@ -272,14 +273,7 @@ impl Parser { | ||
| pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> { | ||
| self.expect_token(&Token::LParen)?; | ||
| let all = self.parse_keyword("ALL"); | ||
| let distinct = self.parse_keyword("DISTINCT"); | ||
| if all && distinct { | ||
| return parser_err!(format!( | ||
| "Cannot specify both ALL and DISTINCT in function: {}", | ||
| name.to_string(), | ||
| )); | ||
| } | ||
| let distinct = self.parse_all_or_distinct()?; | ||
| let args = self.parse_optional_args()?; | ||
| let over = if self.parse_keyword("OVER") { | ||
| // TBD: support window names (`OVER mywin`) in place of inline specification | ||
| @@ -423,6 +417,66 @@ impl Parser { | ||
| }) | ||
| } | ||
| /// Parse a SQL LISTAGG expression, e.g. `LISTAGG(...) WITHIN GROUP (ORDER BY ...)`. | ||
| pub fn parse_listagg_expr(&mut self) -> Result<Expr, ParserError> { | ||
| self.expect_token(&Token::LParen)?; | ||
| let distinct = self.parse_all_or_distinct()?; | ||
| let expr = Box::new(self.parse_expr()?); | ||
| // While ANSI SQL would would require the separator, Redshift makes this optional. Here we | ||
| // choose to make the separator optional as this provides the more general implementation. | ||
| let separator = if self.consume_token(&Token::Comma) { | ||
| Some(Box::new(self.parse_expr()?)) | ||
| } else { | ||
| None | ||
| }; | ||
| let on_overflow = if self.parse_keywords(vec!["ON", "OVERFLOW"]) { | ||
| if self.parse_keyword("ERROR") { | ||
| Some(ListAggOnOverflow::Error) | ||
| } else { | ||
maxcountryman marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| self.expect_keyword("TRUNCATE")?; | ||
| let filler = match self.peek_token() { | ||
| Some(Token::Word(kw)) if kw.keyword == "WITH" || kw.keyword == "WITHOUT" => { | ||
| None | ||
| } | ||
| Some(Token::SingleQuotedString(_)) | ||
| | Some(Token::NationalStringLiteral(_)) | ||
| | Some(Token::HexStringLiteral(_)) => Some(Box::new(self.parse_expr()?)), | ||
Comment on lines
+441
to
+443
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allows for any supported string literal to be parsed as a filler and uses Also it might be a good idea to be stricter with the | ||
| _ => self.expected( | ||
| "either filler, WITH, or WITHOUT in LISTAGG", | ||
| self.peek_token(), | ||
| )?, | ||
| }; | ||
| let with_count = self.parse_keyword("WITH"); | ||
| if !with_count && !self.parse_keyword("WITHOUT") { | ||
| self.expected("either WITH or WITHOUT in LISTAGG", self.peek_token())?; | ||
| } | ||
| self.expect_keyword("COUNT")?; | ||
| Some(ListAggOnOverflow::Truncate { filler, with_count }) | ||
| } | ||
| } else { | ||
| None | ||
| }; | ||
| self.expect_token(&Token::RParen)?; | ||
| // Once again ANSI SQL requires WITHIN GROUP, but Redshift does not. Again we choose the | ||
| // more general implementation. | ||
| let within_group = if self.parse_keywords(vec!["WITHIN", "GROUP"]) { | ||
| self.expect_token(&Token::LParen)?; | ||
| self.expect_keywords(&["ORDER", "BY"])?; | ||
| let order_by_expr = self.parse_comma_separated(Parser::parse_order_by_expr)?; | ||
| self.expect_token(&Token::RParen)?; | ||
| order_by_expr | ||
| } else { | ||
| vec![] | ||
| }; | ||
| Ok(Expr::ListAgg(ListAgg { | ||
| distinct, | ||
| expr, | ||
| separator, | ||
| on_overflow, | ||
| within_group, | ||
| })) | ||
| } | ||
| // This function parses date/time fields for both the EXTRACT function-like | ||
| // operator and interval qualifiers. EXTRACT supports a wider set of | ||
| // date/time fields than interval qualifiers, so this function may need to | ||
| @@ -851,6 +905,18 @@ impl Parser { | ||
| Ok(values) | ||
| } | ||
| /// Parse either `ALL` or `DISTINCT`. Returns `true` if `DISTINCT` is parsed and results in a | ||
| /// `ParserError` if both `ALL` and `DISTINCT` are fround. | ||
| pub fn parse_all_or_distinct(&mut self) -> Result<bool, ParserError> { | ||
maxcountryman marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| let all = self.parse_keyword("ALL"); | ||
| let distinct = self.parse_keyword("DISTINCT"); | ||
| if all && distinct { | ||
| return parser_err!("Cannot specify both ALL and DISTINCT".to_string()); | ||
nickolay marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } else { | ||
| Ok(distinct) | ||
| } | ||
| } | ||
| /// Parse a SQL CREATE statement | ||
| pub fn parse_create(&mut self) -> Result<Statement, ParserError> { | ||
| if self.parse_keyword("TABLE") { | ||
| @@ -1635,11 +1701,7 @@ impl Parser { | ||
| /// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`), | ||
| /// assuming the initial `SELECT` was already consumed | ||
| pub fn parse_select(&mut self) -> Result<Select, ParserError> { | ||
| let all = self.parse_keyword("ALL"); | ||
| let distinct = self.parse_keyword("DISTINCT"); | ||
| if all && distinct { | ||
| return parser_err!("Cannot specify both ALL and DISTINCT in SELECT"); | ||
| } | ||
| let distinct = self.parse_all_or_distinct()?; | ||
| let top = if self.parse_keyword("TOP") { | ||
| Some(self.parse_top()?) | ||
Uh oh!
There was an error while loading. Please reload this page.