From 1cf6585649e62bbc3dabce9f01283fd8adf1f780 Mon Sep 17 00:00:00 2001 From: Lukasz Stefaniak Date: Thu, 29 Feb 2024 13:14:00 +0100 Subject: [PATCH] Support`SELECT AS VALUE` and `SELECT AS STRUCT` for BigQuery (#1135) --- src/ast/mod.rs | 4 ++-- src/ast/query.rs | 28 ++++++++++++++++++++++++++++ src/parser/mod.rs | 14 ++++++++++++++ tests/sqlparser_bigquery.rs | 16 ++++++++++++++++ tests/sqlparser_clickhouse.rs | 1 + tests/sqlparser_common.rs | 7 ++++++- tests/sqlparser_duckdb.rs | 2 ++ tests/sqlparser_mssql.rs | 6 ++++-- tests/sqlparser_mysql.rs | 18 +++++++++++++----- tests/sqlparser_postgres.rs | 3 +++ 10 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 79848ad2..0d2bfff9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -44,8 +44,8 @@ pub use self::query::{ JsonTableColumnErrorHandling, LateralView, LockClause, LockType, NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table, - TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, Values, - WildcardAdditionalOptions, With, + TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode, + Values, WildcardAdditionalOptions, With, }; pub use self::value::{ escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value, diff --git a/src/ast/query.rs b/src/ast/query.rs index 64bda663..f3c2d532 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -245,11 +245,18 @@ pub struct Select { pub named_window: Vec, /// QUALIFY (Snowflake) pub qualify: Option, + /// BigQuery syntax: `SELECT AS VALUE | SELECT AS STRUCT` + pub value_table_mode: Option, } impl fmt::Display for Select { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SELECT")?; + + if let Some(value_table_mode) = self.value_table_mode { + write!(f, " {value_table_mode}")?; + } + if let Some(ref distinct) = self.distinct { write!(f, " {distinct}")?; } @@ -1574,3 +1581,24 @@ impl fmt::Display for JsonTableColumnErrorHandling { } } } + +/// BigQuery supports ValueTables which have 2 modes: +/// `SELECT AS STRUCT` +/// `SELECT AS VALUE` +/// +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum ValueTableMode { + AsStruct, + AsValue, +} + +impl fmt::Display for ValueTableMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ValueTableMode::AsStruct => write!(f, "AS STRUCT"), + ValueTableMode::AsValue => write!(f, "AS VALUE"), + } + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ce28b6b6..3fcf6389 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6826,6 +6826,19 @@ impl<'a> Parser<'a> { /// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`), /// assuming the initial `SELECT` was already consumed pub fn parse_select(&mut self) -> Result { + let value_table_mode = + if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::AS) { + if self.parse_keyword(Keyword::VALUE) { + Some(ValueTableMode::AsValue) + } else if self.parse_keyword(Keyword::STRUCT) { + Some(ValueTableMode::AsStruct) + } else { + self.expected("VALUE or STRUCT", self.peek_token())? + } + } else { + None + }; + let distinct = self.parse_all_or_distinct()?; let top = if self.parse_keyword(Keyword::TOP) { @@ -6962,6 +6975,7 @@ impl<'a> Parser<'a> { having, named_window: named_windows, qualify, + value_table_mode, }) } diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 5e79db33..827291a1 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -1352,3 +1352,19 @@ fn test_bigquery_trim() { bigquery().parse_sql_statements(error_sql).unwrap_err() ); } + +#[test] +fn test_select_as_struct() { + bigquery().verified_only_select("SELECT * FROM (SELECT AS VALUE STRUCT(123 AS a, false AS b))"); + let select = bigquery().verified_only_select("SELECT AS STRUCT 1 AS a, 2 AS b"); + assert_eq!(Some(ValueTableMode::AsStruct), select.value_table_mode); +} + +#[test] +fn test_select_as_value() { + bigquery().verified_only_select( + "SELECT * FROM (SELECT AS VALUE STRUCT(5 AS star_rating, false AS up_down_rating))", + ); + let select = bigquery().verified_only_select("SELECT AS VALUE STRUCT(1 AS a, 2 AS b) AS xyz"); + assert_eq!(Some(ValueTableMode::AsValue), select.value_table_mode); +} diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index e7c85c2a..efb53377 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -110,6 +110,7 @@ fn parse_map_access_expr() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }, select ); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index bd95e316..13bc333c 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -400,7 +400,8 @@ fn parse_update_set_from() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -4212,6 +4213,7 @@ fn test_parse_named_window() { ), ], qualify: None, + value_table_mode: None, }; assert_eq!(actual_select_only, expected); } @@ -4567,6 +4569,7 @@ fn parse_interval_and_or_xor() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -6550,6 +6553,7 @@ fn lateral_function() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }; assert_eq!(actual_select_only, expected); } @@ -7193,6 +7197,7 @@ fn parse_merge() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 97bdd8e1..45ae01bf 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -177,6 +177,7 @@ fn test_select_union_by_name() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))), right: Box::::new(SetExpr::Select(Box::new(Select { distinct: None, @@ -211,6 +212,7 @@ fn test_select_union_by_name() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))), }); assert_eq!(ast.body, expected); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index fc0d6394..ff3e7556 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -112,7 +112,8 @@ fn parse_create_procedure() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))) }))], params: Some(vec![ @@ -595,7 +596,8 @@ fn parse_substring_in_select() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 10353092..90579788 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -785,7 +785,8 @@ fn parse_escaped_quote_identifiers_with_escape() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -829,7 +830,8 @@ fn parse_escaped_quote_identifiers_with_no_escape() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -870,7 +872,8 @@ fn parse_escaped_backticks_with_escape() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -911,7 +914,8 @@ fn parse_escaped_backticks_with_no_escape() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -1581,6 +1585,7 @@ fn parse_select_with_numeric_prefix_column_name() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))) ); } @@ -1631,6 +1636,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))) ); } @@ -1841,7 +1847,8 @@ fn parse_substring_in_select() { sort_by: vec![], having: None, named_window: vec![], - qualify: None + qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -2143,6 +2150,7 @@ fn parse_hex_string_introducer() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, into: None }))), order_by: vec![], diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index c987822b..1131a79b 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1082,6 +1082,7 @@ fn parse_copy_to() { distribute_by: vec![], sort_by: vec![], qualify: None, + value_table_mode: None, }))), order_by: vec![], limit: None, @@ -2139,6 +2140,7 @@ fn parse_array_subquery_expr() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))), right: Box::new(SetExpr::Select(Box::new(Select { distinct: None, @@ -2155,6 +2157,7 @@ fn parse_array_subquery_expr() { having: None, named_window: vec![], qualify: None, + value_table_mode: None, }))), }), order_by: vec![],