// 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 std::ops::Deref; use sqlparser::ast::*; use sqlparser::dialect::{BigQueryDialect, GenericDialect}; use sqlparser::parser::{ParserError, ParserOptions}; use test_utils::*; #[test] fn parse_literal_string() { let sql = concat!( "SELECT ", "'single', ", r#""double", "#, "'''triple-single''', ", r#""""triple-double""", "#, r#"'single\'escaped', "#, r#"'''triple-single\'escaped''', "#, r#"'''triple-single'unescaped''', "#, r#""double\"escaped", "#, r#""""triple-double\"escaped""", "#, r#""""triple-double"unescaped""""#, ); let dialect = TestedDialects { dialects: vec![Box::new(BigQueryDialect {})], options: Some(ParserOptions::new().with_unescape(false)), }; let select = dialect.verified_only_select(sql); assert_eq!(10, 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]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedString("triple-single".to_string())), expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Value(Value::TripleDoubleQuotedString("triple-double".to_string())), expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Value(Value::SingleQuotedString(r#"single\'escaped"#.to_string())), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedString( r#"triple-single\'escaped"#.to_string() )), expr_from_projection(&select.projection[5]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedString( r#"triple-single'unescaped"#.to_string() )), expr_from_projection(&select.projection[6]) ); assert_eq!( &Expr::Value(Value::DoubleQuotedString(r#"double\"escaped"#.to_string())), expr_from_projection(&select.projection[7]) ); assert_eq!( &Expr::Value(Value::TripleDoubleQuotedString( r#"triple-double\"escaped"#.to_string() )), expr_from_projection(&select.projection[8]) ); assert_eq!( &Expr::Value(Value::TripleDoubleQuotedString( r#"triple-double"unescaped"#.to_string() )), expr_from_projection(&select.projection[9]) ); } #[test] fn parse_byte_literal() { let sql = concat!( "SELECT ", "B'abc', ", r#"B"abc", "#, r#"B'f\(abc,(.*),def\)', "#, r#"B"f\(abc,(.*),def\)", "#, r#"B'''abc''', "#, r#"B"""abc""""#, ); let stmt = bigquery().verified_stmt(sql); if let Statement::Query(query) = stmt { if let SetExpr::Select(select) = *query.body { assert_eq!(6, 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]) ); assert_eq!( &Expr::Value(Value::SingleQuotedByteStringLiteral( r"f\(abc,(.*),def\)".to_string() )), expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Value(Value::DoubleQuotedByteStringLiteral( r"f\(abc,(.*),def\)".to_string() )), expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedByteStringLiteral( r"abc".to_string() )), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value(Value::TripleDoubleQuotedByteStringLiteral( r"abc".to_string() )), expr_from_projection(&select.projection[5]) ); } } else { panic!("invalid query"); } bigquery().one_statement_parses_to( r#"SELECT b'123', b"123", b'''123''', b"""123""""#, r#"SELECT B'123', B"123", B'''123''', B"""123""""#, ); } #[test] fn parse_raw_literal() { let sql = concat!( "SELECT ", "R'abc', ", r#"R"abc", "#, r#"R'f\(abc,(.*),def\)', "#, r#"R"f\(abc,(.*),def\)", "#, r#"R'''abc''', "#, r#"R"""abc""""#, ); let stmt = bigquery().verified_stmt(sql); if let Statement::Query(query) = stmt { if let SetExpr::Select(select) = *query.body { assert_eq!(6, select.projection.len()); assert_eq!( &Expr::Value(Value::SingleQuotedRawStringLiteral("abc".to_string())), expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Value(Value::DoubleQuotedRawStringLiteral("abc".to_string())), expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Value(Value::SingleQuotedRawStringLiteral( r"f\(abc,(.*),def\)".to_string() )), expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Value(Value::DoubleQuotedRawStringLiteral( r"f\(abc,(.*),def\)".to_string() )), expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedRawStringLiteral( r"abc".to_string() )), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value(Value::TripleDoubleQuotedRawStringLiteral( r"abc".to_string() )), expr_from_projection(&select.projection[5]) ); } } else { panic!("invalid query"); } bigquery().one_statement_parses_to( r#"SELECT r'123', r"123", r'''123''', r"""123""""#, r#"SELECT R'123', R"123", R'''123''', R"""123""""#, ); } #[test] fn parse_delete_statement() { let sql = "DELETE \"table\" WHERE 1"; match bigquery_and_generic().verified_stmt(sql) { Statement::Delete(Delete { from: FromTable::WithoutKeyword(from), .. }) => { assert_eq!( TableFactor::Table { name: ObjectName(vec![Ident::with_quote('"', "table")]), alias: None, args: None, with_hints: vec![], version: None, partitions: vec![], with_ordinality: false, }, from[0].relation ); } _ => unreachable!(), } } #[test] fn parse_create_view_with_options() { let sql = concat!( "CREATE VIEW myproject.mydataset.newview ", r#"(name, age OPTIONS(description = "field age")) "#, r#"OPTIONS(expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 48 HOUR), "#, r#"friendly_name = "newview", description = "a view that expires in 2 days", labels = [("org_unit", "development")]) "#, "AS SELECT column_1, column_2, column_3 FROM myproject.mydataset.mytable", ); match bigquery().verified_stmt(sql) { Statement::CreateView { name, query, options, columns, .. } => { assert_eq!( name, ObjectName(vec![ "myproject".into(), "mydataset".into(), "newview".into() ]) ); assert_eq!( vec![ ViewColumnDef { name: Ident::new("name"), data_type: None, options: None, }, ViewColumnDef { name: Ident::new("age"), data_type: None, options: Some(vec![SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString("field age".to_string())), }]) }, ], columns ); assert_eq!( "SELECT column_1, column_2, column_3 FROM myproject.mydataset.mytable", query.to_string() ); assert_eq!( r#"OPTIONS(expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 48 HOUR), friendly_name = "newview", description = "a view that expires in 2 days", labels = [("org_unit", "development")])"#, options.to_string() ); let CreateTableOptions::Options(options) = options else { unreachable!() }; assert_eq!( &SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString( "a view that expires in 2 days".to_string() )), }, &options[2], ); } _ => unreachable!(), } } #[test] fn parse_create_view_if_not_exists() { let sql = "CREATE VIEW IF NOT EXISTS mydataset.newview AS SELECT foo FROM bar"; match bigquery().verified_stmt(sql) { Statement::CreateView { name, columns, query, or_replace, materialized, options, cluster_by, comment, with_no_schema_binding: late_binding, if_not_exists, temporary, .. } => { assert_eq!("mydataset.newview", name.to_string()); assert_eq!(Vec::::new(), columns); assert_eq!("SELECT foo FROM bar", query.to_string()); assert!(!materialized); assert!(!or_replace); assert_eq!(options, CreateTableOptions::None); assert_eq!(cluster_by, vec![]); assert!(comment.is_none()); assert!(!late_binding); assert!(if_not_exists); assert!(!temporary); } _ => unreachable!(), } } #[test] fn parse_create_view_with_unquoted_hyphen() { let sql = "CREATE VIEW IF NOT EXISTS my-pro-ject.mydataset.myview AS SELECT 1"; match bigquery().verified_stmt(sql) { Statement::CreateView { name, query, if_not_exists, .. } => { assert_eq!("my-pro-ject.mydataset.myview", name.to_string()); assert_eq!("SELECT 1", query.to_string()); assert!(if_not_exists); } _ => unreachable!(), } } #[test] fn parse_create_table_with_unquoted_hyphen() { let sql = "CREATE TABLE my-pro-ject.mydataset.mytable (x INT64)"; match bigquery().verified_stmt(sql) { Statement::CreateTable(CreateTable { name, columns, .. }) => { assert_eq!( name, ObjectName(vec![ "my-pro-ject".into(), "mydataset".into(), "mytable".into() ]) ); assert_eq!( vec![ColumnDef { name: Ident::new("x"), data_type: DataType::Int64, collation: None, options: vec![] },], columns ); } _ => unreachable!(), } } #[test] fn parse_create_table_with_options() { let sql = concat!( "CREATE TABLE mydataset.newtable ", r#"(x INT64 NOT NULL OPTIONS(description = "field x"), "#, r#"y BOOL OPTIONS(description = "field y")) "#, "PARTITION BY _PARTITIONDATE ", "CLUSTER BY userid, age ", r#"OPTIONS(partition_expiration_days = 1, description = "table option description")"# ); match bigquery().verified_stmt(sql) { Statement::CreateTable(CreateTable { name, columns, partition_by, cluster_by, options, .. }) => { assert_eq!( name, ObjectName(vec!["mydataset".into(), "newtable".into()]) ); assert_eq!( vec![ ColumnDef { name: Ident::new("x"), data_type: DataType::Int64, collation: None, options: vec![ ColumnOptionDef { name: None, option: ColumnOption::NotNull, }, ColumnOptionDef { name: None, option: ColumnOption::Options(vec![SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString( "field x".to_string() )), },]) }, ] }, ColumnDef { name: Ident::new("y"), data_type: DataType::Bool, collation: None, options: vec![ColumnOptionDef { name: None, option: ColumnOption::Options(vec![SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString( "field y".to_string() )), },]) }] }, ], columns ); assert_eq!( ( Some(Box::new(Expr::Identifier(Ident::new("_PARTITIONDATE")))), Some(WrappedCollection::NoWrapping(vec![ Ident::new("userid"), Ident::new("age"), ])), Some(vec![ SqlOption::KeyValue { key: Ident::new("partition_expiration_days"), value: Expr::Value(number("1")), }, SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString( "table option description".to_string() )), }, ]) ), (partition_by, cluster_by, options) ) } _ => unreachable!(), } let sql = concat!( "CREATE TABLE mydataset.newtable ", r#"(x INT64 NOT NULL OPTIONS(description = "field x"), "#, r#"y BOOL OPTIONS(description = "field y")) "#, "CLUSTER BY userid ", r#"OPTIONS(partition_expiration_days = 1, "#, r#"description = "table option description")"# ); bigquery().verified_stmt(sql); } #[test] fn parse_nested_data_types() { let sql = "CREATE TABLE table (x STRUCT, b BYTES(42)>, y ARRAY>)"; match bigquery_and_generic().one_statement_parses_to(sql, sql) { Statement::CreateTable(CreateTable { name, columns, .. }) => { assert_eq!(name, ObjectName(vec!["table".into()])); assert_eq!( columns, vec![ ColumnDef { name: Ident::new("x"), data_type: DataType::Struct( vec![ StructField { field_name: Some("a".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket( Box::new(DataType::Int64,) )) }, StructField { field_name: Some("b".into()), field_type: DataType::Bytes(Some(42)) }, ], StructBracketKind::AngleBrackets ), collation: None, options: vec![], }, ColumnDef { name: Ident::new("y"), data_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Struct( vec![StructField { field_name: None, field_type: DataType::Int64, }], StructBracketKind::AngleBrackets ), ))), collation: None, options: vec![], }, ] ); } _ => unreachable!(), } } #[test] fn parse_invalid_brackets() { let sql = "SELECT STRUCT>(NULL)"; assert_eq!( bigquery_and_generic() .parse_sql_statements(sql) .unwrap_err(), ParserError::ParserError("unmatched > in STRUCT literal".to_string()) ); let sql = "SELECT STRUCT>>(NULL)"; assert_eq!( bigquery_and_generic() .parse_sql_statements(sql) .unwrap_err(), ParserError::ParserError("Expected: (, found: >".to_string()) ); let sql = "CREATE TABLE table (x STRUCT>>)"; assert_eq!( bigquery_and_generic() .parse_sql_statements(sql) .unwrap_err(), ParserError::ParserError( "Expected: ',' or ')' after column definition, found: >".to_string() ) ); } #[test] fn parse_tuple_struct_literal() { // tuple syntax: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#tuple_syntax // syntax: (expr1, expr2 [, ... ]) let sql = "SELECT (1, 2, 3), (1, 1.0, '123', true)"; let select = bigquery().verified_only_select(sql); assert_eq!(2, select.projection.len()); assert_eq!( &Expr::Tuple(vec![ Expr::Value(number("1")), Expr::Value(number("2")), Expr::Value(number("3")), ]), expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Tuple(vec![ Expr::Value(number("1")), Expr::Value(number("1.0")), Expr::Value(Value::SingleQuotedString("123".to_string())), Expr::Value(Value::Boolean(true)) ]), expr_from_projection(&select.projection[1]) ); } #[test] fn parse_typeless_struct_syntax() { // typeless struct syntax https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#typeless_struct_syntax // syntax: STRUCT( expr1 [AS field_name] [, ... ]) let sql = "SELECT STRUCT(1, 2, 3), STRUCT('abc'), STRUCT(1, t.str_col), STRUCT(1 AS a, 'abc' AS b), STRUCT(str_col AS abc)"; let select = bigquery_and_generic().verified_only_select(sql); assert_eq!(5, select.projection.len()); assert_eq!( &Expr::Struct { values: vec![ Expr::Value(number("1")), Expr::Value(number("2")), Expr::Value(number("3")), ], fields: Default::default() }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Value(Value::SingleQuotedString("abc".to_string())),], fields: Default::default() }, expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Struct { values: vec![ Expr::Value(number("1")), Expr::CompoundIdentifier(vec![Ident::from("t"), Ident::from("str_col")]), ], fields: Default::default() }, expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Struct { values: vec![ Expr::Named { expr: Expr::Value(number("1")).into(), name: Ident::from("a") }, Expr::Named { expr: Expr::Value(Value::SingleQuotedString("abc".to_string())).into(), name: Ident::from("b") }, ], fields: Default::default() }, expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Named { expr: Expr::Identifier(Ident::from("str_col")).into(), name: Ident::from("abc") }], fields: Default::default() }, expr_from_projection(&select.projection[4]) ); } #[test] fn parse_typed_struct_syntax_bigquery() { // typed struct syntax https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#typed_struct_syntax // syntax: STRUCT<[field_name] field_type, ...>( expr1 [, ... ]) let sql = r#"SELECT STRUCT(5), STRUCT(1, t.str_col), STRUCT, str STRUCT>(nested_col)"#; let select = bigquery().verified_only_select(sql); assert_eq!(3, select.projection.len()); assert_eq!( &Expr::Struct { values: vec![Expr::Value(number("5")),], fields: vec![StructField { field_name: None, field_type: DataType::Int64, }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![ Expr::Value(number("1")), Expr::CompoundIdentifier(vec![ Ident { value: "t".into(), quote_style: None, }, Ident { value: "str_col".into(), quote_style: None, }, ]), ], fields: vec![ StructField { field_name: Some(Ident { value: "x".into(), quote_style: None, }), field_type: DataType::Int64 }, StructField { field_name: Some(Ident { value: "y".into(), quote_style: None, }), field_type: DataType::String(None) }, ] }, expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Identifier(Ident { value: "nested_col".into(), quote_style: None, }),], fields: vec![ StructField { field_name: Some("arr".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Float64 ))) }, StructField { field_name: Some("str".into()), field_type: DataType::Struct( vec![StructField { field_name: None, field_type: DataType::Bool }], StructBracketKind::AngleBrackets ) }, ] }, expr_from_projection(&select.projection[2]) ); let sql = r#"SELECT STRUCT>(nested_col)"#; let select = bigquery().verified_only_select(sql); assert_eq!(1, select.projection.len()); assert_eq!( &Expr::Struct { values: vec![Expr::Identifier(Ident { value: "nested_col".into(), quote_style: None, }),], fields: vec![ StructField { field_name: Some("x".into()), field_type: DataType::Struct( Default::default(), StructBracketKind::AngleBrackets ) }, StructField { field_name: Some("y".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Struct(Default::default(), StructBracketKind::AngleBrackets) ))) }, ] }, expr_from_projection(&select.projection[0]) ); let sql = r#"SELECT STRUCT(true), STRUCT(B'abc')"#; let select = bigquery().verified_only_select(sql); assert_eq!(2, select.projection.len()); assert_eq!( &Expr::Struct { values: vec![Expr::Value(Value::Boolean(true)),], fields: vec![StructField { field_name: None, field_type: DataType::Bool }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Value(Value::SingleQuotedByteStringLiteral( "abc".into() )),], fields: vec![StructField { field_name: None, field_type: DataType::Bytes(Some(42)) }] }, expr_from_projection(&select.projection[1]) ); let sql = r#"SELECT STRUCT("2011-05-05"), STRUCT(DATETIME '1999-01-01 01:23:34.45'), STRUCT(5.0), STRUCT(1)"#; let select = bigquery().verified_only_select(sql); assert_eq!(4, select.projection.len()); assert_eq!( &Expr::Struct { values: vec![Expr::Value(Value::DoubleQuotedString( "2011-05-05".to_string() )),], fields: vec![StructField { field_name: None, field_type: DataType::Date }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Datetime(None), value: "1999-01-01 01:23:34.45".to_string() },], fields: vec![StructField { field_name: None, field_type: DataType::Datetime(None) }] }, expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Value(number("5.0")),], fields: vec![StructField { field_name: None, field_type: DataType::Float64 }] }, expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Value(number("1")),], fields: vec![StructField { field_name: None, field_type: DataType::Int64 }] }, expr_from_projection(&select.projection[3]) ); let sql = r#"SELECT STRUCT(INTERVAL '2' HOUR), STRUCT(JSON '{"class" : {"students" : [{"name" : "Jane"}]}}')"#; let select = bigquery().verified_only_select(sql); assert_eq!(2, select.projection.len()); assert_eq!( &Expr::Struct { values: vec![Expr::Interval(Interval { value: Box::new(Expr::Value(Value::SingleQuotedString("2".to_string()))), leading_field: Some(DateTimeField::Hour), leading_precision: None, last_field: None, fractional_seconds_precision: None }),], fields: vec![StructField { field_name: None, field_type: DataType::Interval }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::JSON, value: r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.to_string() },], fields: vec![StructField { field_name: None, field_type: DataType::JSON }] }, expr_from_projection(&select.projection[1]) ); let sql = r#"SELECT STRUCT("foo"), STRUCT(TIMESTAMP '2008-12-25 15:30:00 America/Los_Angeles'), STRUCT