Provide LISTAGG implementation (#174)

This patch provides an initial implemenation of LISTAGG[1]. Notably this
implemenation deviates from ANSI SQL by allowing both WITHIN GROUP and
the delimiter to be optional. We do so because Redshift SQL works this
way and this approach is ultimately more flexible.

Fixes #169.

[1] https://modern-sql.com/feature/listagg
This commit is contained in:
Max Countryman 2020-05-30 08:50:17 -07:00 committed by GitHub
parent 418b9631ce
commit 5f3c1bda01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 207 additions and 17 deletions

View file

@ -244,7 +244,7 @@ fn parse_select_all() {
fn parse_select_all_distinct() {
let result = parse_sql_statements("SELECT ALL DISTINCT name FROM customer");
assert_eq!(
ParserError::ParserError("Cannot specify both ALL and DISTINCT in SELECT".to_string()),
ParserError::ParserError("Cannot specify both ALL and DISTINCT".to_string()),
result.unwrap_err(),
);
}
@ -357,9 +357,7 @@ fn parse_select_count_distinct() {
let sql = "SELECT COUNT(ALL DISTINCT + x) FROM customer";
let res = parse_sql_statements(sql);
assert_eq!(
ParserError::ParserError(
"Cannot specify both ALL and DISTINCT in function: COUNT".to_string()
),
ParserError::ParserError("Cannot specify both ALL and DISTINCT".to_string()),
res.unwrap_err()
);
}
@ -914,6 +912,58 @@ fn parse_extract() {
);
}
#[test]
fn parse_listagg() {
let sql = "SELECT LISTAGG(DISTINCT dateid, ', ' ON OVERFLOW TRUNCATE '%' WITHOUT COUNT) \
WITHIN GROUP (ORDER BY id, username)";
let select = verified_only_select(sql);
verified_stmt("SELECT LISTAGG(sellerid) WITHIN GROUP (ORDER BY dateid)");
verified_stmt("SELECT LISTAGG(dateid)");
verified_stmt("SELECT LISTAGG(DISTINCT dateid)");
verified_stmt("SELECT LISTAGG(dateid ON OVERFLOW ERROR)");
verified_stmt("SELECT LISTAGG(dateid ON OVERFLOW TRUNCATE N'...' WITH COUNT)");
verified_stmt("SELECT LISTAGG(dateid ON OVERFLOW TRUNCATE X'deadbeef' WITH COUNT)");
let expr = Box::new(Expr::Identifier(Ident::new("dateid")));
let on_overflow = Some(ListAggOnOverflow::Truncate {
filler: Some(Box::new(Expr::Value(Value::SingleQuotedString(
"%".to_string(),
)))),
with_count: false,
});
let within_group = vec![
OrderByExpr {
expr: Expr::Identifier(Ident {
value: "id".to_string(),
quote_style: None,
}),
asc: None,
nulls_first: None,
},
OrderByExpr {
expr: Expr::Identifier(Ident {
value: "username".to_string(),
quote_style: None,
}),
asc: None,
nulls_first: None,
},
];
assert_eq!(
&Expr::ListAgg(ListAgg {
distinct: true,
expr,
separator: Some(Box::new(Expr::Value(Value::SingleQuotedString(
", ".to_string()
)))),
on_overflow,
within_group
}),
expr_from_projection(only(&select.projection))
);
}
#[test]
fn parse_create_table() {
let sql = "CREATE TABLE uk_cities (\