mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 15:04:04 +00:00
parse grouping sets, rollup, and cube for postgresql (#366)
* parse grouping sets, rollup, and cube * add postgresql flag
This commit is contained in:
parent
d7e84be3e1
commit
40d67aab87
4 changed files with 261 additions and 5 deletions
133
src/ast/mod.rs
133
src/ast/mod.rs
|
@ -278,6 +278,12 @@ pub enum Expr {
|
||||||
Subquery(Box<Query>),
|
Subquery(Box<Query>),
|
||||||
/// The `LISTAGG` function `SELECT LISTAGG(...) WITHIN GROUP (ORDER BY ...)`
|
/// The `LISTAGG` function `SELECT LISTAGG(...) WITHIN GROUP (ORDER BY ...)`
|
||||||
ListAgg(ListAgg),
|
ListAgg(ListAgg),
|
||||||
|
/// The `GROUPING SETS` expr.
|
||||||
|
GroupingSets(Vec<Vec<Expr>>),
|
||||||
|
/// The `CUBE` expr.
|
||||||
|
Cube(Vec<Vec<Expr>>),
|
||||||
|
/// The `ROLLUP` expr.
|
||||||
|
Rollup(Vec<Vec<Expr>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Expr {
|
impl fmt::Display for Expr {
|
||||||
|
@ -376,6 +382,44 @@ impl fmt::Display for Expr {
|
||||||
Expr::Exists(s) => write!(f, "EXISTS ({})", s),
|
Expr::Exists(s) => write!(f, "EXISTS ({})", s),
|
||||||
Expr::Subquery(s) => write!(f, "({})", s),
|
Expr::Subquery(s) => write!(f, "({})", s),
|
||||||
Expr::ListAgg(listagg) => write!(f, "{}", listagg),
|
Expr::ListAgg(listagg) => write!(f, "{}", listagg),
|
||||||
|
Expr::GroupingSets(sets) => {
|
||||||
|
write!(f, "GROUPING SETS (")?;
|
||||||
|
let mut sep = "";
|
||||||
|
for set in sets {
|
||||||
|
write!(f, "{}", sep)?;
|
||||||
|
sep = ", ";
|
||||||
|
write!(f, "({})", display_comma_separated(set))?;
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
Expr::Cube(sets) => {
|
||||||
|
write!(f, "CUBE (")?;
|
||||||
|
let mut sep = "";
|
||||||
|
for set in sets {
|
||||||
|
write!(f, "{}", sep)?;
|
||||||
|
sep = ", ";
|
||||||
|
if set.len() == 1 {
|
||||||
|
write!(f, "{}", set[0])?;
|
||||||
|
} else {
|
||||||
|
write!(f, "({})", display_comma_separated(set))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
Expr::Rollup(sets) => {
|
||||||
|
write!(f, "ROLLUP (")?;
|
||||||
|
let mut sep = "";
|
||||||
|
for set in sets {
|
||||||
|
write!(f, "{}", sep)?;
|
||||||
|
sep = ", ";
|
||||||
|
if set.len() == 1 {
|
||||||
|
write!(f, "{}", set[0])?;
|
||||||
|
} else {
|
||||||
|
write!(f, "({})", display_comma_separated(set))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
Expr::Substring {
|
Expr::Substring {
|
||||||
expr,
|
expr,
|
||||||
substring_from,
|
substring_from,
|
||||||
|
@ -1903,4 +1947,93 @@ mod tests {
|
||||||
let window_frame = WindowFrame::default();
|
let window_frame = WindowFrame::default();
|
||||||
assert_eq!(WindowFrameBound::Preceding(None), window_frame.start_bound);
|
assert_eq!(WindowFrameBound::Preceding(None), window_frame.start_bound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_grouping_sets_display() {
|
||||||
|
// a and b in different group
|
||||||
|
let grouping_sets = Expr::GroupingSets(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("a"))],
|
||||||
|
vec![Expr::Identifier(Ident::new("b"))],
|
||||||
|
]);
|
||||||
|
assert_eq!("GROUPING SETS ((a), (b))", format!("{}", grouping_sets));
|
||||||
|
|
||||||
|
// a and b in the same group
|
||||||
|
let grouping_sets = Expr::GroupingSets(vec![vec![
|
||||||
|
Expr::Identifier(Ident::new("a")),
|
||||||
|
Expr::Identifier(Ident::new("b")),
|
||||||
|
]]);
|
||||||
|
assert_eq!("GROUPING SETS ((a, b))", format!("{}", grouping_sets));
|
||||||
|
|
||||||
|
// (a, b) and (c, d) in different group
|
||||||
|
let grouping_sets = Expr::GroupingSets(vec![
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("a")),
|
||||||
|
Expr::Identifier(Ident::new("b")),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("c")),
|
||||||
|
Expr::Identifier(Ident::new("d")),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
"GROUPING SETS ((a, b), (c, d))",
|
||||||
|
format!("{}", grouping_sets)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rollup_display() {
|
||||||
|
let rollup = Expr::Rollup(vec![vec![Expr::Identifier(Ident::new("a"))]]);
|
||||||
|
assert_eq!("ROLLUP (a)", format!("{}", rollup));
|
||||||
|
|
||||||
|
let rollup = Expr::Rollup(vec![vec![
|
||||||
|
Expr::Identifier(Ident::new("a")),
|
||||||
|
Expr::Identifier(Ident::new("b")),
|
||||||
|
]]);
|
||||||
|
assert_eq!("ROLLUP ((a, b))", format!("{}", rollup));
|
||||||
|
|
||||||
|
let rollup = Expr::Rollup(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("a"))],
|
||||||
|
vec![Expr::Identifier(Ident::new("b"))],
|
||||||
|
]);
|
||||||
|
assert_eq!("ROLLUP (a, b)", format!("{}", rollup));
|
||||||
|
|
||||||
|
let rollup = Expr::Rollup(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("a"))],
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("b")),
|
||||||
|
Expr::Identifier(Ident::new("c")),
|
||||||
|
],
|
||||||
|
vec![Expr::Identifier(Ident::new("d"))],
|
||||||
|
]);
|
||||||
|
assert_eq!("ROLLUP (a, (b, c), d)", format!("{}", rollup));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cube_display() {
|
||||||
|
let cube = Expr::Cube(vec![vec![Expr::Identifier(Ident::new("a"))]]);
|
||||||
|
assert_eq!("CUBE (a)", format!("{}", cube));
|
||||||
|
|
||||||
|
let cube = Expr::Cube(vec![vec![
|
||||||
|
Expr::Identifier(Ident::new("a")),
|
||||||
|
Expr::Identifier(Ident::new("b")),
|
||||||
|
]]);
|
||||||
|
assert_eq!("CUBE ((a, b))", format!("{}", cube));
|
||||||
|
|
||||||
|
let cube = Expr::Cube(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("a"))],
|
||||||
|
vec![Expr::Identifier(Ident::new("b"))],
|
||||||
|
]);
|
||||||
|
assert_eq!("CUBE (a, b)", format!("{}", cube));
|
||||||
|
|
||||||
|
let cube = Expr::Cube(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("a"))],
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("b")),
|
||||||
|
Expr::Identifier(Ident::new("c")),
|
||||||
|
],
|
||||||
|
vec![Expr::Identifier(Ident::new("d"))],
|
||||||
|
]);
|
||||||
|
assert_eq!("CUBE (a, (b, c), d)", format!("{}", cube));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -406,6 +406,7 @@ define_keywords!(
|
||||||
SESSION,
|
SESSION,
|
||||||
SESSION_USER,
|
SESSION_USER,
|
||||||
SET,
|
SET,
|
||||||
|
SETS,
|
||||||
SHOW,
|
SHOW,
|
||||||
SIMILAR,
|
SIMILAR,
|
||||||
SMALLINT,
|
SMALLINT,
|
||||||
|
|
|
@ -338,7 +338,7 @@ impl<'a> Parser<'a> {
|
||||||
return_ok_if_some!(self.maybe_parse(|parser| {
|
return_ok_if_some!(self.maybe_parse(|parser| {
|
||||||
match parser.parse_data_type()? {
|
match parser.parse_data_type()? {
|
||||||
DataType::Interval => parser.parse_literal_interval(),
|
DataType::Interval => parser.parse_literal_interval(),
|
||||||
// PosgreSQL allows almost any identifier to be used as custom data type name,
|
// PostgreSQL allows almost any identifier to be used as custom data type name,
|
||||||
// and we support that in `parse_data_type()`. But unlike Postgres we don't
|
// and we support that in `parse_data_type()`. But unlike Postgres we don't
|
||||||
// have a list of globally reserved keywords (since they vary across dialects),
|
// have a list of globally reserved keywords (since they vary across dialects),
|
||||||
// so given `NOT 'a' LIKE 'b'`, we'd accept `NOT` as a possible custom data type
|
// so given `NOT 'a' LIKE 'b'`, we'd accept `NOT` as a possible custom data type
|
||||||
|
@ -559,6 +559,68 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// parse a group by expr. a group by expr can be one of group sets, roll up, cube, or simple
|
||||||
|
/// expr.
|
||||||
|
fn parse_group_by_expr(&mut self) -> Result<Expr, ParserError> {
|
||||||
|
if dialect_of!(self is PostgreSqlDialect) {
|
||||||
|
if self.parse_keywords(&[Keyword::GROUPING, Keyword::SETS]) {
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let result = self.parse_comma_separated(|p| p.parse_tuple(false, true))?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(Expr::GroupingSets(result))
|
||||||
|
} else if self.parse_keyword(Keyword::CUBE) {
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let result = self.parse_comma_separated(|p| p.parse_tuple(true, true))?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(Expr::Cube(result))
|
||||||
|
} else if self.parse_keyword(Keyword::ROLLUP) {
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let result = self.parse_comma_separated(|p| p.parse_tuple(true, true))?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(Expr::Rollup(result))
|
||||||
|
} else {
|
||||||
|
self.parse_expr()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO parse rollup for other dialects
|
||||||
|
self.parse_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// parse a tuple with `(` and `)`.
|
||||||
|
/// If `lift_singleton` is true, then a singleton tuple is lifted to a tuple of length 1, otherwise it will fail.
|
||||||
|
/// If `allow_empty` is true, then an empty tuple is allowed.
|
||||||
|
fn parse_tuple(
|
||||||
|
&mut self,
|
||||||
|
lift_singleton: bool,
|
||||||
|
allow_empty: bool,
|
||||||
|
) -> Result<Vec<Expr>, ParserError> {
|
||||||
|
if lift_singleton {
|
||||||
|
if self.consume_token(&Token::LParen) {
|
||||||
|
let result = if allow_empty && self.consume_token(&Token::RParen) {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
let result = self.parse_comma_separated(Parser::parse_expr)?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
result
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Ok(vec![self.parse_expr()?])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let result = if allow_empty && self.consume_token(&Token::RParen) {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
let result = self.parse_comma_separated(Parser::parse_expr)?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
result
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_case_expr(&mut self) -> Result<Expr, ParserError> {
|
pub fn parse_case_expr(&mut self) -> Result<Expr, ParserError> {
|
||||||
let mut operand = None;
|
let mut operand = None;
|
||||||
if !self.parse_keyword(Keyword::WHEN) {
|
if !self.parse_keyword(Keyword::WHEN) {
|
||||||
|
@ -2494,7 +2556,7 @@ impl<'a> Parser<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let group_by = if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) {
|
let group_by = if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) {
|
||||||
self.parse_comma_separated(Parser::parse_expr)?
|
self.parse_comma_separated(Parser::parse_group_by_expr)?
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,13 +20,14 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
use test_utils::{all_dialects, expr_from_projection, join, number, only, table, table_alias};
|
|
||||||
|
|
||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::{GenericDialect, SQLiteDialect};
|
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect, SQLiteDialect};
|
||||||
use sqlparser::keywords::ALL_KEYWORDS;
|
use sqlparser::keywords::ALL_KEYWORDS;
|
||||||
use sqlparser::parser::{Parser, ParserError};
|
use sqlparser::parser::{Parser, ParserError};
|
||||||
|
use test_utils::{
|
||||||
|
all_dialects, expr_from_projection, join, number, only, table, table_alias, TestedDialects,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_insert_values() {
|
fn parse_insert_values() {
|
||||||
|
@ -1039,6 +1040,65 @@ fn parse_select_group_by() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_select_group_by_grouping_sets() {
|
||||||
|
let dialects = TestedDialects {
|
||||||
|
dialects: vec![Box::new(PostgreSqlDialect {})],
|
||||||
|
};
|
||||||
|
let sql =
|
||||||
|
"SELECT brand, size, sum(sales) FROM items_sold GROUP BY size, GROUPING SETS ((brand), (size), ())";
|
||||||
|
let select = dialects.verified_only_select(sql);
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("size")),
|
||||||
|
Expr::GroupingSets(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("brand"))],
|
||||||
|
vec![Expr::Identifier(Ident::new("size"))],
|
||||||
|
vec![],
|
||||||
|
])
|
||||||
|
],
|
||||||
|
select.group_by
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_select_group_by_rollup() {
|
||||||
|
let dialects = TestedDialects {
|
||||||
|
dialects: vec![Box::new(PostgreSqlDialect {})],
|
||||||
|
};
|
||||||
|
let sql = "SELECT brand, size, sum(sales) FROM items_sold GROUP BY size, ROLLUP (brand, size)";
|
||||||
|
let select = dialects.verified_only_select(sql);
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("size")),
|
||||||
|
Expr::Rollup(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("brand"))],
|
||||||
|
vec![Expr::Identifier(Ident::new("size"))],
|
||||||
|
])
|
||||||
|
],
|
||||||
|
select.group_by
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_select_group_by_cube() {
|
||||||
|
let dialects = TestedDialects {
|
||||||
|
dialects: vec![Box::new(PostgreSqlDialect {})],
|
||||||
|
};
|
||||||
|
let sql = "SELECT brand, size, sum(sales) FROM items_sold GROUP BY size, CUBE (brand, size)";
|
||||||
|
let select = dialects.verified_only_select(sql);
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
Expr::Identifier(Ident::new("size")),
|
||||||
|
Expr::Cube(vec![
|
||||||
|
vec![Expr::Identifier(Ident::new("brand"))],
|
||||||
|
vec![Expr::Identifier(Ident::new("size"))],
|
||||||
|
])
|
||||||
|
],
|
||||||
|
select.group_by
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_select_having() {
|
fn parse_select_having() {
|
||||||
let sql = "SELECT foo FROM bar GROUP BY foo HAVING COUNT(*) > 1";
|
let sql = "SELECT foo FROM bar GROUP BY foo HAVING COUNT(*) > 1";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue