datafusion-sqlparse/tests/sqlparser_bigquery.rs
2023-03-02 10:32:20 -05:00

449 lines
15 KiB
Rust

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[macro_use]
mod test_utils;
use sqlparser::ast::*;
use sqlparser::dialect::{BigQueryDialect, GenericDialect};
use test_utils::*;
#[cfg(feature = "bigdecimal")]
use bigdecimal::*;
#[cfg(feature = "bigdecimal")]
use std::str::FromStr;
#[test]
fn parse_literal_string() {
let sql = r#"SELECT 'single', "double""#;
let select = bigquery().verified_only_select(sql);
assert_eq!(2, select.projection.len());
assert_eq!(
&Expr::Value(Value::SingleQuotedString("single".to_string())),
expr_from_projection(&select.projection[0])
);
assert_eq!(
&Expr::Value(Value::DoubleQuotedString("double".to_string())),
expr_from_projection(&select.projection[1])
);
}
#[test]
fn parse_byte_literal() {
let sql = r#"SELECT B'abc', B"abc""#;
let select = bigquery().verified_only_select(sql);
assert_eq!(2, select.projection.len());
assert_eq!(
&Expr::Value(Value::SingleQuotedByteStringLiteral("abc".to_string())),
expr_from_projection(&select.projection[0])
);
assert_eq!(
&Expr::Value(Value::DoubleQuotedByteStringLiteral("abc".to_string())),
expr_from_projection(&select.projection[1])
);
let sql = r#"SELECT b'abc', b"abc""#;
bigquery().one_statement_parses_to(sql, r#"SELECT B'abc', B"abc""#);
}
#[test]
fn parse_raw_literal() {
let sql = r#"SELECT R'abc', R"abc", R'f\(abc,(.*),def\)', R"f\(abc,(.*),def\)""#;
let stmt = bigquery().one_statement_parses_to(
sql,
r#"SELECT R'abc', R'abc', R'f\(abc,(.*),def\)', R'f\(abc,(.*),def\)'"#,
);
if let Statement::Query(query) = stmt {
if let SetExpr::Select(select) = *query.body {
assert_eq!(4, select.projection.len());
assert_eq!(
&Expr::Value(Value::RawStringLiteral("abc".to_string())),
expr_from_projection(&select.projection[0])
);
assert_eq!(
&Expr::Value(Value::RawStringLiteral("abc".to_string())),
expr_from_projection(&select.projection[1])
);
assert_eq!(
&Expr::Value(Value::RawStringLiteral(r#"f\(abc,(.*),def\)"#.to_string())),
expr_from_projection(&select.projection[2])
);
assert_eq!(
&Expr::Value(Value::RawStringLiteral(r#"f\(abc,(.*),def\)"#.to_string())),
expr_from_projection(&select.projection[3])
);
return;
}
}
panic!("invalid query")
}
#[test]
fn parse_table_identifiers() {
fn test_table_ident(ident: &str, expected: Vec<Ident>) {
let sql = format!("SELECT 1 FROM {ident}");
let select = bigquery().verified_only_select(&sql);
assert_eq!(
select.from,
vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(expected),
alias: None,
args: None,
columns_definition: None,
with_hints: vec![],
},
joins: vec![]
},]
);
}
fn test_table_ident_err(ident: &str) {
let sql = format!("SELECT 1 FROM {ident}");
assert!(bigquery().parse_sql_statements(&sql).is_err());
}
test_table_ident("da-sh-es", vec![Ident::new("da-sh-es")]);
test_table_ident("`spa ce`", vec![Ident::with_quote('`', "spa ce")]);
test_table_ident(
"`!@#$%^&*()-=_+`",
vec![Ident::with_quote('`', "!@#$%^&*()-=_+")],
);
test_table_ident(
"_5abc.dataField",
vec![Ident::new("_5abc"), Ident::new("dataField")],
);
test_table_ident(
"`5abc`.dataField",
vec![Ident::with_quote('`', "5abc"), Ident::new("dataField")],
);
test_table_ident_err("5abc.dataField");
test_table_ident(
"abc5.dataField",
vec![Ident::new("abc5"), Ident::new("dataField")],
);
test_table_ident_err("abc5!.dataField");
test_table_ident(
"`GROUP`.dataField",
vec![Ident::with_quote('`', "GROUP"), Ident::new("dataField")],
);
// TODO: this should be error
// test_table_ident_err("GROUP.dataField");
test_table_ident("abc5.GROUP", vec![Ident::new("abc5"), Ident::new("GROUP")]);
}
#[test]
fn parse_join_constraint_unnest_alias() {
assert_eq!(
only(
bigquery()
.verified_only_select("SELECT * FROM t1 JOIN UNNEST(t1.a) AS f ON c1 = c2")
.from
)
.joins,
vec![Join {
relation: TableFactor::UNNEST {
alias: table_alias("f"),
array_expr: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("t1"),
Ident::new("a")
])),
with_offset: false,
with_offset_alias: None
},
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::Identifier("c1".into())),
op: BinaryOperator::Eq,
right: Box::new(Expr::Identifier("c2".into())),
})),
}]
);
}
#[test]
fn parse_trailing_comma() {
for (sql, canonical) in [
("SELECT a,", "SELECT a"),
("SELECT a, b,", "SELECT a, b"),
("SELECT a, b AS c,", "SELECT a, b AS c"),
("SELECT a, b AS c, FROM t", "SELECT a, b AS c FROM t"),
("SELECT a, b, FROM t", "SELECT a, b FROM t"),
("SELECT a, b, LIMIT 1", "SELECT a, b LIMIT 1"),
("SELECT a, (SELECT 1, )", "SELECT a, (SELECT 1)"),
] {
bigquery().one_statement_parses_to(sql, canonical);
}
}
#[test]
fn parse_cast_type() {
let sql = r#"SELECT SAFE_CAST(1 AS INT64)"#;
bigquery().verified_only_select(sql);
}
#[test]
fn parse_like() {
fn chk(negated: bool) {
let sql = &format!(
"SELECT * FROM customers WHERE name {}LIKE '%a'",
if negated { "NOT " } else { "" }
);
let select = bigquery().verified_only_select(sql);
assert_eq!(
Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None,
},
select.selection.unwrap()
);
// Test with escape char
let sql = &format!(
"SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'",
if negated { "NOT " } else { "" }
);
let select = bigquery().verified_only_select(sql);
assert_eq!(
Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\'),
},
select.selection.unwrap()
);
// This statement tests that LIKE and NOT LIKE have the same precedence.
// This was previously mishandled (#81).
let sql = &format!(
"SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL",
if negated { "NOT " } else { "" }
);
let select = bigquery().verified_only_select(sql);
assert_eq!(
Expr::IsNull(Box::new(Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None,
})),
select.selection.unwrap()
);
}
chk(false);
chk(true);
}
#[test]
fn parse_similar_to() {
fn chk(negated: bool) {
let sql = &format!(
"SELECT * FROM customers WHERE name {}SIMILAR TO '%a'",
if negated { "NOT " } else { "" }
);
let select = bigquery().verified_only_select(sql);
assert_eq!(
Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None,
},
select.selection.unwrap()
);
// Test with escape char
let sql = &format!(
"SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'",
if negated { "NOT " } else { "" }
);
let select = bigquery().verified_only_select(sql);
assert_eq!(
Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\'),
},
select.selection.unwrap()
);
// This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence.
let sql = &format!(
"SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL",
if negated { "NOT " } else { "" }
);
let select = bigquery().verified_only_select(sql);
assert_eq!(
Expr::IsNull(Box::new(Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\'),
})),
select.selection.unwrap()
);
}
chk(false);
chk(true);
}
#[test]
fn parse_array_agg_func() {
for sql in [
"SELECT ARRAY_AGG(x ORDER BY x) AS a FROM T",
"SELECT ARRAY_AGG(x ORDER BY x LIMIT 2) FROM tbl",
"SELECT ARRAY_AGG(DISTINCT x ORDER BY x LIMIT 2) FROM tbl",
] {
bigquery().verified_stmt(sql);
}
}
#[test]
fn test_select_wildcard_with_except() {
let select = bigquery_and_generic().verified_only_select("SELECT * EXCEPT (col_a) FROM data");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(ExceptSelectItem {
first_element: Ident::new("col_a"),
additional_elements: vec![],
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
let select = bigquery_and_generic()
.verified_only_select("SELECT * EXCEPT (department_id, employee_id) FROM employee_table");
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_except: Some(ExceptSelectItem {
first_element: Ident::new("department_id"),
additional_elements: vec![Ident::new("employee_id")],
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
assert_eq!(
bigquery_and_generic()
.parse_sql_statements("SELECT * EXCEPT () FROM employee_table")
.unwrap_err()
.to_string(),
"sql parser error: Expected identifier, found: )"
);
}
#[test]
fn test_select_wildcard_with_replace() {
let select = bigquery_and_generic()
.verified_only_select(r#"SELECT * REPLACE ('widget' AS item_name) FROM orders"#);
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_replace: Some(ReplaceSelectItem {
items: vec![Box::new(ReplaceSelectElement {
expr: Expr::Value(Value::SingleQuotedString("widget".to_owned())),
column_name: Ident::new("item_name"),
as_keyword: true,
})],
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
let select = bigquery_and_generic().verified_only_select(
r#"SELECT * REPLACE (quantity / 2 AS quantity, 3 AS order_id) FROM orders"#,
);
let expected = SelectItem::Wildcard(WildcardAdditionalOptions {
opt_replace: Some(ReplaceSelectItem {
items: vec![
Box::new(ReplaceSelectElement {
expr: Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("quantity"))),
op: BinaryOperator::Divide,
#[cfg(not(feature = "bigdecimal"))]
right: Box::new(Expr::Value(Value::Number("2".to_string(), false))),
#[cfg(feature = "bigdecimal")]
right: Box::new(Expr::Value(Value::Number(
BigDecimal::from_str("2").unwrap(),
false,
))),
},
column_name: Ident::new("quantity"),
as_keyword: true,
}),
Box::new(ReplaceSelectElement {
#[cfg(not(feature = "bigdecimal"))]
expr: Expr::Value(Value::Number("3".to_string(), false)),
#[cfg(feature = "bigdecimal")]
expr: Expr::Value(Value::Number(BigDecimal::from_str("3").unwrap(), false)),
column_name: Ident::new("order_id"),
as_keyword: true,
}),
],
}),
..Default::default()
});
assert_eq!(expected, select.projection[0]);
}
fn bigquery() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(BigQueryDialect {})],
}
}
fn bigquery_and_generic() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(BigQueryDialect {}), Box::new(GenericDialect {})],
}
}
#[test]
fn parse_map_access_offset() {
let sql = "SELECT d[offset(0)]";
let _select = bigquery().verified_only_select(sql);
#[cfg(not(feature = "bigdecimal"))]
assert_eq!(
_select.projection[0],
SelectItem::UnnamedExpr(Expr::MapAccess {
column: Box::new(Expr::Identifier(Ident {
value: "d".to_string(),
quote_style: None,
})),
keys: vec![Expr::Function(Function {
name: ObjectName(vec!["offset".into()]),
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
Value::Number("0".into(), false)
))),],
over: None,
distinct: false,
special: false,
})],
})
);
// test other operators
for sql in [
"SELECT d[SAFE_OFFSET(0)]",
"SELECT d[ORDINAL(0)]",
"SELECT d[SAFE_ORDINAL(0)]",
] {
bigquery().verified_only_select(sql);
}
}