| // 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, |
| 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 1,", "SELECT 1"), |
| ("SELECT 1,2,", "SELECT 1, 2"), |
| ("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 {})], |
| options: None, |
| } |
| } |
| |
| fn bigquery_and_generic() -> TestedDialects { |
| TestedDialects { |
| dialects: vec![Box::new(BigQueryDialect {}), Box::new(GenericDialect {})], |
| options: None, |
| } |
| } |
| |
| #[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, |
| order_by: vec![], |
| })], |
| }) |
| ); |
| |
| // 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); |
| } |
| } |