// 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 sqlparser::tokenizer::{Location, Span}; use test_utils::*; #[test] fn parse_literal_string() { let sql = concat!( "SELECT ", // line 1, column 1 "'single', ", // line 1, column 7 r#""double", "#, // line 1, column 14 "'''triple-single''', ", // line 1, column 22 r#""""triple-double""", "#, // line 1, column 33 r#"'single\'escaped', "#, // line 1, column 43 r#"'''triple-single\'escaped''', "#, // line 1, column 55 r#"'''triple-single'unescaped''', "#, // line 1, column 68 r#""double\"escaped", "#, // line 1, column 83 r#""""triple-double\"escaped""", "#, // line 1, column 92 r#""""triple-double"unescaped""", "#, // line 1, column 105 r#""""triple-double'unescaped""", "#, // line 1, column 118 r#"'''triple-single"unescaped'''"#, // line 1, column 131 ); let dialect = TestedDialects::new_with_options( vec![Box::new(BigQueryDialect {})], ParserOptions::new().with_unescape(false), ); let select = dialect.verified_only_select(sql); assert_eq!(12, select.projection.len()); assert_eq!( &Expr::Value(Value::SingleQuotedString("single".into()).with_empty_span()), expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Value(Value::DoubleQuotedString("double".into()).with_empty_span()), expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedString("triple-single".into()).with_empty_span()), expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Value(Value::TripleDoubleQuotedString("triple-double".into()).with_empty_span()), expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Value(Value::SingleQuotedString(r#"single\'escaped"#.into()).with_empty_span()), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value( Value::TripleSingleQuotedString(r#"triple-single\'escaped"#.into()).with_empty_span() ), expr_from_projection(&select.projection[5]) ); assert_eq!( &Expr::Value( Value::TripleSingleQuotedString(r#"triple-single'unescaped"#.into()).with_empty_span() ), expr_from_projection(&select.projection[6]) ); assert_eq!( &Expr::Value(Value::DoubleQuotedString(r#"double\"escaped"#.to_string()).with_empty_span()), expr_from_projection(&select.projection[7]) ); assert_eq!( &Expr::Value( Value::TripleDoubleQuotedString(r#"triple-double\"escaped"#.to_string()) .with_empty_span() ), expr_from_projection(&select.projection[8]) ); assert_eq!( &Expr::Value( Value::TripleDoubleQuotedString(r#"triple-double"unescaped"#.to_string()) .with_empty_span() ), expr_from_projection(&select.projection[9]) ); assert_eq!( &Expr::Value( Value::TripleDoubleQuotedString(r#"triple-double'unescaped"#.to_string()) .with_empty_span() ), expr_from_projection(&select.projection[10]) ); assert_eq!( &Expr::Value( Value::TripleSingleQuotedString(r#"triple-single"unescaped"#.to_string()) .with_empty_span() ), expr_from_projection(&select.projection[11]) ); } #[test] fn parse_byte_literal() { let sql = concat!( "SELECT ", // line 1, column 1 "B'abc', ", // line 1, column 8 r#"B"abc", "#, // line 1, column 15 r#"B'f\(abc,(.*),def\)', "#, // line 1, column 22 r#"B"f\(abc,(.*),def\)", "#, // line 1, column 42 r#"B'''abc''', "#, // line 1, column 62 r#"B"""abc""""#, // line 1, column 74 ); 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()).with_empty_span() ), expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Value( Value::DoubleQuotedByteStringLiteral("abc".to_string()).with_empty_span() ), expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Value( Value::SingleQuotedByteStringLiteral(r"f\(abc,(.*),def\)".to_string()) .with_empty_span() ), expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Value( Value::DoubleQuotedByteStringLiteral(r"f\(abc,(.*),def\)".to_string()) .with_empty_span() ), expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Value( Value::TripleSingleQuotedByteStringLiteral(r"abc".to_string()) .with_empty_span() ), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value( Value::TripleDoubleQuotedByteStringLiteral(r"abc".to_string()) .with_empty_span() ), 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 ", // line 1, column 1 "R'abc', ", // line 1, column 8 r#"R"abc", "#, // line 1, column 15 r#"R'f\(abc,(.*),def\)', "#, // line 1, column 22 r#"R"f\(abc,(.*),def\)", "#, // line 1, column 42 r#"R'''abc''', "#, // line 1, column 62 r#"R"""abc""""#, // line 1, column 74 ); 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()).with_empty_span() ), expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Value( Value::DoubleQuotedRawStringLiteral("abc".to_string()).with_empty_span() ), expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Value( Value::SingleQuotedRawStringLiteral(r"f\(abc,(.*),def\)".to_string()) .with_empty_span() ), expr_from_projection(&select.projection[2]) ); assert_eq!( &Expr::Value( Value::DoubleQuotedRawStringLiteral(r"f\(abc,(.*),def\)".to_string()) .with_empty_span() ), expr_from_projection(&select.projection[3]) ); assert_eq!( &Expr::Value( Value::TripleSingleQuotedRawStringLiteral(r"abc".to_string()).with_empty_span() ), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value( Value::TripleDoubleQuotedRawStringLiteral(r"abc".to_string()).with_empty_span() ), 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_big_query_non_reserved_column_alias() { let sql = r#"SELECT OFFSET, EXPLAIN, ANALYZE, SORT, TOP, VIEW FROM T"#; bigquery().verified_stmt(sql); let sql = r#"SELECT 1 AS OFFSET, 2 AS EXPLAIN, 3 AS ANALYZE FROM T"#; bigquery().verified_stmt(sql); } #[test] fn parse_at_at_identifier() { bigquery().verified_stmt("SELECT @@error.stack_trace, @@error.message"); } #[test] fn parse_begin() { let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; RAISE USING MESSAGE = FORMAT('ERR: %s', 'Bad'); END"#; let Statement::StartTransaction { statements, exception, has_end_keyword, .. } = bigquery().verified_stmt(sql) else { unreachable!(); }; assert_eq!(1, statements.len()); assert!(exception.is_some()); let exception = exception.unwrap(); assert_eq!(1, exception.len()); assert!(has_end_keyword); bigquery().verified_stmt( "BEGIN SELECT 1; SELECT 2; EXCEPTION WHEN ERROR THEN SELECT 2; SELECT 4; END", ); bigquery() .verified_stmt("BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT @@error.stack_trace; END"); bigquery().verified_stmt("BEGIN EXCEPTION WHEN ERROR THEN SELECT 2; END"); bigquery().verified_stmt("BEGIN SELECT 1; SELECT 2; EXCEPTION WHEN ERROR THEN END"); bigquery().verified_stmt("BEGIN EXCEPTION WHEN ERROR THEN END"); bigquery().verified_stmt("BEGIN SELECT 1; SELECT 2; END"); bigquery().verified_stmt("BEGIN END"); assert_eq!( bigquery() .parse_sql_statements("BEGIN SELECT 1; SELECT 2 END") .unwrap_err(), ParserError::ParserError("Expected: ;, found: END".to_string()) ); assert_eq!( bigquery() .parse_sql_statements("BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2 END") .unwrap_err(), ParserError::ParserError("Expected: ;, found: END".to_string()) ); } #[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!( table_from_name(ObjectName::from(vec![Ident::with_quote('"', "table")])), 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::from(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(ColumnOptions::CommaSeparated(vec![ColumnOption::Options( vec![SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value( Value::DoubleQuotedString("field age".to_string()).with_span( Span::new(Location::new(1, 42), Location::new(1, 52)) ) ), }] )])), }, ], 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()) .with_empty_span() ), }, &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::from(vec![ "my-pro-ject".into(), "mydataset".into(), "mytable".into() ]) ); assert_eq!( vec![ColumnDef { name: Ident::new("x"), data_type: DataType::Int64, 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, table_options, .. }) => { assert_eq!( name, ObjectName::from(vec!["mydataset".into(), "newtable".into()]) ); assert_eq!( vec![ ColumnDef { name: Ident::new("x"), data_type: DataType::Int64, 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()).with_span( Span::new(Location::new(1, 42), Location::new(1, 52)) ) ), },]) }, ] }, ColumnDef { name: Ident::new("y"), data_type: DataType::Bool, options: vec![ColumnOptionDef { name: None, option: ColumnOption::Options(vec![SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value( Value::DoubleQuotedString("field y".to_string()).with_span( Span::new(Location::new(1, 42), Location::new(1, 52)) ) ), },]) }] }, ], columns ); assert_eq!( ( Some(Box::new(Expr::Identifier(Ident::new("_PARTITIONDATE")))), Some(WrappedCollection::NoWrapping(vec![ Expr::Identifier(Ident::new("userid")), Expr::Identifier(Ident::new("age")), ])), CreateTableOptions::Options(vec![ SqlOption::KeyValue { key: Ident::new("partition_expiration_days"), value: Expr::Value( number("1").with_span(Span::new( Location::new(1, 42), Location::new(1, 43) )) ), }, SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value( Value::DoubleQuotedString("table option description".to_string()) .with_span(Span::new( Location::new(1, 42), Location::new(1, 52) )) ), }, ]) ), (partition_by, cluster_by, table_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); let sql = "CREATE TABLE foo (x INT64) OPTIONS()"; bigquery().verified_stmt(sql); let sql = "CREATE TABLE db.schema.test (x INT64 OPTIONS(description = 'An optional INTEGER field')) OPTIONS()"; 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::from(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,) )), options: None, }, StructField { field_name: Some("b".into()), field_type: DataType::Bytes(Some(42)), options: None, }, ], StructBracketKind::AngleBrackets ), 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, options: None, }], StructBracketKind::AngleBrackets ), ))), options: vec![], }, ] ); } _ => unreachable!(), } } #[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)"; // line 1, column 1 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".into()).with_empty_span()), Expr::Value(Value::Boolean(true).with_empty_span()) ]), 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".into()).with_empty_span() )], 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".into()).with_empty_span()) .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, options: None, }] }, 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, span: Span::empty(), }, Ident { value: "str_col".into(), quote_style: None, span: Span::empty(), }, ]), ], fields: vec![ StructField { field_name: Some(Ident { value: "x".into(), quote_style: None, span: Span::empty(), }), field_type: DataType::Int64, options: None, }, StructField { field_name: Some(Ident { value: "y".into(), quote_style: None, span: Span::empty(), }), field_type: DataType::String(None), options: None, }, ] }, expr_from_projection(&select.projection[1]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Identifier(Ident { value: "nested_col".into(), quote_style: None, span: Span::empty(), })], fields: vec![ StructField { field_name: Some("arr".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Float64 ))), options: None, }, StructField { field_name: Some("str".into()), field_type: DataType::Struct( vec![StructField { field_name: None, field_type: DataType::Bool, options: None, }], StructBracketKind::AngleBrackets ), options: None, }, ] }, 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, span: Span::empty(), })], fields: vec![ StructField { field_name: Some("x".into()), field_type: DataType::Struct( Default::default(), StructBracketKind::AngleBrackets ), options: None, }, StructField { field_name: Some("y".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Struct(Default::default(), StructBracketKind::AngleBrackets) ))), options: None, }, ] }, 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).with_empty_span())], fields: vec![StructField { field_name: None, field_type: DataType::Bool, options: None, }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::Value( Value::SingleQuotedByteStringLiteral("abc".into()).with_empty_span() )], fields: vec![StructField { field_name: None, field_type: DataType::Bytes(Some(42)), options: None, }] }, 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".into()).with_empty_span() )], fields: vec![StructField { field_name: None, field_type: DataType::Date, options: None, }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Datetime(None), value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), span: Span::empty(), }, }], fields: vec![StructField { field_name: None, field_type: DataType::Datetime(None), options: 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, options: None, }] }, 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, options: None, }] }, 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".into()).with_empty_span() )), 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, options: None, }] }, expr_from_projection(&select.projection[0]) ); assert_eq!( &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::JSON, value: ValueWithSpan { value: Value::SingleQuotedString( r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() ), span: Span::empty(), } }], fields: vec![StructField { field_name: None, field_type: DataType::JSON, options: None, }] }, expr_from_projection(&select.projection[1]) ); let sql = r#"SELECT STRUCT("foo"), STRUCT(TIMESTAMP '2008-12-25 15:30:00 America/Los_Angeles'), STRUCT, ", "f1 STRUCT<", "a STRING OPTIONS(description = 'This is a string', type = 'string'), ", "b INT64", "> OPTIONS(description = 'This is a struct field')", ")", )); } #[test] fn test_struct_trailing_and_nested_bracket() { bigquery().verified_stmt(concat!( "CREATE TABLE my_table (", "f0 STRING, ", "f1 STRUCT>, ", "f2 STRING", ")", )); // More complex nested structs bigquery().verified_stmt(concat!( "CREATE TABLE my_table (", "f0 STRING, ", "f1 STRUCT>>, ", "f2 STRUCT>>>, ", "f3 STRUCT>", ")", )); // Bad case with missing closing bracket assert_eq!( ParserError::ParserError("Expected: >, found: )".to_owned()), bigquery() .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT after parsing data type STRUCT)".to_owned() ), bigquery() .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT>)") .unwrap_err() ); // Base case with redundant closing bracket in nested struct assert_eq!( ParserError::ParserError( "Expected: ',' or ')' after column definition, found: >".to_owned() ), bigquery() .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT>>, c INT64)") .unwrap_err() ); 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() ) ); }