From 36fea1ca18b8fddec45cd2f2fa5bb5f471bf91d5 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Tue, 5 Nov 2024 00:52:06 +0800 Subject: [PATCH 01/17] Add support for MSSQL's `OPENJSON WITH` clause --- src/ast/mod.rs | 11 +++--- src/ast/query.rs | 74 ++++++++++++++++++++++++++++++++++++++++ src/keywords.rs | 1 + src/parser/mod.rs | 55 ++++++++++++++++++++++++++++- tests/sqlparser_mssql.rs | 29 ++++++++++++++++ 5 files changed, 164 insertions(+), 6 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b2672552e..f19523fa4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -56,11 +56,12 @@ pub use self::query::{ InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, - OffsetRows, OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, - RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, - SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, - TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity, - ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill, + OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, + Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, + RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, + SymbolDefinition, Table, TableAlias, TableFactor, TableFunctionArgs, TableVersion, + TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, + WithFill, }; pub use self::trigger::{ diff --git a/src/ast/query.rs b/src/ast/query.rs index dc5966e5e..08bc37136 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1026,6 +1026,27 @@ pub enum TableFactor { /// The alias for the table. alias: Option, }, + /// The MSSQL's `OPENJSON` table-valued function. + /// + /// ```sql + /// OPENJSON( jsonExpression [ , path ] ) [ ] + /// + /// ::= WITH ( { colName type [ column_path ] [ AS JSON ] } [ ,...n ] ) + /// ```` + /// + /// Reference: + OpenJsonTable { + /// The JSON expression to be evaluated. It must evaluate to a json string + json_expr: Expr, + /// The path to the array or object to be iterated over. + /// It must evaluate to a json array or object. + json_path: Option, + /// The columns to be extracted from each element of the array or object. + /// Each column must have a name and a type. + columns: Vec, + /// The alias for the table. + alias: Option, + }, /// Represents a parenthesized table factor. The SQL spec only allows a /// join expression (`(foo bar [ baz ... ])`) to be nested, /// possibly several times. @@ -1451,6 +1472,25 @@ impl fmt::Display for TableFactor { } Ok(()) } + TableFactor::OpenJsonTable { + json_expr, + json_path, + columns, + alias, + } => { + write!(f, "OPENJSON({json_expr}")?; + if let Some(json_path) = json_path { + write!(f, ", {json_path}")?; + } + write!(f, ")")?; + if !columns.is_empty() { + write!(f, " WITH ({})", display_comma_separated(columns))?; + } + if let Some(alias) = alias { + write!(f, " AS {alias}")?; + } + Ok(()) + } TableFactor::NestedJoin { table_with_joins, alias, @@ -2346,6 +2386,40 @@ impl fmt::Display for JsonTableColumnErrorHandling { } } +/// A single column definition in MSSQL's `OPENJSON WITH` clause. +/// +/// ```sql +/// colName type [ column_path ] [ AS JSON ] +/// ``` +/// +/// Reference: +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct OpenJsonTableColumn { + /// The name of the column to be extracted. + pub name: Ident, + /// The type of the column to be extracted. + pub r#type: DataType, + /// The path to the column to be extracted. Must be a literal string. + pub path: Option, + /// The `AS JSON` option. + pub as_json: bool, +} + +impl fmt::Display for OpenJsonTableColumn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.name, self.r#type)?; + if let Some(path) = &self.path { + write!(f, " {}", path)?; + } + if self.as_json { + write!(f, " AS JSON")?; + } + Ok(()) + } +} + /// BigQuery supports ValueTables which have 2 modes: /// `SELECT AS STRUCT` /// `SELECT AS VALUE` diff --git a/src/keywords.rs b/src/keywords.rs index e98309681..fa39e6c10 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -535,6 +535,7 @@ define_keywords!( ONE, ONLY, OPEN, + OPENJSON, OPERATOR, OPTIMIZE, OPTIMIZER_COSTS, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c4b92ba4e..ace74d780 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10000,7 +10000,7 @@ impl<'a> Parser<'a> { table_with_joins: Box::new(table_and_joins), alias, }) - } else if dialect_of!(self is SnowflakeDialect | GenericDialect) { + } else if dialect_of!(self is SnowflakeDialect | GenericDialect | MsSqlDialect) { // Dialect-specific behavior: Snowflake diverges from the // standard and from most of the other implementations by // allowing extra parentheses not only around a join (B), but @@ -10020,6 +10020,7 @@ impl<'a> Parser<'a> { | TableFactor::Function { alias, .. } | TableFactor::UNNEST { alias, .. } | TableFactor::JsonTable { alias, .. } + | TableFactor::OpenJsonTable { alias, .. } | TableFactor::TableFunction { alias, .. } | TableFactor::Pivot { alias, .. } | TableFactor::Unpivot { alias, .. } @@ -10133,6 +10134,30 @@ impl<'a> Parser<'a> { columns, alias, }) + } else if self.parse_keyword_with_tokens(Keyword::OPENJSON, &[Token::LParen]) { + let json_expr = self.parse_expr()?; + let json_path = if self.consume_token(&Token::Comma) { + Some(self.parse_value()?) + } else { + None + }; + self.expect_token(&Token::RParen)?; + let columns = if self.parse_keyword(Keyword::WITH) { + self.expect_token(&Token::LParen)?; + let columns = + self.parse_comma_separated(Parser::parse_openjson_table_column_def)?; + self.expect_token(&Token::RParen)?; + columns + } else { + Vec::new() + }; + let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; + Ok(TableFactor::OpenJsonTable { + json_expr, + json_path, + columns, + alias, + }) } else { let name = self.parse_object_name(true)?; @@ -10468,6 +10493,34 @@ impl<'a> Parser<'a> { }) } + /// Parses MSSQL's `OPENJSON WITH` column definition. + /// + /// ```sql + /// colName type [ column_path ] [ AS JSON ] + /// ``` + /// + /// Reference: + pub fn parse_openjson_table_column_def(&mut self) -> Result { + let name = self.parse_identifier(false)?; + let r#type = self.parse_data_type()?; + let path = if let Token::SingleQuotedString(path) = self.peek_token().token { + self.next_token(); + Some(Value::SingleQuotedString(path)) + } else { + None + }; + let as_json = self.parse_keyword(Keyword::AS); + if as_json { + self.expect_keyword(Keyword::JSON)?; + } + Ok(OpenJsonTableColumn { + name, + r#type, + path, + as_json, + }) + } + fn parse_json_table_column_error_handling( &mut self, ) -> Result, ParserError> { diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 0223e2915..9f20e8705 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -192,6 +192,35 @@ fn parse_mssql_apply_join() { ); } +#[test] +fn parse_mssql_cross_apply_json() { + let _ = ms().verified_only_select( + "SELECT B.kind, B.id_list \ + FROM t_test_table AS A \ + CROSS APPLY OPENJSON(A.param, '$.config') WITH (kind VARCHAR(20) '$.kind', [id_list] NVARCHAR(MAX) '$.id_list' AS JSON) AS B", + ); + let _ = ms().verified_only_select( + "SELECT B.kind, B.id_list \ + FROM t_test_table AS A \ + CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20) '$.kind', [id_list] NVARCHAR(MAX) '$.id_list' AS JSON) AS B", + ); + let _ = ms().verified_only_select( + "SELECT B.kind, B.id_list \ + FROM t_test_table AS A \ + CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20), [id_list] NVARCHAR(MAX) AS JSON) AS B", + ); + let _ = ms().verified_only_select( + "SELECT B.kind, B.id_list \ + FROM t_test_table AS A \ + CROSS APPLY OPENJSON(A.param, '$.config') AS B", + ); + let _ = ms().verified_only_select( + "SELECT B.kind, B.id_list \ + FROM t_test_table AS A \ + CROSS APPLY OPENJSON(A.param) AS B", + ); +} + #[test] fn parse_mssql_top_paren() { let sql = "SELECT TOP (5) * FROM foo"; From b07bfc54d160f54cf364d9adcc3a08a08d51d571 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Thu, 7 Nov 2024 11:34:38 +0800 Subject: [PATCH 02/17] Code review comments --- src/ast/query.rs | 4 +- src/parser/mod.rs | 57 ++++--- tests/sqlparser_mssql.rs | 320 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 347 insertions(+), 34 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 08bc37136..a2999040d 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2402,7 +2402,7 @@ pub struct OpenJsonTableColumn { /// The type of the column to be extracted. pub r#type: DataType, /// The path to the column to be extracted. Must be a literal string. - pub path: Option, + pub path: Option, /// The `AS JSON` option. pub as_json: bool, } @@ -2411,7 +2411,7 @@ impl fmt::Display for OpenJsonTableColumn { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", self.name, self.r#type)?; if let Some(path) = &self.path { - write!(f, " {}", path)?; + write!(f, " '{}'", value::escape_single_quote_string(path))?; } if self.as_json { write!(f, " AS JSON")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ace74d780..f22376be3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10000,7 +10000,7 @@ impl<'a> Parser<'a> { table_with_joins: Box::new(table_and_joins), alias, }) - } else if dialect_of!(self is SnowflakeDialect | GenericDialect | MsSqlDialect) { + } else if dialect_of!(self is SnowflakeDialect | GenericDialect) { // Dialect-specific behavior: Snowflake diverges from the // standard and from most of the other implementations by // allowing extra parentheses not only around a join (B), but @@ -10135,29 +10135,8 @@ impl<'a> Parser<'a> { alias, }) } else if self.parse_keyword_with_tokens(Keyword::OPENJSON, &[Token::LParen]) { - let json_expr = self.parse_expr()?; - let json_path = if self.consume_token(&Token::Comma) { - Some(self.parse_value()?) - } else { - None - }; - self.expect_token(&Token::RParen)?; - let columns = if self.parse_keyword(Keyword::WITH) { - self.expect_token(&Token::LParen)?; - let columns = - self.parse_comma_separated(Parser::parse_openjson_table_column_def)?; - self.expect_token(&Token::RParen)?; - columns - } else { - Vec::new() - }; - let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; - Ok(TableFactor::OpenJsonTable { - json_expr, - json_path, - columns, - alias, - }) + self.prev_token(); + self.parse_open_json_table_factor() } else { let name = self.parse_object_name(true)?; @@ -10223,6 +10202,34 @@ impl<'a> Parser<'a> { } } + /// Parses `OPENJSON( jsonExpression [ , path ] ) [ ]` clause, + /// assuming the `OPENJSON` keyword was already consumed. + fn parse_open_json_table_factor(&mut self) -> Result { + self.expect_token(&Token::LParen)?; + let json_expr = self.parse_expr()?; + let json_path = if self.consume_token(&Token::Comma) { + Some(self.parse_value()?) + } else { + None + }; + self.expect_token(&Token::RParen)?; + let columns = if self.parse_keyword(Keyword::WITH) { + self.expect_token(&Token::LParen)?; + let columns = self.parse_comma_separated(Parser::parse_openjson_table_column_def)?; + self.expect_token(&Token::RParen)?; + columns + } else { + Vec::new() + }; + let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; + Ok(TableFactor::OpenJsonTable { + json_expr, + json_path, + columns, + alias, + }) + } + fn parse_match_recognize(&mut self, table: TableFactor) -> Result { self.expect_token(&Token::LParen)?; @@ -10505,7 +10512,7 @@ impl<'a> Parser<'a> { let r#type = self.parse_data_type()?; let path = if let Token::SingleQuotedString(path) = self.peek_token().token { self.next_token(); - Some(Value::SingleQuotedString(path)) + Some(path) } else { None }; diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 9f20e8705..9da91f275 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -193,32 +193,338 @@ fn parse_mssql_apply_join() { } #[test] -fn parse_mssql_cross_apply_json() { - let _ = ms().verified_only_select( +fn parse_mssql_openjson() { + let select = ms().verified_only_select( "SELECT B.kind, B.id_list \ FROM t_test_table AS A \ CROSS APPLY OPENJSON(A.param, '$.config') WITH (kind VARCHAR(20) '$.kind', [id_list] NVARCHAR(MAX) '$.id_list' AS JSON) AS B", ); - let _ = ms().verified_only_select( + assert_eq!( + vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: "t_test_table".into(), + quote_style: None, + },]), + alias: Some(TableAlias { + name: Ident { + value: "A".into(), + quote_style: None + }, + columns: vec![] + }), + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![] + }, + joins: vec![Join { + relation: TableFactor::OpenJsonTable { + json_expr: Expr::CompoundIdentifier(vec![ + Ident { + value: "A".into(), + quote_style: None, + }, + Ident { + value: "param".into(), + quote_style: None, + } + ]), + json_path: Some(Value::SingleQuotedString("$.config".into())), + columns: vec![ + OpenJsonTableColumn { + name: Ident { + value: "kind".into(), + quote_style: None, + }, + r#type: DataType::Varchar(Some(CharacterLength::IntegerLength { + length: 20, + unit: None + })), + path: Some("$.kind".into()), + as_json: false + }, + OpenJsonTableColumn { + name: Ident { + value: "id_list".into(), + quote_style: Some('['), + }, + r#type: DataType::Nvarchar(Some(CharacterLength::Max)), + path: Some("$.id_list".into()), + as_json: true + } + ], + alias: Some(TableAlias { + name: Ident { + value: "B".into(), + quote_style: None + }, + columns: vec![] + }) + }, + global: false, + join_operator: JoinOperator::CrossApply + }] + }], + select.from + ); + let select = ms().verified_only_select( "SELECT B.kind, B.id_list \ FROM t_test_table AS A \ CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20) '$.kind', [id_list] NVARCHAR(MAX) '$.id_list' AS JSON) AS B", ); - let _ = ms().verified_only_select( + assert_eq!( + vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: "t_test_table".into(), + quote_style: None, + },]), + alias: Some(TableAlias { + name: Ident { + value: "A".into(), + quote_style: None + }, + columns: vec![] + }), + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![] + }, + joins: vec![Join { + relation: TableFactor::OpenJsonTable { + json_expr: Expr::CompoundIdentifier(vec![ + Ident { + value: "A".into(), + quote_style: None, + }, + Ident { + value: "param".into(), + quote_style: None, + } + ]), + json_path: None, + columns: vec![ + OpenJsonTableColumn { + name: Ident { + value: "kind".into(), + quote_style: None, + }, + r#type: DataType::Varchar(Some(CharacterLength::IntegerLength { + length: 20, + unit: None + })), + path: Some("$.kind".into()), + as_json: false + }, + OpenJsonTableColumn { + name: Ident { + value: "id_list".into(), + quote_style: Some('['), + }, + r#type: DataType::Nvarchar(Some(CharacterLength::Max)), + path: Some("$.id_list".into()), + as_json: true + } + ], + alias: Some(TableAlias { + name: Ident { + value: "B".into(), + quote_style: None + }, + columns: vec![] + }) + }, + global: false, + join_operator: JoinOperator::CrossApply + }] + }], + select.from + ); + let select = ms().verified_only_select( "SELECT B.kind, B.id_list \ FROM t_test_table AS A \ - CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20), [id_list] NVARCHAR(MAX) AS JSON) AS B", + CROSS APPLY OPENJSON(A.param) WITH (kind VARCHAR(20), [id_list] NVARCHAR(MAX)) AS B", ); - let _ = ms().verified_only_select( + assert_eq!( + vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: "t_test_table".into(), + quote_style: None, + },]), + alias: Some(TableAlias { + name: Ident { + value: "A".into(), + quote_style: None + }, + columns: vec![] + }), + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![] + }, + joins: vec![Join { + relation: TableFactor::OpenJsonTable { + json_expr: Expr::CompoundIdentifier(vec![ + Ident { + value: "A".into(), + quote_style: None, + }, + Ident { + value: "param".into(), + quote_style: None, + } + ]), + json_path: None, + columns: vec![ + OpenJsonTableColumn { + name: Ident { + value: "kind".into(), + quote_style: None, + }, + r#type: DataType::Varchar(Some(CharacterLength::IntegerLength { + length: 20, + unit: None + })), + path: None, + as_json: false + }, + OpenJsonTableColumn { + name: Ident { + value: "id_list".into(), + quote_style: Some('['), + }, + r#type: DataType::Nvarchar(Some(CharacterLength::Max)), + path: None, + as_json: false + } + ], + alias: Some(TableAlias { + name: Ident { + value: "B".into(), + quote_style: None + }, + columns: vec![] + }) + }, + global: false, + join_operator: JoinOperator::CrossApply + }] + }], + select.from + ); + let select = ms_and_generic().verified_only_select( "SELECT B.kind, B.id_list \ FROM t_test_table AS A \ CROSS APPLY OPENJSON(A.param, '$.config') AS B", ); - let _ = ms().verified_only_select( + assert_eq!( + vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: "t_test_table".into(), + quote_style: None, + },]), + alias: Some(TableAlias { + name: Ident { + value: "A".into(), + quote_style: None + }, + columns: vec![] + }), + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![] + }, + joins: vec![Join { + relation: TableFactor::OpenJsonTable { + json_expr: Expr::CompoundIdentifier(vec![ + Ident { + value: "A".into(), + quote_style: None, + }, + Ident { + value: "param".into(), + quote_style: None, + } + ]), + json_path: Some(Value::SingleQuotedString("$.config".into())), + columns: vec![], + alias: Some(TableAlias { + name: Ident { + value: "B".into(), + quote_style: None + }, + columns: vec![] + }) + }, + global: false, + join_operator: JoinOperator::CrossApply + }] + }], + select.from + ); + let select = ms_and_generic().verified_only_select( "SELECT B.kind, B.id_list \ FROM t_test_table AS A \ CROSS APPLY OPENJSON(A.param) AS B", ); + assert_eq!( + vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: "t_test_table".into(), + quote_style: None, + },]), + alias: Some(TableAlias { + name: Ident { + value: "A".into(), + quote_style: None + }, + columns: vec![] + }), + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![] + }, + joins: vec![Join { + relation: TableFactor::OpenJsonTable { + json_expr: Expr::CompoundIdentifier(vec![ + Ident { + value: "A".into(), + quote_style: None, + }, + Ident { + value: "param".into(), + quote_style: None, + } + ]), + json_path: None, + columns: vec![], + alias: Some(TableAlias { + name: Ident { + value: "B".into(), + quote_style: None + }, + columns: vec![] + }) + }, + global: false, + join_operator: JoinOperator::CrossApply + }] + }], + select.from + ); } #[test] From 342595787ee3e7db3a99ff3ab3dbd6734c0a8989 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Thu, 7 Nov 2024 11:54:37 +0800 Subject: [PATCH 03/17] Resolve conflict --- src/ast/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a24739a60..00fd05558 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -56,12 +56,12 @@ pub use self::query::{ InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn, LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, - NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OrderBy, OrderByExpr, - PivotValueSource, ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, - ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, - SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableFactor, - TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode, Values, - WildcardAdditionalOptions, With, WithFill, + NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn, + OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, + RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, + SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, + TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity, + ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill, }; pub use self::trigger::{ From a4c73f3c6b221d4db51c8126330e177c5d9bd820 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Sat, 9 Nov 2024 22:35:48 +0800 Subject: [PATCH 04/17] Add support for MSSQL's `JSON_ARRAY`/`JSON_OBJECT` expr --- src/ast/mod.rs | 131 +++++++++++++ src/keywords.rs | 3 + src/parser/mod.rs | 71 +++++++- tests/sqlparser_mssql.rs | 385 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 589 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b2672552e..69609a9c0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -931,6 +931,54 @@ pub enum Expr { /// /// See . Lambda(LambdaFunction), + /// MSSQL's `JSON_ARRAY` function for construct JSON-ARRAY object + /// + /// Syntax: + /// + /// ```plaintext + /// JSON_ARRAY ( [ [,...n] ] [ ] ) + /// + /// ::= value_expression + /// + /// ::= + /// NULL ON NULL + /// | ABSENT ON NULL + /// ``` + /// + /// Example: + /// + /// ```sql + /// SELECT JSON_ARRAY('a', 1, 'b', 2) --["a",1,"b",2] + /// SELECT JSON_ARRAY('a', 1, NULL, 2 NULL ON NULL) --["a",1,null,2] + /// SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1), JSON_ARRAY(1, null, 2 NULL ON NULL)) --["a",{"name":"value","type":1},[1,null,2]] + /// ``` + /// + /// Reference: + JsonArray(JsonArray), + /// MSSQL's `JSON_OBJECT` function for construct JSON-OBJECT object + /// + /// Syntax: + /// + /// ```plaintext + /// JSON_OBJECT ( [ [,...n] ] [ json_null_clause ] ) + /// + /// ::= json_key_name : value_expression + /// + /// ::= + /// NULL ON NULL + /// | ABSENT ON NULL + /// ``` + /// + /// Example: + /// + /// ```sql + /// SELECT JSON_OBJECT('name':'value', 'type':1) --{"name":"value","type":1} + /// SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL) --{"name":"value"} + /// SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2)) --{"name":"value","type":[1,2]} + /// ``` + /// + /// Reference: + JsonObject(JsonObject), } /// The contents inside the `[` and `]` in a subscript expression. @@ -1658,6 +1706,8 @@ impl fmt::Display for Expr { } Expr::Prior(expr) => write!(f, "PRIOR {expr}"), Expr::Lambda(lambda) => write!(f, "{lambda}"), + Expr::JsonArray(json_array) => write!(f, "{json_array}"), + Expr::JsonObject(json_object) => write!(f, "{json_object}"), } } } @@ -7317,6 +7367,87 @@ impl Display for UtilityOption { } } +/// MSSQL's `JSON_ARRAY` constructor function +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonArray { + pub args: Vec, + pub null_clause: Option, +} + +impl Display for JsonArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "JSON_ARRAY({}", display_comma_separated(&self.args))?; + if let Some(null_clause) = &self.null_clause { + if !self.args.is_empty() { + write!(f, " ")?; + } + write!(f, "{null_clause}")?; + } + write!(f, ")") + } +} + +/// MSSQL's `JSON_OBJECT` constructor function +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonObject { + pub key_values: Vec, + pub null_clause: Option, +} + +impl Display for JsonObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "JSON_OBJECT({}", + display_comma_separated(&self.key_values) + )?; + if let Some(null_clause) = &self.null_clause { + if !self.key_values.is_empty() { + write!(f, " ")?; + } + write!(f, "{null_clause}")?; + } + write!(f, ")") + } +} + +/// A key-value pair of MSSQL's `JSON_OBJECT` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonKeyValue { + pub key: Expr, + pub value: Expr, +} + +impl Display for JsonKeyValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.key, self.value) + } +} + +/// MSSQL's json null clause +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonNullClause { + NullOnNull, + AbsentOnNull, +} + +impl Display for JsonNullClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonNullClause::NullOnNull => write!(f, "NULL ON NULL"), + JsonNullClause::AbsentOnNull => write!(f, "ABSENT ON NULL"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/keywords.rs b/src/keywords.rs index e98309681..b4a38f376 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -74,6 +74,7 @@ macro_rules! define_keywords { define_keywords!( ABORT, ABS, + ABSENT, ABSOLUTE, ACCESS, ACTION, @@ -419,6 +420,8 @@ define_keywords!( JSON, JSONB, JSONFILE, + JSON_ARRAY, + JSON_OBJECT, JSON_TABLE, JULIAN, KEY, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c4b92ba4e..b70f25434 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -991,7 +991,7 @@ impl<'a> Parser<'a> { if let Some(expr) = opt_expr { return Ok(expr); } - + let next_token = self.next_token(); let expr = match next_token.token { Token::Word(w) => match w.keyword { @@ -1028,6 +1028,11 @@ impl<'a> Parser<'a> { Keyword::CAST => self.parse_cast_expr(CastKind::Cast), Keyword::TRY_CAST => self.parse_cast_expr(CastKind::TryCast), Keyword::SAFE_CAST => self.parse_cast_expr(CastKind::SafeCast), + Keyword::JSON_ARRAY + if self.peek_token() == Token::LParen + && dialect_of!(self is MsSqlDialect) => self.parse_json_array(), + Keyword::JSON_OBJECT if self.peek_token() == Token::LParen + && dialect_of!(self is MsSqlDialect) => self.parse_json_object(), Keyword::EXISTS // Support parsing Databricks has a function named `exists`. if !dialect_of!(self is DatabricksDialect) @@ -3013,6 +3018,70 @@ impl<'a> Parser<'a> { }) } + /// Parses MSSQL's `JSON_ARRAY` constructor function + pub fn parse_json_array(&mut self) -> Result { + self.expect_token(&Token::LParen)?; + let mut args = Vec::new(); + let mut null_clause = None; + loop { + // JSON_ARRAY() + if self.consume_token(&Token::RParen) { + self.prev_token(); + break; + } + // JSON_ARRAY(NULL ON NULL) + else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { + null_clause = Some(JsonNullClause::NullOnNull); + break; + } + // JSON_ARRAY(ABSENT ON NULL) + else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { + null_clause = Some(JsonNullClause::AbsentOnNull); + break; + } else if !args.is_empty() && !self.consume_token(&Token::Comma) { + break; + } + args.push(self.parse_expr()?); + } + self.expect_token(&Token::RParen)?; + Ok(Expr::JsonArray(JsonArray { args, null_clause })) + } + + /// Parses MSSQL's `JSON_OBJECT` constructor function + pub fn parse_json_object(&mut self) -> Result { + self.expect_token(&Token::LParen)?; + let mut key_values = Vec::new(); + let mut null_clause = None; + loop { + // JSON_OBJECT() + if self.consume_token(&Token::RParen) { + self.prev_token(); + break; + } + // JSON_OBJECT(NULL ON NULL) + else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { + null_clause = Some(JsonNullClause::NullOnNull); + break; + } + // JSON_OBJECT(ABSENT ON NULL) + else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { + null_clause = Some(JsonNullClause::AbsentOnNull); + break; + } else if !key_values.is_empty() && !self.consume_token(&Token::Comma) { + break; + } + let key = self.parse_expr()?; + self.expect_token(&Token::Colon)?; + let value = self.parse_expr()?; + key_values.push(JsonKeyValue { + key, + value + }); + } + self.expect_token(&Token::RParen)?; + Ok(Expr::JsonObject(JsonObject { key_values, null_clause })) + } + /// Parses the parens following the `[ NOT ] IN` operator. pub fn parse_in(&mut self, expr: Expr, negated: bool) -> Result { // BigQuery allows `IN UNNEST(array_expression)` diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 0223e2915..6d00fda22 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -448,6 +448,391 @@ fn parse_for_json_expect_ast() { ); } +#[test] +fn parse_mssql_json_object() { + let select = ms().verified_only_select("SELECT JSON_OBJECT()"); + assert_eq!( + &Expr::JsonObject(JsonObject { + key_values: vec![], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_OBJECT('name':'value', 'type':1)"); + assert_eq!( + &Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("value".into())) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type".into())), + value: Expr::Value(number("1")) + } + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = + ms().verified_only_select("SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL)"); + assert_eq!( + &Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("value".into())) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type".into())), + value: Expr::Value(Value::Null) + } + ], + null_clause: Some(JsonNullClause::AbsentOnNull) + }), + expr_from_projection(&select.projection[0]) + ); + let select = + ms().verified_only_select("SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2))"); + assert_eq!( + &Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("value".into())) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type".into())), + value: Expr::JsonArray(JsonArray { + args: vec![Expr::Value(number("1")), Expr::Value(number("2"))], + null_clause: None + }) + } + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select( + "SELECT JSON_OBJECT('name':'value', 'type':JSON_OBJECT('type_id':1, 'name':'a'))", + ); + assert_eq!( + &Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("value".into())) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type".into())), + value: Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type_id".into())), + value: Expr::Value(number("1")) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("a".into())) + } + ], + null_clause: None + }) + } + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select( + "SELECT JSON_OBJECT('user_name':USER_NAME(), @id_key:@id_value, 'sid':(SELECT @@SPID))", + ); + match expr_from_projection(&select.projection[0]) { + Expr::JsonObject(JsonObject { + key_values, + null_clause: None, + }) => { + assert!(matches!( + key_values[0], + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString(_)), + value: Expr::Function(_) + } + )); + assert!(matches!( + key_values[1], + JsonKeyValue { + key: Expr::Identifier(_), + value: Expr::Identifier(_) + } + )); + assert!(matches!( + key_values[2], + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString(_)), + value: Expr::Subquery(_) + } + )); + } + _ => unreachable!(), + } + let select = ms().verified_only_select( + "SELECT s.session_id, JSON_OBJECT('security_id':s.security_id, 'login':s.login_name, 'status':s.status) AS info \ + FROM sys.dm_exec_sessions AS s \ + WHERE s.is_user_process = 1", + ); + match &select.projection[1] { + SelectItem::ExprWithAlias { expr, .. } => { + assert_eq!( + &Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("security_id".into())), + value: Expr::CompoundIdentifier(vec![ + Ident { + value: "s".into(), + quote_style: None + }, + Ident { + value: "security_id".into(), + quote_style: None + } + ]) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("login".into())), + value: Expr::CompoundIdentifier(vec![ + Ident { + value: "s".into(), + quote_style: None + }, + Ident { + value: "login_name".into(), + quote_style: None + } + ]) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("status".into())), + value: Expr::CompoundIdentifier(vec![ + Ident { + value: "s".into(), + quote_style: None + }, + Ident { + value: "status".into(), + quote_style: None + } + ]) + } + ], + null_clause: None + }), + expr + ); + } + _ => unreachable!(), + } +} + +#[test] +fn parse_mssql_json_array() { + let select = ms().verified_only_select("SELECT JSON_ARRAY()"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, 'b', 2)"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(Value::SingleQuotedString("a".into())), + Expr::Value(number("1")), + Expr::Value(Value::SingleQuotedString("b".into())), + Expr::Value(number("2")) + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, 'b', NULL)"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(Value::SingleQuotedString("a".into())), + Expr::Value(number("1")), + Expr::Value(Value::SingleQuotedString("b".into())), + Expr::Value(Value::Null) + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 NULL ON NULL)"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(Value::SingleQuotedString("a".into())), + Expr::Value(number("1")), + Expr::Value(Value::Null), + Expr::Value(number("2")) + ], + null_clause: Some(JsonNullClause::NullOnNull) + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 ABSENT ON NULL)"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(Value::SingleQuotedString("a".into())), + Expr::Value(number("1")), + Expr::Value(Value::Null), + Expr::Value(number("2")) + ], + null_clause: Some(JsonNullClause::AbsentOnNull) + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY(NULL ON NULL)"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![], + null_clause: Some(JsonNullClause::NullOnNull) + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY(ABSENT ON NULL)"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![], + null_clause: Some(JsonNullClause::AbsentOnNull) + }), + expr_from_projection(&select.projection[0]) + ); + let select = + ms().verified_only_select("SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1))"); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(Value::SingleQuotedString("a".into())), + Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("value".into())) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type".into())), + value: Expr::Value(number("1")) + } + ], + null_clause: None + }) + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select( + "SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1), JSON_ARRAY(1, NULL, 2 NULL ON NULL))", + ); + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(Value::SingleQuotedString("a".into())), + Expr::JsonObject(JsonObject { + key_values: vec![ + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("name".into())), + value: Expr::Value(Value::SingleQuotedString("value".into())) + }, + JsonKeyValue { + key: Expr::Value(Value::SingleQuotedString("type".into())), + value: Expr::Value(number("1")) + } + ], + null_clause: None + }), + Expr::JsonArray(JsonArray { + args: vec![ + Expr::Value(number("1")), + Expr::Value(Value::Null), + Expr::Value(number("2")), + ], + null_clause: Some(JsonNullClause::NullOnNull) + }) + ], + null_clause: None + }), + expr_from_projection(&select.projection[0]) + ); + let select = ms().verified_only_select("SELECT JSON_ARRAY(1, @id_value, (SELECT @@SPID))"); + match expr_from_projection(&select.projection[0]) { + Expr::JsonArray(JsonArray { + args, + null_clause: None, + }) => { + assert!(matches!(args[0], Expr::Value(Value::Number(..)))); + assert!(matches!(args[1], Expr::Identifier(_))); + assert!(matches!(args[2], Expr::Subquery(_))); + } + _ => unreachable!(), + } + let select = ms().verified_only_select( + "SELECT s.session_id, JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name) AS info \ + FROM sys.dm_exec_sessions AS s \ + WHERE s.is_user_process = 1", + ); + match &select.projection[1] { + SelectItem::ExprWithAlias { expr, .. } => { + assert_eq!( + &Expr::JsonArray(JsonArray { + args: vec![ + Expr::CompoundIdentifier(vec![ + Ident { + value: "s".into(), + quote_style: None + }, + Ident { + value: "host_name".into(), + quote_style: None + } + ]), + Expr::CompoundIdentifier(vec![ + Ident { + value: "s".into(), + quote_style: None + }, + Ident { + value: "program_name".into(), + quote_style: None + } + ]), + Expr::CompoundIdentifier(vec![ + Ident { + value: "s".into(), + quote_style: None + }, + Ident { + value: "client_interface_name".into(), + quote_style: None + } + ]) + ], + null_clause: None + }), + expr + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_ampersand_arobase() { // In SQL Server, a&@b means (a) & (@b), in PostgreSQL it means (a) &@ (b) From b3147366bbcbbe7076aeec22d5334d3da2134ed7 Mon Sep 17 00:00:00 2001 From: gaoqiangz <38213294+gaoqiangz@users.noreply.github.com> Date: Sun, 10 Nov 2024 00:34:17 +0800 Subject: [PATCH 05/17] Update src/ast/mod.rs Co-authored-by: Ifeanyi Ubah --- src/ast/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 69609a9c0..21ae694b3 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -955,7 +955,7 @@ pub enum Expr { /// /// Reference: JsonArray(JsonArray), - /// MSSQL's `JSON_OBJECT` function for construct JSON-OBJECT object + /// MSSQL's `JSON_OBJECT` function to construct a `JSON-OBJECT` object /// /// Syntax: /// From 8cee33bb764dcef91fe9c1c42e901aeb490da996 Mon Sep 17 00:00:00 2001 From: gaoqiangz <38213294+gaoqiangz@users.noreply.github.com> Date: Sun, 10 Nov 2024 00:34:26 +0800 Subject: [PATCH 06/17] Update src/ast/mod.rs Co-authored-by: Ifeanyi Ubah --- src/ast/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 21ae694b3..77bd225fa 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -972,9 +972,9 @@ pub enum Expr { /// Example: /// /// ```sql - /// SELECT JSON_OBJECT('name':'value', 'type':1) --{"name":"value","type":1} - /// SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL) --{"name":"value"} - /// SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2)) --{"name":"value","type":[1,2]} + /// SELECT JSON_OBJECT('name':'value', 'type':1) + /// SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL) + /// SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2)) /// ``` /// /// Reference: From ba52214b12c9a152aea404d6469e3ca52cf750d0 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Sun, 10 Nov 2024 13:26:19 +0800 Subject: [PATCH 07/17] Code review comments --- src/ast/mod.rs | 129 +------ src/parser/mod.rs | 126 +++---- tests/sqlparser_common.rs | 8 +- tests/sqlparser_duckdb.rs | 4 +- tests/sqlparser_mssql.rs | 744 ++++++++++++++++++++------------------ 5 files changed, 470 insertions(+), 541 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 77bd225fa..0630760f0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -931,54 +931,6 @@ pub enum Expr { /// /// See . Lambda(LambdaFunction), - /// MSSQL's `JSON_ARRAY` function for construct JSON-ARRAY object - /// - /// Syntax: - /// - /// ```plaintext - /// JSON_ARRAY ( [ [,...n] ] [ ] ) - /// - /// ::= value_expression - /// - /// ::= - /// NULL ON NULL - /// | ABSENT ON NULL - /// ``` - /// - /// Example: - /// - /// ```sql - /// SELECT JSON_ARRAY('a', 1, 'b', 2) --["a",1,"b",2] - /// SELECT JSON_ARRAY('a', 1, NULL, 2 NULL ON NULL) --["a",1,null,2] - /// SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1), JSON_ARRAY(1, null, 2 NULL ON NULL)) --["a",{"name":"value","type":1},[1,null,2]] - /// ``` - /// - /// Reference: - JsonArray(JsonArray), - /// MSSQL's `JSON_OBJECT` function to construct a `JSON-OBJECT` object - /// - /// Syntax: - /// - /// ```plaintext - /// JSON_OBJECT ( [ [,...n] ] [ json_null_clause ] ) - /// - /// ::= json_key_name : value_expression - /// - /// ::= - /// NULL ON NULL - /// | ABSENT ON NULL - /// ``` - /// - /// Example: - /// - /// ```sql - /// SELECT JSON_OBJECT('name':'value', 'type':1) - /// SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL) - /// SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2)) - /// ``` - /// - /// Reference: - JsonObject(JsonObject), } /// The contents inside the `[` and `]` in a subscript expression. @@ -1706,8 +1658,6 @@ impl fmt::Display for Expr { } Expr::Prior(expr) => write!(f, "PRIOR {expr}"), Expr::Lambda(lambda) => write!(f, "{lambda}"), - Expr::JsonArray(json_array) => write!(f, "{json_array}"), - Expr::JsonObject(json_object) => write!(f, "{json_object}"), } } } @@ -5383,6 +5333,8 @@ pub enum FunctionArgOperator { RightArrow, /// function(arg1 := value1) Assignment, + /// function(arg1 : value1) + Colon, } impl fmt::Display for FunctionArgOperator { @@ -5391,6 +5343,7 @@ impl fmt::Display for FunctionArgOperator { FunctionArgOperator::Equals => f.write_str("="), FunctionArgOperator::RightArrow => f.write_str("=>"), FunctionArgOperator::Assignment => f.write_str(":="), + FunctionArgOperator::Colon => f.write_str(":"), } } } @@ -5400,7 +5353,7 @@ impl fmt::Display for FunctionArgOperator { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum FunctionArg { Named { - name: Ident, + name: Expr, arg: FunctionArgExpr, operator: FunctionArgOperator, }, @@ -5553,7 +5506,10 @@ impl fmt::Display for FunctionArgumentList { } write!(f, "{}", display_comma_separated(&self.args))?; if !self.clauses.is_empty() { - write!(f, " {}", display_separated(&self.clauses, " "))?; + if !self.args.is_empty() { + write!(f, " ")?; + } + write!(f, "{}", display_separated(&self.clauses, " "))?; } Ok(()) } @@ -5595,6 +5551,11 @@ pub enum FunctionArgumentClause { /// /// [`GROUP_CONCAT`]: https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_group-concat Separator(Value), + /// The json-null-clause to the [`JSON_ARRAY`]/[`JSON_OBJECT`] function in MSSQL. + /// + /// [`JSON_ARRAY`]: + /// [`JSON_OBJECT`]: + JsonNullClause(JsonNullClause), } impl fmt::Display for FunctionArgumentClause { @@ -5610,6 +5571,7 @@ impl fmt::Display for FunctionArgumentClause { FunctionArgumentClause::OnOverflow(on_overflow) => write!(f, "{on_overflow}"), FunctionArgumentClause::Having(bound) => write!(f, "{bound}"), FunctionArgumentClause::Separator(sep) => write!(f, "SEPARATOR {sep}"), + FunctionArgumentClause::JsonNullClause(null_clause) => write!(f, "{null_clause}"), } } } @@ -7367,69 +7329,6 @@ impl Display for UtilityOption { } } -/// MSSQL's `JSON_ARRAY` constructor function -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct JsonArray { - pub args: Vec, - pub null_clause: Option, -} - -impl Display for JsonArray { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "JSON_ARRAY({}", display_comma_separated(&self.args))?; - if let Some(null_clause) = &self.null_clause { - if !self.args.is_empty() { - write!(f, " ")?; - } - write!(f, "{null_clause}")?; - } - write!(f, ")") - } -} - -/// MSSQL's `JSON_OBJECT` constructor function -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct JsonObject { - pub key_values: Vec, - pub null_clause: Option, -} - -impl Display for JsonObject { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "JSON_OBJECT({}", - display_comma_separated(&self.key_values) - )?; - if let Some(null_clause) = &self.null_clause { - if !self.key_values.is_empty() { - write!(f, " ")?; - } - write!(f, "{null_clause}")?; - } - write!(f, ")") - } -} - -/// A key-value pair of MSSQL's `JSON_OBJECT` -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct JsonKeyValue { - pub key: Expr, - pub value: Expr, -} - -impl Display for JsonKeyValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.key, self.value) - } -} - /// MSSQL's json null clause #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b70f25434..21458fae9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -991,7 +991,7 @@ impl<'a> Parser<'a> { if let Some(expr) = opt_expr { return Ok(expr); } - + let next_token = self.next_token(); let expr = match next_token.token { Token::Word(w) => match w.keyword { @@ -1028,11 +1028,6 @@ impl<'a> Parser<'a> { Keyword::CAST => self.parse_cast_expr(CastKind::Cast), Keyword::TRY_CAST => self.parse_cast_expr(CastKind::TryCast), Keyword::SAFE_CAST => self.parse_cast_expr(CastKind::SafeCast), - Keyword::JSON_ARRAY - if self.peek_token() == Token::LParen - && dialect_of!(self is MsSqlDialect) => self.parse_json_array(), - Keyword::JSON_OBJECT if self.peek_token() == Token::LParen - && dialect_of!(self is MsSqlDialect) => self.parse_json_object(), Keyword::EXISTS // Support parsing Databricks has a function named `exists`. if !dialect_of!(self is DatabricksDialect) @@ -3018,70 +3013,6 @@ impl<'a> Parser<'a> { }) } - /// Parses MSSQL's `JSON_ARRAY` constructor function - pub fn parse_json_array(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let mut args = Vec::new(); - let mut null_clause = None; - loop { - // JSON_ARRAY() - if self.consume_token(&Token::RParen) { - self.prev_token(); - break; - } - // JSON_ARRAY(NULL ON NULL) - else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { - null_clause = Some(JsonNullClause::NullOnNull); - break; - } - // JSON_ARRAY(ABSENT ON NULL) - else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { - null_clause = Some(JsonNullClause::AbsentOnNull); - break; - } else if !args.is_empty() && !self.consume_token(&Token::Comma) { - break; - } - args.push(self.parse_expr()?); - } - self.expect_token(&Token::RParen)?; - Ok(Expr::JsonArray(JsonArray { args, null_clause })) - } - - /// Parses MSSQL's `JSON_OBJECT` constructor function - pub fn parse_json_object(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let mut key_values = Vec::new(); - let mut null_clause = None; - loop { - // JSON_OBJECT() - if self.consume_token(&Token::RParen) { - self.prev_token(); - break; - } - // JSON_OBJECT(NULL ON NULL) - else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { - null_clause = Some(JsonNullClause::NullOnNull); - break; - } - // JSON_OBJECT(ABSENT ON NULL) - else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { - null_clause = Some(JsonNullClause::AbsentOnNull); - break; - } else if !key_values.is_empty() && !self.consume_token(&Token::Comma) { - break; - } - let key = self.parse_expr()?; - self.expect_token(&Token::Colon)?; - let value = self.parse_expr()?; - key_values.push(JsonKeyValue { - key, - value - }); - } - self.expect_token(&Token::RParen)?; - Ok(Expr::JsonObject(JsonObject { key_values, null_clause })) - } - /// Parses the parens following the `[ NOT ] IN` operator. pub fn parse_in(&mut self, expr: Expr, negated: bool) -> Result { // BigQuery allows `IN UNNEST(array_expression)` @@ -11129,7 +11060,7 @@ impl<'a> Parser<'a> { pub fn parse_function_args(&mut self) -> Result { if self.peek_nth_token(1) == Token::RArrow { - let name = self.parse_identifier(false)?; + let name = Expr::Identifier(self.parse_identifier(false)?); self.expect_token(&Token::RArrow)?; let arg = self.parse_wildcard_expr()?.into(); @@ -11142,7 +11073,7 @@ impl<'a> Parser<'a> { } else if self.dialect.supports_named_fn_args_with_eq_operator() && self.peek_nth_token(1) == Token::Eq { - let name = self.parse_identifier(false)?; + let name = Expr::Identifier(self.parse_identifier(false)?); self.expect_token(&Token::Eq)?; let arg = self.parse_wildcard_expr()?.into(); @@ -11155,7 +11086,7 @@ impl<'a> Parser<'a> { } else if dialect_of!(self is DuckDbDialect | GenericDialect) && self.peek_nth_token(1) == Token::Assignment { - let name = self.parse_identifier(false)?; + let name = Expr::Identifier(self.parse_identifier(false)?); self.expect_token(&Token::Assignment)?; let arg = self.parse_expr()?.into(); @@ -11165,6 +11096,19 @@ impl<'a> Parser<'a> { arg, operator: FunctionArgOperator::Assignment, }) + } else if dialect_of!(self is MsSqlDialect) { + // FUNC( : ) + let name = self.parse_wildcard_expr()?; + if self.consume_token(&Token::Colon) { + let arg = self.parse_expr()?.into(); + Ok(FunctionArg::Named { + name, + arg, + operator: FunctionArgOperator::Colon, + }) + } else { + Ok(FunctionArg::Unnamed(name.into())) + } } else { Ok(FunctionArg::Unnamed(self.parse_wildcard_expr()?.into())) } @@ -11210,19 +11154,34 @@ impl<'a> Parser<'a> { /// FIRST_VALUE(x IGNORE NULL); /// ``` fn parse_function_argument_list(&mut self) -> Result { + let mut clauses = vec![]; + + if dialect_of!(self is MsSqlDialect) { + // JSON_ARRAY(NULL ON NULL) + if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { + clauses.push(FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull, + )); + } + // JSON_ARRAY(ABSENT ON NULL) + else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { + clauses.push(FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull, + )); + } + } + if self.consume_token(&Token::RParen) { return Ok(FunctionArgumentList { duplicate_treatment: None, args: vec![], - clauses: vec![], + clauses, }); } let duplicate_treatment = self.parse_duplicate_treatment()?; let args = self.parse_comma_separated(Parser::parse_function_args)?; - let mut clauses = vec![]; - if self.dialect.supports_window_function_null_treatment_arg() { if let Some(null_treatment) = self.parse_null_treatment()? { clauses.push(FunctionArgumentClause::IgnoreOrRespectNulls(null_treatment)); @@ -11263,6 +11222,21 @@ impl<'a> Parser<'a> { clauses.push(FunctionArgumentClause::OnOverflow(on_overflow)); } + if dialect_of!(self is MsSqlDialect) { + // JSON_ARRAY( NULL ON NULL) + if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { + clauses.push(FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull, + )); + } + // JSON_ARRAY( ABSENT ON NULL) + else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { + clauses.push(FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull, + )); + } + } + self.expect_token(&Token::RParen)?; Ok(FunctionArgumentList { duplicate_treatment, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 4016e5a69..44323017d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4421,14 +4421,14 @@ fn parse_named_argument_function() { duplicate_treatment: None, args: vec![ FunctionArg::Named { - name: Ident::new("a"), + name: Expr::Identifier(Ident::new("a")), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "1".to_owned() ))), operator: FunctionArgOperator::RightArrow }, FunctionArg::Named { - name: Ident::new("b"), + name: Expr::Identifier(Ident::new("b")), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "2".to_owned() ))), @@ -4460,14 +4460,14 @@ fn parse_named_argument_function_with_eq_operator() { duplicate_treatment: None, args: vec![ FunctionArg::Named { - name: Ident::new("a"), + name: Expr::Identifier(Ident::new("a")), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "1".to_owned() ))), operator: FunctionArgOperator::Equals }, FunctionArg::Named { - name: Ident::new("b"), + name: Expr::Identifier(Ident::new("b")), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "2".to_owned() ))), diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index a4109b0a3..0412002b4 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -611,14 +611,14 @@ fn test_duckdb_named_argument_function_with_assignment_operator() { duplicate_treatment: None, args: vec![ FunctionArg::Named { - name: Ident::new("a"), + name: Expr::Identifier(Ident::new("a")), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "1".to_owned() ))), operator: FunctionArgOperator::Assignment }, FunctionArg::Named { - name: Ident::new("b"), + name: Expr::Identifier(Ident::new("b")), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "2".to_owned() ))), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 6d00fda22..cc4ab2854 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -450,385 +450,441 @@ fn parse_for_json_expect_ast() { #[test] fn parse_mssql_json_object() { - let select = ms().verified_only_select("SELECT JSON_OBJECT()"); - assert_eq!( - &Expr::JsonObject(JsonObject { - key_values: vec![], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); - let select = ms().verified_only_select("SELECT JSON_OBJECT('name':'value', 'type':1)"); - assert_eq!( - &Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("value".into())) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type".into())), - value: Expr::Value(number("1")) - } - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); - let select = - ms().verified_only_select("SELECT JSON_OBJECT('name':'value', 'type':NULL ABSENT ON NULL)"); - assert_eq!( - &Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("value".into())) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type".into())), - value: Expr::Value(Value::Null) - } - ], - null_clause: Some(JsonNullClause::AbsentOnNull) - }), - expr_from_projection(&select.projection[0]) - ); - let select = - ms().verified_only_select("SELECT JSON_OBJECT('name':'value', 'type':JSON_ARRAY(1, 2))"); - assert_eq!( - &Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("value".into())) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type".into())), - value: Expr::JsonArray(JsonArray { - args: vec![Expr::Value(number("1")), Expr::Value(number("2"))], - null_clause: None - }) - } - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); + let select = ms().verified_only_select("SELECT JSON_OBJECT('name' : 'value', 'type' : 1)"); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, .. }) => { + assert_eq!( + &[ + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString("type".into())), + arg: FunctionArgExpr::Expr(Expr::Value(number("1"))), + operator: FunctionArgOperator::Colon + } + ], + &args[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + let select = ms() + .verified_only_select("SELECT JSON_OBJECT('name' : 'value', 'type' : NULL ABSENT ON NULL)"); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert_eq!( + &[ + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString("type".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::Null)), + operator: FunctionArgOperator::Colon + } + ], + &args[..] + ); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + let select = ms().verified_only_select("SELECT JSON_OBJECT(NULL ON NULL)"); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + let select = ms().verified_only_select("SELECT JSON_OBJECT(ABSENT ON NULL)"); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select( - "SELECT JSON_OBJECT('name':'value', 'type':JSON_OBJECT('type_id':1, 'name':'a'))", + "SELECT JSON_OBJECT('name' : 'value', 'type' : JSON_ARRAY(1, 2) ABSENT ON NULL)", ); - assert_eq!( - &Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("value".into())) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type".into())), - value: Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type_id".into())), - value: Expr::Value(number("1")) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("a".into())) - } - ], - null_clause: None - }) - } - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert_eq!( + &FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Function(_)), + operator: FunctionArgOperator::Colon + } + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + let select = ms().verified_only_select( + "SELECT JSON_OBJECT('name' : 'value', 'type' : JSON_OBJECT('type_id' : 1, 'name' : 'a') NULL ON NULL)", ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert_eq!( + &FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Function(_)), + operator: FunctionArgOperator::Colon + } + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select( - "SELECT JSON_OBJECT('user_name':USER_NAME(), @id_key:@id_value, 'sid':(SELECT @@SPID))", + "SELECT JSON_OBJECT('user_name' : USER_NAME(), LOWER(@id_key) : @id_value, 'sid' : (SELECT @@SPID) ABSENT ON NULL)", ); match expr_from_projection(&select.projection[0]) { - Expr::JsonObject(JsonObject { - key_values, - null_clause: None, - }) => { - assert!(matches!( - key_values[0], - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString(_)), - value: Expr::Function(_) - } - )); - assert!(matches!( - key_values[1], - JsonKeyValue { - key: Expr::Identifier(_), - value: Expr::Identifier(_) - } - )); - assert!(matches!( - key_values[2], - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString(_)), - value: Expr::Subquery(_) - } - )); - } + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert!(matches!( + args[0], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Function(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[1], + FunctionArg::Named { + name: Expr::Function(_), + arg: FunctionArgExpr::Expr(Expr::Identifier(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[2], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Subquery(_)), + operator: FunctionArgOperator::Colon + } + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, _ => unreachable!(), } let select = ms().verified_only_select( - "SELECT s.session_id, JSON_OBJECT('security_id':s.security_id, 'login':s.login_name, 'status':s.status) AS info \ + "SELECT s.session_id, JSON_OBJECT('security_id' : s.security_id, 'login' : s.login_name, 'status' : s.status) AS info \ FROM sys.dm_exec_sessions AS s \ WHERE s.is_user_process = 1", ); match &select.projection[1] { - SelectItem::ExprWithAlias { expr, .. } => { - assert_eq!( - &Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("security_id".into())), - value: Expr::CompoundIdentifier(vec![ - Ident { - value: "s".into(), - quote_style: None - }, - Ident { - value: "security_id".into(), - quote_style: None - } - ]) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("login".into())), - value: Expr::CompoundIdentifier(vec![ - Ident { - value: "s".into(), - quote_style: None - }, - Ident { - value: "login_name".into(), - quote_style: None - } - ]) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("status".into())), - value: Expr::CompoundIdentifier(vec![ - Ident { - value: "s".into(), - quote_style: None - }, - Ident { - value: "status".into(), - quote_style: None - } - ]) - } - ], - null_clause: None - }), - expr - ); - } + SelectItem::ExprWithAlias { expr, .. } => match expr { + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }) => { + assert!(matches!( + args[0], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[1], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[2], + FunctionArg::Named { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), + operator: FunctionArgOperator::Colon + } + )); + } + _ => unreachable!(), + }, _ => unreachable!(), } } #[test] fn parse_mssql_json_array() { - let select = ms().verified_only_select("SELECT JSON_ARRAY()"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); - let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, 'b', 2)"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(Value::SingleQuotedString("a".into())), - Expr::Value(number("1")), - Expr::Value(Value::SingleQuotedString("b".into())), - Expr::Value(number("2")) - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); - let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, 'b', NULL)"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(Value::SingleQuotedString("a".into())), - Expr::Value(number("1")), - Expr::Value(Value::SingleQuotedString("b".into())), - Expr::Value(Value::Null) - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 NULL ON NULL)"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(Value::SingleQuotedString("a".into())), - Expr::Value(number("1")), - Expr::Value(Value::Null), - Expr::Value(number("2")) - ], - null_clause: Some(JsonNullClause::NullOnNull) - }), - expr_from_projection(&select.projection[0]) - ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert_eq!( + &[ + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Null))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), + ], + &args[..] + ); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 ABSENT ON NULL)"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(Value::SingleQuotedString("a".into())), - Expr::Value(number("1")), - Expr::Value(Value::Null), - Expr::Value(number("2")) - ], - null_clause: Some(JsonNullClause::AbsentOnNull) - }), - expr_from_projection(&select.projection[0]) - ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert_eq!( + &[ + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Null))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), + ], + &args[..] + ); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select("SELECT JSON_ARRAY(NULL ON NULL)"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![], - null_clause: Some(JsonNullClause::NullOnNull) - }), - expr_from_projection(&select.projection[0]) - ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select("SELECT JSON_ARRAY(ABSENT ON NULL)"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![], - null_clause: Some(JsonNullClause::AbsentOnNull) - }), - expr_from_projection(&select.projection[0]) - ); - let select = - ms().verified_only_select("SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1))"); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(Value::SingleQuotedString("a".into())), - Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("value".into())) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type".into())), - value: Expr::Value(number("1")) - } - ], - null_clause: None - }) - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) - ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select( - "SELECT JSON_ARRAY('a', JSON_OBJECT('name':'value', 'type':1), JSON_ARRAY(1, NULL, 2 NULL ON NULL))", + "SELECT JSON_ARRAY('a', JSON_OBJECT('name' : 'value', 'type' : 1) NULL ON NULL)", ); - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(Value::SingleQuotedString("a".into())), - Expr::JsonObject(JsonObject { - key_values: vec![ - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("name".into())), - value: Expr::Value(Value::SingleQuotedString("value".into())) - }, - JsonKeyValue { - key: Expr::Value(Value::SingleQuotedString("type".into())), - value: Expr::Value(number("1")) - } - ], - null_clause: None - }), - Expr::JsonArray(JsonArray { - args: vec![ - Expr::Value(number("1")), - Expr::Value(Value::Null), - Expr::Value(number("2")), - ], - null_clause: Some(JsonNullClause::NullOnNull) - }) - ], - null_clause: None - }), - expr_from_projection(&select.projection[0]) + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { + assert_eq!( + &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + let select = ms().verified_only_select( + "SELECT JSON_ARRAY('a', JSON_OBJECT('name' : 'value', 'type' : 1), JSON_ARRAY(1, NULL, 2 NULL ON NULL))", ); + match expr_from_projection(&select.projection[0]) { + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, .. }) => { + assert_eq!( + &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) + )); + assert!(matches!( + args[2], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) + )); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } let select = ms().verified_only_select("SELECT JSON_ARRAY(1, @id_value, (SELECT @@SPID))"); match expr_from_projection(&select.projection[0]) { - Expr::JsonArray(JsonArray { - args, - null_clause: None, - }) => { - assert!(matches!(args[0], Expr::Value(Value::Number(..)))); - assert!(matches!(args[1], Expr::Identifier(_))); - assert!(matches!(args[2], Expr::Subquery(_))); - } + Expr::Function(Function { args, .. }) => match args { + FunctionArguments::List(FunctionArgumentList { args, .. }) => { + assert_eq!( + &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(_))) + )); + assert!(matches!( + args[2], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Subquery(_))) + )); + } + _ => unreachable!(), + }, _ => unreachable!(), } let select = ms().verified_only_select( - "SELECT s.session_id, JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name) AS info \ + "SELECT s.session_id, JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name NULL ON NULL) AS info \ FROM sys.dm_exec_sessions AS s \ WHERE s.is_user_process = 1", ); match &select.projection[1] { - SelectItem::ExprWithAlias { expr, .. } => { - assert_eq!( - &Expr::JsonArray(JsonArray { - args: vec![ - Expr::CompoundIdentifier(vec![ - Ident { - value: "s".into(), - quote_style: None - }, - Ident { - value: "host_name".into(), - quote_style: None - } - ]), - Expr::CompoundIdentifier(vec![ - Ident { - value: "s".into(), - quote_style: None - }, - Ident { - value: "program_name".into(), - quote_style: None - } - ]), - Expr::CompoundIdentifier(vec![ - Ident { - value: "s".into(), - quote_style: None - }, - Ident { - value: "client_interface_name".into(), - quote_style: None - } - ]) - ], - null_clause: None - }), - expr - ); - } + SelectItem::ExprWithAlias { expr, .. } => match expr { + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(matches!( + args[0], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) + )); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) + )); + assert!(matches!( + args[2], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } + _ => unreachable!(), + }, _ => unreachable!(), } } From 31ae437c39e9906b7b4c4f0a2a445c07920b7d7e Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Sun, 10 Nov 2024 13:31:18 +0800 Subject: [PATCH 08/17] Remove unnecessary keywords --- src/keywords.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/keywords.rs b/src/keywords.rs index b4a38f376..d21379cc4 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -420,8 +420,6 @@ define_keywords!( JSON, JSONB, JSONFILE, - JSON_ARRAY, - JSON_OBJECT, JSON_TABLE, JULIAN, KEY, From 43b0652bb1469e253ef3e1d9e606f43c30dba845 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Sun, 10 Nov 2024 21:10:42 +0800 Subject: [PATCH 09/17] Code review comments --- src/ast/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 0630760f0..3cd604225 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -7330,6 +7330,14 @@ impl Display for UtilityOption { } /// MSSQL's json null clause +/// +/// ```plaintext +/// ::= +/// NULL ON NULL +/// | ABSENT ON NULL +/// ``` +/// +/// #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] From 05dc98ca65894cbb5b701846018e4eded01bb65e Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Sun, 10 Nov 2024 21:46:32 +0800 Subject: [PATCH 10/17] Code review comments --- src/parser/mod.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 21458fae9..f19e54fe7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11096,21 +11096,20 @@ impl<'a> Parser<'a> { arg, operator: FunctionArgOperator::Assignment, }) - } else if dialect_of!(self is MsSqlDialect) { - // FUNC( : ) + } else { let name = self.parse_wildcard_expr()?; - if self.consume_token(&Token::Colon) { - let arg = self.parse_expr()?.into(); - Ok(FunctionArg::Named { - name, - arg, - operator: FunctionArgOperator::Colon, - }) - } else { - Ok(FunctionArg::Unnamed(name.into())) + if dialect_of!(self is MsSqlDialect) { + // FUNC( : ) + if self.consume_token(&Token::Colon) { + let arg = self.parse_expr()?.into(); + return Ok(FunctionArg::Named { + name, + arg, + operator: FunctionArgOperator::Colon, + }); + } } - } else { - Ok(FunctionArg::Unnamed(self.parse_wildcard_expr()?.into())) + Ok(FunctionArg::Unnamed(name.into())) } } From 9a2c4c4f627fe6ca6d2f6ab0633b04501473d2ba Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Wed, 13 Nov 2024 16:09:21 +0800 Subject: [PATCH 11/17] Refactor parsing function argument list --- src/ast/mod.rs | 16 + src/dialect/duckdb.rs | 4 + src/dialect/generic.rs | 4 + src/dialect/mod.rs | 23 +- src/dialect/mssql.rs | 12 + src/parser/mod.rs | 146 ++++---- tests/sqlparser_common.rs | 11 +- tests/sqlparser_duckdb.rs | 4 +- tests/sqlparser_mssql.rs | 700 +++++++++++++++++++------------------- 9 files changed, 486 insertions(+), 434 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 3cd604225..7b681c91a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -5352,7 +5352,18 @@ impl fmt::Display for FunctionArgOperator { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum FunctionArg { + /// `name` is identifier + /// + /// Enabled when `Dialect::supports_named_fn_args_with_expr_name` returns 'false' Named { + name: Ident, + arg: FunctionArgExpr, + operator: FunctionArgOperator, + }, + /// `name` is arbitrary expression + /// + /// Enabled when `Dialect::supports_named_fn_args_with_expr_name` returns 'true' + ExprNamed { name: Expr, arg: FunctionArgExpr, operator: FunctionArgOperator, @@ -5368,6 +5379,11 @@ impl fmt::Display for FunctionArg { arg, operator, } => write!(f, "{name} {operator} {arg}"), + FunctionArg::ExprNamed { + name, + arg, + operator, + } => write!(f, "{name} {operator} {arg}"), FunctionArg::Unnamed(unnamed_arg) => write!(f, "{unnamed_arg}"), } } diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index e1b8db118..604a0590e 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -47,6 +47,10 @@ impl Dialect for DuckDbDialect { true } + fn supports_named_fn_args_with_assignment_operator(&self) -> bool { + true + } + // DuckDB uses this syntax for `STRUCT`s. // // https://duckdb.org/docs/sql/data_types/struct.html#creating-structs diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 0a5464c9c..99f85dd22 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -111,4 +111,8 @@ impl Dialect for GenericDialect { fn supports_try_convert(&self) -> bool { true } + + fn supports_named_fn_args_with_assignment_operator(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 28e7ac7d1..566bb4e08 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -231,11 +231,32 @@ pub trait Dialect: Debug + Any { false } - /// Returns true if the dialect supports named arguments of the form FUN(a = '1', b = '2'). + /// Returns true if the dialect supports named arguments of the form `FUN(a = '1', b = '2')`. fn supports_named_fn_args_with_eq_operator(&self) -> bool { false } + /// Returns true if the dialect supports named arguments of the form `FUN(a : '1', b : '2')`. + fn supports_named_fn_args_with_colon_operator(&self) -> bool { + false + } + + /// Returns true if the dialect supports named arguments of the form `FUN(a := '1', b := '2')`. + fn supports_named_fn_args_with_assignment_operator(&self) -> bool { + false + } + + /// Returns true if the dialect supports named arguments of the form `FUN(a => '1', b => '2')`. + fn supports_named_fn_args_with_rarrow_operator(&self) -> bool { + true //for compatible + } + + /// Returns true if dialect supports argument name as arbitrary expression (`FUN(:,...)`),and will use the `FunctionArg::ExprNamed` variant, + /// otherwise use the `FunctionArg::Named` variant (compatible reason). + fn supports_named_fn_args_with_expr_name(&self) -> bool { + false + } + /// Returns true if the dialect supports identifiers starting with a numeric /// prefix such as tables named `59901_user_login` fn supports_numeric_prefix(&self) -> bool { diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index 78ec621ed..512f218c7 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -57,4 +57,16 @@ impl Dialect for MsSqlDialect { fn supports_try_convert(&self) -> bool { true } + + fn supports_named_fn_args_with_colon_operator(&self) -> bool { + true + } + + fn supports_named_fn_args_with_expr_name(&self) -> bool { + true + } + + fn supports_named_fn_args_with_rarrow_operator(&self) -> bool { + false + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f19e54fe7..035aff908 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3138,7 +3138,7 @@ impl<'a> Parser<'a> { /// Return nth non-whitespace token that has not yet been processed pub fn peek_nth_token(&self, mut n: usize) -> TokenWithLocation { - let mut index = self.index; + let mut index: usize = self.index; loop { index += 1; match self.tokens.get(index - 1) { @@ -11059,57 +11059,58 @@ impl<'a> Parser<'a> { } pub fn parse_function_args(&mut self) -> Result { - if self.peek_nth_token(1) == Token::RArrow { - let name = Expr::Identifier(self.parse_identifier(false)?); - - self.expect_token(&Token::RArrow)?; - let arg = self.parse_wildcard_expr()?.into(); - - Ok(FunctionArg::Named { - name, - arg, - operator: FunctionArgOperator::RightArrow, - }) - } else if self.dialect.supports_named_fn_args_with_eq_operator() - && self.peek_nth_token(1) == Token::Eq - { - let name = Expr::Identifier(self.parse_identifier(false)?); - - self.expect_token(&Token::Eq)?; - let arg = self.parse_wildcard_expr()?.into(); - - Ok(FunctionArg::Named { - name, - arg, - operator: FunctionArgOperator::Equals, - }) - } else if dialect_of!(self is DuckDbDialect | GenericDialect) - && self.peek_nth_token(1) == Token::Assignment - { - let name = Expr::Identifier(self.parse_identifier(false)?); - - self.expect_token(&Token::Assignment)?; - let arg = self.parse_expr()?.into(); - - Ok(FunctionArg::Named { - name, - arg, - operator: FunctionArgOperator::Assignment, - }) + let arg = if self.dialect.supports_named_fn_args_with_expr_name() { + self.maybe_parse(|p| { + let name = p.parse_expr()?; + let operator = p.parse_function_named_arg_operator()?; + let arg = p.parse_wildcard_expr()?.into(); + Ok(FunctionArg::ExprNamed { + name, + arg, + operator, + }) + })? } else { - let name = self.parse_wildcard_expr()?; - if dialect_of!(self is MsSqlDialect) { - // FUNC( : ) - if self.consume_token(&Token::Colon) { - let arg = self.parse_expr()?.into(); - return Ok(FunctionArg::Named { - name, - arg, - operator: FunctionArgOperator::Colon, - }); - } + self.maybe_parse(|p| { + let name = p.parse_identifier(false)?; + let operator = p.parse_function_named_arg_operator()?; + let arg = p.parse_wildcard_expr()?.into(); + Ok(FunctionArg::Named { + name, + arg, + operator, + }) + })? + }; + if let Some(arg) = arg { + return Ok(arg); + } + Ok(FunctionArg::Unnamed(self.parse_wildcard_expr()?.into())) + } + + fn parse_function_named_arg_operator(&mut self) -> Result { + let tok = self.next_token(); + match tok.token { + Token::RArrow if self.dialect.supports_named_fn_args_with_rarrow_operator() => { + Ok(FunctionArgOperator::RightArrow) + } + Token::Eq if self.dialect.supports_named_fn_args_with_eq_operator() => { + Ok(FunctionArgOperator::Equals) + } + Token::Assignment + if self + .dialect + .supports_named_fn_args_with_assignment_operator() => + { + Ok(FunctionArgOperator::Assignment) + } + Token::Colon if self.dialect.supports_named_fn_args_with_colon_operator() => { + Ok(FunctionArgOperator::Colon) + } + _ => { + self.prev_token(); + self.expected("argument operator", tok) } - Ok(FunctionArg::Unnamed(name.into())) } } @@ -11155,19 +11156,9 @@ impl<'a> Parser<'a> { fn parse_function_argument_list(&mut self) -> Result { let mut clauses = vec![]; - if dialect_of!(self is MsSqlDialect) { - // JSON_ARRAY(NULL ON NULL) - if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { - clauses.push(FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull, - )); - } - // JSON_ARRAY(ABSENT ON NULL) - else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { - clauses.push(FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull, - )); - } + // For MSSQL empty argument list with json-null-clause case, e.g. `JSON_ARRAY(NULL ON NULL)` + if let Some(null_clause) = self.parse_json_null_clause() { + clauses.push(FunctionArgumentClause::JsonNullClause(null_clause)); } if self.consume_token(&Token::RParen) { @@ -11221,19 +11212,8 @@ impl<'a> Parser<'a> { clauses.push(FunctionArgumentClause::OnOverflow(on_overflow)); } - if dialect_of!(self is MsSqlDialect) { - // JSON_ARRAY( NULL ON NULL) - if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { - clauses.push(FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull, - )); - } - // JSON_ARRAY( ABSENT ON NULL) - else if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { - clauses.push(FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull, - )); - } + if let Some(null_clause) = self.parse_json_null_clause() { + clauses.push(FunctionArgumentClause::JsonNullClause(null_clause)); } self.expect_token(&Token::RParen)?; @@ -11244,6 +11224,20 @@ impl<'a> Parser<'a> { }) } + /// Parses MSSQL's json-null-clause + fn parse_json_null_clause(&mut self) -> Option { + if !dialect_of!(self is MsSqlDialect) { + return None; + } + if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { + Some(JsonNullClause::AbsentOnNull) + } else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { + Some(JsonNullClause::NullOnNull) + } else { + None + } + } + fn parse_duplicate_treatment(&mut self) -> Result, ParserError> { let loc = self.peek_token().location; match ( diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 44323017d..d91f14c6f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4410,8 +4410,9 @@ fn parse_explain_query_plan() { #[test] fn parse_named_argument_function() { + let dialects = all_dialects_where(|d| d.supports_named_fn_args_with_rarrow_operator()); let sql = "SELECT FUN(a => '1', b => '2') FROM foo"; - let select = verified_only_select(sql); + let select = dialects.verified_only_select(sql); assert_eq!( &Expr::Function(Function { @@ -4421,14 +4422,14 @@ fn parse_named_argument_function() { duplicate_treatment: None, args: vec![ FunctionArg::Named { - name: Expr::Identifier(Ident::new("a")), + name: Ident::new("a"), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "1".to_owned() ))), operator: FunctionArgOperator::RightArrow }, FunctionArg::Named { - name: Expr::Identifier(Ident::new("b")), + name: Ident::new("b"), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "2".to_owned() ))), @@ -4460,14 +4461,14 @@ fn parse_named_argument_function_with_eq_operator() { duplicate_treatment: None, args: vec![ FunctionArg::Named { - name: Expr::Identifier(Ident::new("a")), + name: Ident::new("a"), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "1".to_owned() ))), operator: FunctionArgOperator::Equals }, FunctionArg::Named { - name: Expr::Identifier(Ident::new("b")), + name: Ident::new("b"), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "2".to_owned() ))), diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 0412002b4..a4109b0a3 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -611,14 +611,14 @@ fn test_duckdb_named_argument_function_with_assignment_operator() { duplicate_treatment: None, args: vec![ FunctionArg::Named { - name: Expr::Identifier(Ident::new("a")), + name: Ident::new("a"), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "1".to_owned() ))), operator: FunctionArgOperator::Assignment }, FunctionArg::Named { - name: Expr::Identifier(Ident::new("b")), + name: Ident::new("b"), arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( "2".to_owned() ))), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index cc4ab2854..4c14a3cb1 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -452,204 +452,202 @@ fn parse_for_json_expect_ast() { fn parse_mssql_json_object() { let select = ms().verified_only_select("SELECT JSON_OBJECT('name' : 'value', 'type' : 1)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, .. }) => { - assert_eq!( - &[ - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString("name".into())), - arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( - "value".into() - ))), - operator: FunctionArgOperator::Colon - }, - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString("type".into())), - arg: FunctionArgExpr::Expr(Expr::Value(number("1"))), - operator: FunctionArgOperator::Colon - } - ], - &args[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }) => assert_eq!( + &[ + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString("type".into())), + arg: FunctionArgExpr::Expr(Expr::Value(number("1"))), + operator: FunctionArgOperator::Colon + } + ], + &args[..] + ), _ => unreachable!(), } let select = ms() .verified_only_select("SELECT JSON_OBJECT('name' : 'value', 'type' : NULL ABSENT ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert_eq!( - &[ - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString("name".into())), - arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( - "value".into() - ))), - operator: FunctionArgOperator::Colon - }, - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString("type".into())), - arg: FunctionArgExpr::Expr(Expr::Value(Value::Null)), - operator: FunctionArgOperator::Colon - } - ], - &args[..] - ); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert_eq!( + &[ + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString("type".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::Null)), + operator: FunctionArgOperator::Colon + } + ], + &args[..] + ); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select("SELECT JSON_OBJECT(NULL ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert!(args.is_empty()); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select("SELECT JSON_OBJECT(ABSENT ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert!(args.is_empty()); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select( "SELECT JSON_OBJECT('name' : 'value', 'type' : JSON_ARRAY(1, 2) ABSENT ON NULL)", ); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert_eq!( - &FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString("name".into())), - arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( - "value".into() - ))), - operator: FunctionArgOperator::Colon - }, - &args[0] - ); - assert!(matches!( - args[1], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::Function(_)), - operator: FunctionArgOperator::Colon - } - )); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert_eq!( + &FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Function(_)), + operator: FunctionArgOperator::Colon + } + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select( "SELECT JSON_OBJECT('name' : 'value', 'type' : JSON_OBJECT('type_id' : 1, 'name' : 'a') NULL ON NULL)", ); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert_eq!( - &FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString("name".into())), - arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( - "value".into() - ))), - operator: FunctionArgOperator::Colon - }, - &args[0] - ); - assert!(matches!( - args[1], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::Function(_)), - operator: FunctionArgOperator::Colon - } - )); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert_eq!( + &FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString("name".into())), + arg: FunctionArgExpr::Expr(Expr::Value(Value::SingleQuotedString( + "value".into() + ))), + operator: FunctionArgOperator::Colon + }, + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Function(_)), + operator: FunctionArgOperator::Colon + } + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select( "SELECT JSON_OBJECT('user_name' : USER_NAME(), LOWER(@id_key) : @id_value, 'sid' : (SELECT @@SPID) ABSENT ON NULL)", ); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert!(matches!( - args[0], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::Function(_)), - operator: FunctionArgOperator::Colon - } - )); - assert!(matches!( - args[1], - FunctionArg::Named { - name: Expr::Function(_), - arg: FunctionArgExpr::Expr(Expr::Identifier(_)), - operator: FunctionArgOperator::Colon - } - )); - assert!(matches!( - args[2], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::Subquery(_)), - operator: FunctionArgOperator::Colon - } - )); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(matches!( + args[0], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Function(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[1], + FunctionArg::ExprNamed { + name: Expr::Function(_), + arg: FunctionArgExpr::Expr(Expr::Identifier(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[2], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::Subquery(_)), + operator: FunctionArgOperator::Colon + } + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select( @@ -658,38 +656,39 @@ fn parse_mssql_json_object() { WHERE s.is_user_process = 1", ); match &select.projection[1] { - SelectItem::ExprWithAlias { expr, .. } => match expr { - Expr::Function(Function { - args: FunctionArguments::List(FunctionArgumentList { args, .. }), - .. - }) => { - assert!(matches!( - args[0], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), - operator: FunctionArgOperator::Colon - } - )); - assert!(matches!( - args[1], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), - operator: FunctionArgOperator::Colon - } - )); - assert!(matches!( - args[2], - FunctionArg::Named { - name: Expr::Value(Value::SingleQuotedString(_)), - arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), - operator: FunctionArgOperator::Colon - } - )); - } - _ => unreachable!(), - }, + SelectItem::ExprWithAlias { + expr: + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }), + .. + } => { + assert!(matches!( + args[0], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[1], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), + operator: FunctionArgOperator::Colon + } + )); + assert!(matches!( + args[2], + FunctionArg::ExprNamed { + name: Expr::Value(Value::SingleQuotedString(_)), + arg: FunctionArgExpr::Expr(Expr::CompoundIdentifier(_)), + operator: FunctionArgOperator::Colon + } + )); + } _ => unreachable!(), } } @@ -698,159 +697,159 @@ fn parse_mssql_json_object() { fn parse_mssql_json_array() { let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 NULL ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert_eq!( - &[ - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("a".into()) - ))), - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Null))), - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), - ], - &args[..] - ); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert_eq!( + &[ + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Null))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), + ], + &args[..] + ); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select("SELECT JSON_ARRAY('a', 1, NULL, 2 ABSENT ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert_eq!( - &[ - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("a".into()) - ))), - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Null))), - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), - ], - &args[..] - ); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert_eq!( + &[ + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Null))), + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), + ], + &args[..] + ); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select("SELECT JSON_ARRAY(NULL ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert!(args.is_empty()); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select("SELECT JSON_ARRAY(ABSENT ON NULL)"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert!(args.is_empty()); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::AbsentOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(args.is_empty()); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::AbsentOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select( "SELECT JSON_ARRAY('a', JSON_OBJECT('name' : 'value', 'type' : 1) NULL ON NULL)", ); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, clauses, .. }) => { - assert_eq!( - &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("a".into()) - ))), - &args[0] - ); - assert!(matches!( - args[1], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) - )); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert_eq!( + &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } let select = ms().verified_only_select( "SELECT JSON_ARRAY('a', JSON_OBJECT('name' : 'value', 'type' : 1), JSON_ARRAY(1, NULL, 2 NULL ON NULL))", ); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, .. }) => { - assert_eq!( - &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("a".into()) - ))), - &args[0] - ); - assert!(matches!( - args[1], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) - )); - assert!(matches!( - args[2], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) - )); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }) => { + assert_eq!( + &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( + Value::SingleQuotedString("a".into()) + ))), + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) + )); + assert!(matches!( + args[2], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(_))) + )); + } _ => unreachable!(), } let select = ms().verified_only_select("SELECT JSON_ARRAY(1, @id_value, (SELECT @@SPID))"); match expr_from_projection(&select.projection[0]) { - Expr::Function(Function { args, .. }) => match args { - FunctionArguments::List(FunctionArgumentList { args, .. }) => { - assert_eq!( - &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), - &args[0] - ); - assert!(matches!( - args[1], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(_))) - )); - assert!(matches!( - args[2], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Subquery(_))) - )); - } - _ => unreachable!(), - }, + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }) => { + assert_eq!( + &FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("1")))), + &args[0] + ); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(_))) + )); + assert!(matches!( + args[2], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Subquery(_))) + )); + } _ => unreachable!(), } let select = ms().verified_only_select( @@ -859,32 +858,33 @@ fn parse_mssql_json_array() { WHERE s.is_user_process = 1", ); match &select.projection[1] { - SelectItem::ExprWithAlias { expr, .. } => match expr { - Expr::Function(Function { - args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), - .. - }) => { - assert!(matches!( - args[0], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) - )); - assert!(matches!( - args[1], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) - )); - assert!(matches!( - args[2], - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) - )); - assert_eq!( - &[FunctionArgumentClause::JsonNullClause( - JsonNullClause::NullOnNull - )], - &clauses[..] - ); - } - _ => unreachable!(), - }, + SelectItem::ExprWithAlias { + expr: + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }), + .. + } => { + assert!(matches!( + args[0], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) + )); + assert!(matches!( + args[1], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) + )); + assert!(matches!( + args[2], + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::CompoundIdentifier(_))) + )); + assert_eq!( + &[FunctionArgumentClause::JsonNullClause( + JsonNullClause::NullOnNull + )], + &clauses[..] + ); + } _ => unreachable!(), } } From 4b7c2eaf7f44c10707ee7040735274e7377c2f73 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Wed, 13 Nov 2024 16:16:23 +0800 Subject: [PATCH 12/17] revert unexpected modified --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 035aff908..097368760 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3138,7 +3138,7 @@ impl<'a> Parser<'a> { /// Return nth non-whitespace token that has not yet been processed pub fn peek_nth_token(&self, mut n: usize) -> TokenWithLocation { - let mut index: usize = self.index; + let mut index = self.index; loop { index += 1; match self.tokens.get(index - 1) { From 09271e239947d07b55bebd275bf4421839826795 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Thu, 14 Nov 2024 10:33:36 +0800 Subject: [PATCH 13/17] Optimize code --- src/parser/mod.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 301989525..0128de13d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11411,13 +11411,16 @@ impl<'a> Parser<'a> { if !dialect_of!(self is MsSqlDialect) { return None; } - if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { - Some(JsonNullClause::AbsentOnNull) - } else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { - Some(JsonNullClause::NullOnNull) - } else { - None + if let Some(kw) = self.parse_one_of_keywords(&[Keyword::ABSENT, Keyword::NULL]) { + if self.parse_keywords(&[Keyword::ON, Keyword::NULL]) { + if kw == Keyword::ABSENT { + return Some(JsonNullClause::AbsentOnNull); + } else if kw == Keyword::NULL { + return Some(JsonNullClause::NullOnNull); + } + } } + None } fn parse_duplicate_treatment(&mut self) -> Result, ParserError> { From b789f3cbce665a32bd7964e8568405c2b2dd66a6 Mon Sep 17 00:00:00 2001 From: gaoqiangz <38213294+gaoqiangz@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:20:30 +0800 Subject: [PATCH 14/17] Update src/dialect/mod.rs Co-authored-by: Ifeanyi Ubah --- src/dialect/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 3b032b812..fd45b3473 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -251,7 +251,9 @@ pub trait Dialect: Debug + Any { true //for compatible } - /// Returns true if dialect supports argument name as arbitrary expression (`FUN(:,...)`),and will use the `FunctionArg::ExprNamed` variant, + /// Returns true if dialect supports argument name as arbitrary expression. + /// e.g. `FUN(LOWER('a'):'1', b:'2')` + /// Such function arguments are represented in the AST by the `FunctionArg::ExprNamed` variant, /// otherwise use the `FunctionArg::Named` variant (compatible reason). fn supports_named_fn_args_with_expr_name(&self) -> bool { false From 78e4f07b826627a51dd1aa0a26cd7f9e898170af Mon Sep 17 00:00:00 2001 From: gaoqiangz <38213294+gaoqiangz@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:20:35 +0800 Subject: [PATCH 15/17] Update src/dialect/mod.rs Co-authored-by: Ifeanyi Ubah --- src/dialect/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index fd45b3473..e77760757 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -248,7 +248,7 @@ pub trait Dialect: Debug + Any { /// Returns true if the dialect supports named arguments of the form `FUN(a => '1', b => '2')`. fn supports_named_fn_args_with_rarrow_operator(&self) -> bool { - true //for compatible + true } /// Returns true if dialect supports argument name as arbitrary expression. From 8182fef77768920d05998003744e3abc2293ed18 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Thu, 14 Nov 2024 15:31:34 +0800 Subject: [PATCH 16/17] Code review comments --- src/parser/mod.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0128de13d..301989525 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11411,16 +11411,13 @@ impl<'a> Parser<'a> { if !dialect_of!(self is MsSqlDialect) { return None; } - if let Some(kw) = self.parse_one_of_keywords(&[Keyword::ABSENT, Keyword::NULL]) { - if self.parse_keywords(&[Keyword::ON, Keyword::NULL]) { - if kw == Keyword::ABSENT { - return Some(JsonNullClause::AbsentOnNull); - } else if kw == Keyword::NULL { - return Some(JsonNullClause::NullOnNull); - } - } + if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { + Some(JsonNullClause::AbsentOnNull) + } else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) { + Some(JsonNullClause::NullOnNull) + } else { + None } - None } fn parse_duplicate_treatment(&mut self) -> Result, ParserError> { From 32b5e4d2f2c62b5895488e02fe91af888d1c1739 Mon Sep 17 00:00:00 2001 From: gaoqiangz Date: Fri, 15 Nov 2024 10:47:02 +0800 Subject: [PATCH 17/17] Code review comments --- src/parser/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 301989525..21283c0f8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11408,9 +11408,6 @@ impl<'a> Parser<'a> { /// Parses MSSQL's json-null-clause fn parse_json_null_clause(&mut self) -> Option { - if !dialect_of!(self is MsSqlDialect) { - return None; - } if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { Some(JsonNullClause::AbsentOnNull) } else if self.parse_keywords(&[Keyword::NULL, Keyword::ON, Keyword::NULL]) {