mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-31 20:22:17 +00:00
458 lines
15 KiB
Rust
458 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.
|
|
|
|
#![warn(clippy::all)]
|
|
//! Test SQL syntax specific to Snowflake. The parser based on the
|
|
//! generic dialect is also tested (on the inputs it can handle).
|
|
|
|
use sqlparser::ast::*;
|
|
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
|
|
use sqlparser::parser::ParserError;
|
|
use sqlparser::tokenizer::*;
|
|
use test_utils::*;
|
|
|
|
#[macro_use]
|
|
mod test_utils;
|
|
|
|
#[test]
|
|
fn test_snowflake_create_table() {
|
|
let sql = "CREATE TABLE _my_$table (am00unt number)";
|
|
match snowflake_and_generic().verified_stmt(sql) {
|
|
Statement::CreateTable { name, .. } => {
|
|
assert_eq!("_my_$table", name.to_string());
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_snowflake_single_line_tokenize() {
|
|
let sql = "CREATE TABLE# this is a comment \ntable_1";
|
|
let dialect = SnowflakeDialect {};
|
|
let mut tokenizer = Tokenizer::new(&dialect, sql);
|
|
let tokens = tokenizer.tokenize().unwrap();
|
|
|
|
let expected = vec![
|
|
Token::make_keyword("CREATE"),
|
|
Token::Whitespace(Whitespace::Space),
|
|
Token::make_keyword("TABLE"),
|
|
Token::Whitespace(Whitespace::SingleLineComment {
|
|
prefix: "#".to_string(),
|
|
comment: " this is a comment \n".to_string(),
|
|
}),
|
|
Token::make_word("table_1", None),
|
|
];
|
|
|
|
assert_eq!(expected, tokens);
|
|
|
|
let sql = "CREATE TABLE// this is a comment \ntable_1";
|
|
let mut tokenizer = Tokenizer::new(&dialect, sql);
|
|
let tokens = tokenizer.tokenize().unwrap();
|
|
|
|
let expected = vec![
|
|
Token::make_keyword("CREATE"),
|
|
Token::Whitespace(Whitespace::Space),
|
|
Token::make_keyword("TABLE"),
|
|
Token::Whitespace(Whitespace::SingleLineComment {
|
|
prefix: "//".to_string(),
|
|
comment: " this is a comment \n".to_string(),
|
|
}),
|
|
Token::make_word("table_1", None),
|
|
];
|
|
|
|
assert_eq!(expected, tokens);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sf_derived_table_in_parenthesis() {
|
|
// Nesting a subquery in an extra set of parentheses is non-standard,
|
|
// but supported in Snowflake SQL
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM ((SELECT 1) AS t)",
|
|
"SELECT * FROM (SELECT 1) AS t",
|
|
);
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (((SELECT 1) AS t))",
|
|
"SELECT * FROM (SELECT 1) AS t",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_table_in_parenthesis() {
|
|
// Parenthesized table names are non-standard, but supported in Snowflake SQL
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN (b))",
|
|
"SELECT * FROM (a NATURAL JOIN b)",
|
|
);
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN ((b)))",
|
|
"SELECT * FROM (a NATURAL JOIN b)",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_table_in_parenthesis_with_alias() {
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN (b) c )",
|
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
|
);
|
|
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN ((b)) c )",
|
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
|
);
|
|
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN ( (b) c ) )",
|
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
|
);
|
|
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN ( (b) as c ) )",
|
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
|
);
|
|
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a alias1 NATURAL JOIN ( (b) c ) )",
|
|
"SELECT * FROM (a AS alias1 NATURAL JOIN b AS c)",
|
|
);
|
|
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a as alias1 NATURAL JOIN ( (b) as c ) )",
|
|
"SELECT * FROM (a AS alias1 NATURAL JOIN b AS c)",
|
|
);
|
|
|
|
snowflake_and_generic().one_statement_parses_to(
|
|
"SELECT * FROM (a NATURAL JOIN b) c",
|
|
"SELECT * FROM (a NATURAL JOIN b) AS c",
|
|
);
|
|
|
|
let res = snowflake().parse_sql_statements("SELECT * FROM (a b) c");
|
|
assert_eq!(
|
|
ParserError::ParserError("duplicate alias b".to_string()),
|
|
res.unwrap_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_array() {
|
|
let sql = "SELECT CAST(a AS ARRAY) FROM customer";
|
|
let select = snowflake().verified_only_select(sql);
|
|
assert_eq!(
|
|
&Expr::Cast {
|
|
expr: Box::new(Expr::Identifier(Ident::new("a"))),
|
|
data_type: DataType::Array(None),
|
|
},
|
|
expr_from_projection(only(&select.projection))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_json_using_colon() {
|
|
let sql = "SELECT a:b FROM t";
|
|
let select = snowflake().verified_only_select(sql);
|
|
assert_eq!(
|
|
SelectItem::UnnamedExpr(Expr::JsonAccess {
|
|
left: Box::new(Expr::Identifier(Ident::new("a"))),
|
|
operator: JsonOperator::Colon,
|
|
right: Box::new(Expr::Value(Value::UnQuotedString("b".to_string()))),
|
|
}),
|
|
select.projection[0]
|
|
);
|
|
|
|
let sql = "SELECT a:type FROM t";
|
|
let select = snowflake().verified_only_select(sql);
|
|
assert_eq!(
|
|
SelectItem::UnnamedExpr(Expr::JsonAccess {
|
|
left: Box::new(Expr::Identifier(Ident::new("a"))),
|
|
operator: JsonOperator::Colon,
|
|
right: Box::new(Expr::Value(Value::UnQuotedString("type".to_string()))),
|
|
}),
|
|
select.projection[0]
|
|
);
|
|
|
|
let sql = "SELECT a:location FROM t";
|
|
let select = snowflake().verified_only_select(sql);
|
|
assert_eq!(
|
|
SelectItem::UnnamedExpr(Expr::JsonAccess {
|
|
left: Box::new(Expr::Identifier(Ident::new("a"))),
|
|
operator: JsonOperator::Colon,
|
|
right: Box::new(Expr::Value(Value::UnQuotedString("location".to_string()))),
|
|
}),
|
|
select.projection[0]
|
|
);
|
|
|
|
snowflake().one_statement_parses_to("SELECT a:b::int FROM t", "SELECT CAST(a:b AS INT) FROM t");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_delimited_identifiers() {
|
|
// check that quoted identifiers in any position remain quoted after serialization
|
|
let select = snowflake().verified_only_select(
|
|
r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#,
|
|
);
|
|
// check FROM
|
|
match only(select.from).relation {
|
|
TableFactor::Table {
|
|
name,
|
|
alias,
|
|
args,
|
|
with_hints,
|
|
} => {
|
|
assert_eq!(vec![Ident::with_quote('"', "a table")], name.0);
|
|
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
|
|
assert!(args.is_none());
|
|
assert!(with_hints.is_empty());
|
|
}
|
|
_ => panic!("Expecting TableFactor::Table"),
|
|
}
|
|
// check SELECT
|
|
assert_eq!(3, select.projection.len());
|
|
assert_eq!(
|
|
&Expr::CompoundIdentifier(vec![
|
|
Ident::with_quote('"', "alias"),
|
|
Ident::with_quote('"', "bar baz"),
|
|
]),
|
|
expr_from_projection(&select.projection[0]),
|
|
);
|
|
assert_eq!(
|
|
&Expr::Function(Function {
|
|
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
|
|
args: vec![],
|
|
over: None,
|
|
distinct: false,
|
|
special: false,
|
|
}),
|
|
expr_from_projection(&select.projection[1]),
|
|
);
|
|
match &select.projection[2] {
|
|
SelectItem::ExprWithAlias { expr, alias } => {
|
|
assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr);
|
|
assert_eq!(&Ident::with_quote('"', "column alias"), alias);
|
|
}
|
|
_ => panic!("Expected ExprWithAlias"),
|
|
}
|
|
|
|
snowflake().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#);
|
|
snowflake().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#);
|
|
//TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_like() {
|
|
fn chk(negated: bool) {
|
|
let sql = &format!(
|
|
"SELECT * FROM customers WHERE name {}LIKE '%a'",
|
|
if negated { "NOT " } else { "" }
|
|
);
|
|
let select = snowflake().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 = snowflake().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 = snowflake().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 = snowflake().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 = snowflake().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 = snowflake().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 test_array_agg_func() {
|
|
for sql in [
|
|
"SELECT ARRAY_AGG(x) WITHIN GROUP (ORDER BY x) AS a FROM T",
|
|
"SELECT ARRAY_AGG(DISTINCT x) WITHIN GROUP (ORDER BY x ASC) FROM tbl",
|
|
] {
|
|
snowflake().verified_stmt(sql);
|
|
}
|
|
|
|
let sql = "select array_agg(x order by x) as a from T";
|
|
let result = snowflake().parse_sql_statements(sql);
|
|
assert_eq!(
|
|
result,
|
|
Err(ParserError::ParserError(String::from(
|
|
"Expected ), found: order"
|
|
)))
|
|
)
|
|
}
|
|
|
|
fn snowflake() -> TestedDialects {
|
|
TestedDialects {
|
|
dialects: vec![Box::new(SnowflakeDialect {})],
|
|
}
|
|
}
|
|
|
|
fn snowflake_and_generic() -> TestedDialects {
|
|
TestedDialects {
|
|
dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_select_wildcard_with_exclude() {
|
|
match snowflake_and_generic().verified_stmt("SELECT * EXCLUDE (col_a) FROM data") {
|
|
Statement::Query(query) => match *query.body {
|
|
SetExpr::Select(select) => match &select.projection[0] {
|
|
SelectItem::Wildcard(WildcardAdditionalOptions {
|
|
opt_exclude: Some(exclude),
|
|
..
|
|
}) => {
|
|
assert_eq!(
|
|
*exclude,
|
|
ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])
|
|
)
|
|
}
|
|
_ => unreachable!(),
|
|
},
|
|
_ => unreachable!(),
|
|
},
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
match snowflake_and_generic()
|
|
.verified_stmt("SELECT name.* EXCLUDE department_id FROM employee_table")
|
|
{
|
|
Statement::Query(query) => match *query.body {
|
|
SetExpr::Select(select) => match &select.projection[0] {
|
|
SelectItem::QualifiedWildcard(
|
|
_,
|
|
WildcardAdditionalOptions {
|
|
opt_exclude: Some(exclude),
|
|
..
|
|
},
|
|
) => {
|
|
assert_eq!(
|
|
*exclude,
|
|
ExcludeSelectItem::Single(Ident::new("department_id"))
|
|
)
|
|
}
|
|
_ => unreachable!(),
|
|
},
|
|
_ => unreachable!(),
|
|
},
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
match snowflake_and_generic()
|
|
.verified_stmt("SELECT * EXCLUDE (department_id, employee_id) FROM employee_table")
|
|
{
|
|
Statement::Query(query) => match *query.body {
|
|
SetExpr::Select(select) => match &select.projection[0] {
|
|
SelectItem::Wildcard(WildcardAdditionalOptions {
|
|
opt_exclude: Some(exclude),
|
|
..
|
|
}) => {
|
|
assert_eq!(
|
|
*exclude,
|
|
ExcludeSelectItem::Multiple(vec![
|
|
Ident::new("department_id"),
|
|
Ident::new("employee_id")
|
|
])
|
|
)
|
|
}
|
|
_ => unreachable!(),
|
|
},
|
|
_ => unreachable!(),
|
|
},
|
|
_ => unreachable!(),
|
|
};
|
|
}
|