mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00

Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
409 lines
13 KiB
Rust
409 lines
13 KiB
Rust
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you 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::tokenizer::Span;
|
|
use test_utils::*;
|
|
|
|
use sqlparser::ast::*;
|
|
use sqlparser::dialect::GenericDialect;
|
|
use sqlparser::dialect::RedshiftSqlDialect;
|
|
|
|
#[test]
|
|
fn test_square_brackets_over_db_schema_table_name() {
|
|
let select = redshift().verified_only_select("SELECT [col1] FROM [test_schema].[test_table]");
|
|
assert_eq!(
|
|
select.projection[0],
|
|
SelectItem::UnnamedExpr(Expr::Identifier(Ident {
|
|
value: "col1".to_string(),
|
|
quote_style: Some('['),
|
|
span: Span::empty(),
|
|
})),
|
|
);
|
|
assert_eq!(
|
|
select.from[0],
|
|
TableWithJoins {
|
|
relation: table_from_name(ObjectName::from(vec![
|
|
Ident {
|
|
value: "test_schema".to_string(),
|
|
quote_style: Some('['),
|
|
span: Span::empty(),
|
|
},
|
|
Ident {
|
|
value: "test_table".to_string(),
|
|
quote_style: Some('['),
|
|
span: Span::empty(),
|
|
}
|
|
])),
|
|
joins: vec![],
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn brackets_over_db_schema_table_name_with_whites_paces() {
|
|
match redshift().parse_sql_statements("SELECT [ col1 ] FROM [ test_schema].[ test_table]") {
|
|
Ok(statements) => {
|
|
assert_eq!(statements.len(), 1);
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_double_quotes_over_db_schema_table_name() {
|
|
let select =
|
|
redshift().verified_only_select("SELECT \"col1\" FROM \"test_schema\".\"test_table\"");
|
|
assert_eq!(
|
|
select.projection[0],
|
|
SelectItem::UnnamedExpr(Expr::Identifier(Ident {
|
|
value: "col1".to_string(),
|
|
quote_style: Some('"'),
|
|
span: Span::empty(),
|
|
})),
|
|
);
|
|
assert_eq!(
|
|
select.from[0],
|
|
TableWithJoins {
|
|
relation: table_from_name(ObjectName::from(vec![
|
|
Ident {
|
|
value: "test_schema".to_string(),
|
|
quote_style: Some('"'),
|
|
span: Span::empty(),
|
|
},
|
|
Ident {
|
|
value: "test_table".to_string(),
|
|
quote_style: Some('"'),
|
|
span: Span::empty(),
|
|
}
|
|
])),
|
|
joins: vec![],
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_delimited_identifiers() {
|
|
// check that quoted identifiers in any position remain quoted after serialization
|
|
let select = redshift().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,
|
|
version,
|
|
..
|
|
} => {
|
|
assert_eq!(
|
|
ObjectName::from(vec![Ident::with_quote('"', "a table")]),
|
|
name
|
|
);
|
|
assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name);
|
|
assert!(args.is_none());
|
|
assert!(with_hints.is_empty());
|
|
assert!(version.is_none());
|
|
}
|
|
_ => 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::from(vec![Ident::with_quote('"', "myfun")]),
|
|
uses_odbc_syntax: false,
|
|
parameters: FunctionArguments::None,
|
|
args: FunctionArguments::List(FunctionArgumentList {
|
|
duplicate_treatment: None,
|
|
args: vec![],
|
|
clauses: vec![],
|
|
}),
|
|
null_treatment: None,
|
|
filter: None,
|
|
over: None,
|
|
within_group: vec![],
|
|
}),
|
|
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"),
|
|
}
|
|
|
|
redshift().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#);
|
|
// An alias starting with a number
|
|
redshift().verified_stmt(r#"CREATE TABLE "foo" ("1" INT)"#);
|
|
redshift().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#);
|
|
//TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#);
|
|
}
|
|
|
|
fn redshift() -> TestedDialects {
|
|
TestedDialects::new(vec![Box::new(RedshiftSqlDialect {})])
|
|
}
|
|
|
|
fn redshift_and_generic() -> TestedDialects {
|
|
TestedDialects::new(vec![
|
|
Box::new(RedshiftSqlDialect {}),
|
|
Box::new(GenericDialect {}),
|
|
])
|
|
}
|
|
|
|
#[test]
|
|
fn test_sharp() {
|
|
let sql = "SELECT #_of_values";
|
|
let select = redshift().verified_only_select(sql);
|
|
assert_eq!(
|
|
SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("#_of_values"))),
|
|
select.projection[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_view_with_no_schema_binding() {
|
|
redshift_and_generic()
|
|
.verified_stmt("CREATE VIEW myevent AS SELECT eventname FROM event WITH NO SCHEMA BINDING");
|
|
}
|
|
|
|
#[test]
|
|
fn test_redshift_json_path() {
|
|
let dialects = all_dialects_where(|d| d.supports_partiql());
|
|
let sql = "SELECT cust.c_orders[0].o_orderkey FROM customer_orders_lineitem";
|
|
let select = dialects.verified_only_select(sql);
|
|
|
|
assert_eq!(
|
|
&Expr::JsonAccess {
|
|
value: Box::new(Expr::CompoundIdentifier(vec![
|
|
Ident::new("cust"),
|
|
Ident::new("c_orders")
|
|
])),
|
|
path: JsonPath {
|
|
path: vec![
|
|
JsonPathElem::Bracket {
|
|
key: Expr::value(number("0"))
|
|
},
|
|
JsonPathElem::Dot {
|
|
key: "o_orderkey".to_string(),
|
|
quoted: false
|
|
}
|
|
]
|
|
}
|
|
},
|
|
expr_from_projection(only(&select.projection))
|
|
);
|
|
|
|
let sql = "SELECT cust.c_orders[0]['id'] FROM customer_orders_lineitem";
|
|
let select = dialects.verified_only_select(sql);
|
|
assert_eq!(
|
|
&Expr::JsonAccess {
|
|
value: Box::new(Expr::CompoundIdentifier(vec![
|
|
Ident::new("cust"),
|
|
Ident::new("c_orders")
|
|
])),
|
|
path: JsonPath {
|
|
path: vec![
|
|
JsonPathElem::Bracket {
|
|
key: Expr::value(number("0"))
|
|
},
|
|
JsonPathElem::Bracket {
|
|
key: Expr::Value(
|
|
(Value::SingleQuotedString("id".to_owned())).with_empty_span()
|
|
)
|
|
}
|
|
]
|
|
}
|
|
},
|
|
expr_from_projection(only(&select.projection))
|
|
);
|
|
|
|
let sql = "SELECT db1.sc1.tbl1.col1[0]['id'] FROM customer_orders_lineitem";
|
|
let select = dialects.verified_only_select(sql);
|
|
assert_eq!(
|
|
&Expr::JsonAccess {
|
|
value: Box::new(Expr::CompoundIdentifier(vec![
|
|
Ident::new("db1"),
|
|
Ident::new("sc1"),
|
|
Ident::new("tbl1"),
|
|
Ident::new("col1")
|
|
])),
|
|
path: JsonPath {
|
|
path: vec![
|
|
JsonPathElem::Bracket {
|
|
key: Expr::value(number("0"))
|
|
},
|
|
JsonPathElem::Bracket {
|
|
key: Expr::Value(
|
|
(Value::SingleQuotedString("id".to_owned())).with_empty_span()
|
|
)
|
|
}
|
|
]
|
|
}
|
|
},
|
|
expr_from_projection(only(&select.projection))
|
|
);
|
|
|
|
let sql = r#"SELECT db1.sc1.tbl1.col1[0]."id" FROM customer_orders_lineitem"#;
|
|
let select = dialects.verified_only_select(sql);
|
|
assert_eq!(
|
|
&Expr::JsonAccess {
|
|
value: Box::new(Expr::CompoundIdentifier(vec![
|
|
Ident::new("db1"),
|
|
Ident::new("sc1"),
|
|
Ident::new("tbl1"),
|
|
Ident::new("col1")
|
|
])),
|
|
path: JsonPath {
|
|
path: vec![
|
|
JsonPathElem::Bracket {
|
|
key: Expr::value(number("0"))
|
|
},
|
|
JsonPathElem::Dot {
|
|
key: "id".to_string(),
|
|
quoted: true,
|
|
}
|
|
]
|
|
}
|
|
},
|
|
expr_from_projection(only(&select.projection))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_json_path_from() {
|
|
let dialects = all_dialects_where(|d| d.supports_partiql());
|
|
let select = dialects.verified_only_select("SELECT * FROM src[0].a AS a");
|
|
match &select.from[0].relation {
|
|
TableFactor::Table {
|
|
name, json_path, ..
|
|
} => {
|
|
assert_eq!(name, &ObjectName::from(vec![Ident::new("src")]));
|
|
assert_eq!(
|
|
json_path,
|
|
&Some(JsonPath {
|
|
path: vec![
|
|
JsonPathElem::Bracket {
|
|
key: Expr::value(number("0"))
|
|
},
|
|
JsonPathElem::Dot {
|
|
key: "a".to_string(),
|
|
quoted: false
|
|
}
|
|
]
|
|
})
|
|
);
|
|
}
|
|
_ => panic!(),
|
|
}
|
|
|
|
let select = dialects.verified_only_select("SELECT * FROM src[0].a[1].b AS a");
|
|
match &select.from[0].relation {
|
|
TableFactor::Table {
|
|
name, json_path, ..
|
|
} => {
|
|
assert_eq!(name, &ObjectName::from(vec![Ident::new("src")]));
|
|
assert_eq!(
|
|
json_path,
|
|
&Some(JsonPath {
|
|
path: vec![
|
|
JsonPathElem::Bracket {
|
|
key: Expr::value(number("0"))
|
|
},
|
|
JsonPathElem::Dot {
|
|
key: "a".to_string(),
|
|
quoted: false
|
|
},
|
|
JsonPathElem::Bracket {
|
|
key: Expr::Value(
|
|
(Value::Number("1".parse().unwrap(), false)).with_empty_span()
|
|
)
|
|
},
|
|
JsonPathElem::Dot {
|
|
key: "b".to_string(),
|
|
quoted: false
|
|
},
|
|
]
|
|
})
|
|
);
|
|
}
|
|
_ => panic!(),
|
|
}
|
|
|
|
let select = dialects.verified_only_select("SELECT * FROM src.a.b");
|
|
match &select.from[0].relation {
|
|
TableFactor::Table {
|
|
name, json_path, ..
|
|
} => {
|
|
assert_eq!(
|
|
name,
|
|
&ObjectName::from(vec![Ident::new("src"), Ident::new("a"), Ident::new("b")])
|
|
);
|
|
assert_eq!(json_path, &None);
|
|
}
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_select_numbered_columns() {
|
|
// An alias starting with a number
|
|
redshift_and_generic().verified_stmt(r#"SELECT 1 AS "1" FROM a"#);
|
|
redshift_and_generic().verified_stmt(r#"SELECT 1 AS "1abc" FROM a"#);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_nested_quoted_identifier() {
|
|
redshift().verified_stmt(r#"SELECT 1 AS ["1"] FROM a"#);
|
|
redshift().verified_stmt(r#"SELECT 1 AS ["[="] FROM a"#);
|
|
redshift().verified_stmt(r#"SELECT 1 AS ["=]"] FROM a"#);
|
|
redshift().verified_stmt(r#"SELECT 1 AS ["a[b]"] FROM a"#);
|
|
// trim spaces
|
|
redshift().one_statement_parses_to(r#"SELECT 1 AS [ " 1 " ]"#, r#"SELECT 1 AS [" 1 "]"#);
|
|
// invalid query
|
|
assert!(redshift()
|
|
.parse_sql_statements(r#"SELECT 1 AS ["1]"#)
|
|
.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_extract_single_quotes() {
|
|
let sql = "SELECT EXTRACT('month' FROM my_timestamp) FROM my_table";
|
|
redshift().verified_stmt(sql);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_string_literal_backslash_escape() {
|
|
redshift().one_statement_parses_to(r#"SELECT 'l\'auto'"#, "SELECT 'l''auto'");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_utf8_multibyte_idents() {
|
|
redshift().verified_stmt("SELECT 🚀.city AS 🎸 FROM customers AS 🚀");
|
|
}
|