// 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 std::ops::Deref; use sqlparser::ast::*; use sqlparser::dialect::{BigQueryDialect, GenericDialect}; use sqlparser::parser::ParserError; use test_utils::*; #[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_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![], }, 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"), options: None, }, ViewColumnDef { name: Ident::new("age"), options: Some(vec![SqlOption { name: 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 { name: 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, 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!(!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 { 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 { 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 { name: 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 { name: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString( "field y".to_string() )), },]) }] }, ], columns ); assert_eq!( ( Some(Box::new(Expr::Identifier(Ident::new("_PARTITIONDATE")))), Some(vec![Ident::new("userid"), Ident::new("age"),]), Some(vec![ SqlOption { name: Ident::new("partition_expiration_days"), value: Expr::Value(number("1")), }, SqlOption { name: 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().one_statement_parses_to(sql, sql) { Statement::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)) }, ]), 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, }]), ))), collation: None, options: vec![], }, ] ); } _ => unreachable!(), } } #[test] fn parse_invalid_brackets() { let sql = "SELECT STRUCT>(NULL)"; assert_eq!( bigquery().parse_sql_statements(sql).unwrap_err(), ParserError::ParserError("unmatched > in STRUCT literal".to_string()) ); let sql = "SELECT STRUCT>>(NULL)"; assert_eq!( bigquery().parse_sql_statements(sql).unwrap_err(), ParserError::ParserError("Expected (, found: >".to_string()) ); let sql = "CREATE TABLE table (x STRUCT>>)"; assert_eq!( bigquery().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().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() { // 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 }]) }, ] }, 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()) }, StructField { field_name: Some("y".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Struct(Default::default()) ))) }, ] }, 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 '1-2 3 4:5:6.789999'), 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(ast::Interval { value: Box::new(Expr::Value(Value::SingleQuotedString( "1-2 3 4:5:6.789999".to_string() ))), leading_field: None, 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