diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5b50d020c..438021c4a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -7802,11 +7802,16 @@ 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. + /// The `ON NULL` clause for some JSON functions in MSSQL and PostgreSQL /// - /// [`JSON_ARRAY`]: - /// [`JSON_OBJECT`]: + /// [MSSQL `JSON_ARRAY`](https://learn.microsoft.com/en-us/sql/t-sql/functions/json-array-transact-sql?view=sql-server-ver16) + /// [MSSQL `JSON_OBJECT`](https://learn.microsoft.com/en-us/sql/t-sql/functions/json-object-transact-sql?view=sql-server-ver16>) + /// [PostgreSQL JSON functions](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING) JsonNullClause(JsonNullClause), + /// The `RETURNING` clause for some JSON functions in PostgreSQL + /// + /// [`JSON_OBJECT`](https://www.postgresql.org/docs/current/functions-json.html#:~:text=json_object) + JsonReturningClause(JsonReturningClause), } impl fmt::Display for FunctionArgumentClause { @@ -7823,6 +7828,9 @@ impl fmt::Display for FunctionArgumentClause { FunctionArgumentClause::Having(bound) => write!(f, "{bound}"), FunctionArgumentClause::Separator(sep) => write!(f, "SEPARATOR {sep}"), FunctionArgumentClause::JsonNullClause(null_clause) => write!(f, "{null_clause}"), + FunctionArgumentClause::JsonReturningClause(returning_clause) => { + write!(f, "{returning_clause}") + } } } } @@ -10071,6 +10079,25 @@ impl Display for JsonNullClause { } } +/// PostgreSQL JSON function RETURNING clause +/// +/// Example: +/// ```sql +/// JSON_OBJECT('a': 1 RETURNING jsonb) +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonReturningClause { + pub data_type: DataType, +} + +impl Display for JsonReturningClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "RETURNING {}", self.data_type) + } +} + /// rename object definition #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/ast/spans.rs b/src/ast/spans.rs index e17090268..e1b63c779 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1779,6 +1779,7 @@ impl Spanned for FunctionArgumentClause { FunctionArgumentClause::Having(HavingBound(_kind, expr)) => expr.span(), FunctionArgumentClause::Separator(value) => value.span(), FunctionArgumentClause::JsonNullClause(_) => Span::empty(), + FunctionArgumentClause::JsonReturningClause(_) => Span::empty(), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3d4762541..f69c30cd4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15307,7 +15307,7 @@ impl<'a> Parser<'a> { Ok(TableFunctionArgs { args, settings }) } - /// Parses a potentially empty list of arguments to a window function + /// Parses a potentially empty list of arguments to a function /// (including the closing parenthesis). /// /// Examples: @@ -15318,11 +15318,18 @@ impl<'a> Parser<'a> { fn parse_function_argument_list(&mut self) -> Result { let mut clauses = vec![]; - // For MSSQL empty argument list with json-null-clause case, e.g. `JSON_ARRAY(NULL ON NULL)` + // Handle clauses that may exist with an empty argument list + if let Some(null_clause) = self.parse_json_null_clause() { clauses.push(FunctionArgumentClause::JsonNullClause(null_clause)); } + if let Some(json_returning_clause) = self.parse_json_returning_clause()? { + clauses.push(FunctionArgumentClause::JsonReturningClause( + json_returning_clause, + )); + } + if self.consume_token(&Token::RParen) { return Ok(FunctionArgumentList { duplicate_treatment: None, @@ -15378,6 +15385,12 @@ impl<'a> Parser<'a> { clauses.push(FunctionArgumentClause::JsonNullClause(null_clause)); } + if let Some(json_returning_clause) = self.parse_json_returning_clause()? { + clauses.push(FunctionArgumentClause::JsonReturningClause( + json_returning_clause, + )); + } + self.expect_token(&Token::RParen)?; Ok(FunctionArgumentList { duplicate_treatment, @@ -15386,7 +15399,6 @@ impl<'a> Parser<'a> { }) } - /// Parses MSSQL's json-null-clause fn parse_json_null_clause(&mut self) -> Option { if self.parse_keywords(&[Keyword::ABSENT, Keyword::ON, Keyword::NULL]) { Some(JsonNullClause::AbsentOnNull) @@ -15397,6 +15409,15 @@ impl<'a> Parser<'a> { } } + fn parse_json_returning_clause(&mut self) -> Result, ParserError> { + if self.parse_keyword(Keyword::RETURNING) { + let data_type = self.parse_data_type()?; + Ok(Some(JsonReturningClause { data_type })) + } else { + Ok(None) + } + } + fn parse_duplicate_treatment(&mut self) -> Result, ParserError> { let loc = self.peek_token().span.start; match ( diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index a7c9779b1..6a57fa63a 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3351,7 +3351,31 @@ fn test_json() { } #[test] -fn test_fn_arg_with_value_operator() { +fn json_object_colon_syntax() { + match pg().verified_expr("JSON_OBJECT('name' : 'value')") { + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, .. }), + .. + }) => { + assert!( + matches!( + &args[..], + &[FunctionArg::ExprNamed { + operator: FunctionArgOperator::Colon, + .. + }] + ), + "Invalid function argument: {args:?}" + ); + } + other => panic!( + "Expected: JSON_OBJECT('name' : 'value') to be parsed as a function, but got {other:?}" + ), + } +} + +#[test] +fn json_object_value_syntax() { match pg().verified_expr("JSON_OBJECT('name' VALUE 'value')") { Expr::Function(Function { args: FunctionArguments::List(FunctionArgumentList { args, .. }), .. }) => { assert!(matches!( @@ -3363,6 +3387,68 @@ fn test_fn_arg_with_value_operator() { } } +#[test] +fn json_object_with_null_clause() { + match pg().verified_expr("JSON_OBJECT('name' VALUE 'value' NULL ON NULL)") { + Expr::Function(Function { args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), .. }) => { + assert!(matches!( + &args[..], + &[FunctionArg::ExprNamed { operator: FunctionArgOperator::Value, .. }] + ), "Invalid function argument: {args:?}"); + assert_eq!( + clauses, + vec![FunctionArgumentClause::JsonNullClause(JsonNullClause::NullOnNull)], + "Expected: NULL ON NULL to be parsed as a null treatment clause, but got {clauses:?}" + ); + } + other => panic!("Expected: JSON_OBJECT('name' VALUE 'value' NULL ON NULL) to be parsed as a function, but got {other:?}"), + } +} + +#[test] +fn json_object_with_returning_clause() { + match pg().verified_expr("JSON_OBJECT('name' VALUE 'value' RETURNING JSONB)") { + Expr::Function(Function { args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), .. }) => { + assert!(matches!( + &args[..], + &[FunctionArg::ExprNamed { operator: FunctionArgOperator::Value, .. }] + ), "Invalid function argument: {args:?}"); + assert_eq!( + clauses, + vec![FunctionArgumentClause::JsonReturningClause(JsonReturningClause { + data_type: DataType::JSONB + })], + "Expected: RETURNING JSONB to be parsed as a returning clause, but got {clauses:?}" + ); + } + other => panic!("Expected: JSON_OBJECT('name' VALUE 'value' RETURNING jsonb) to be parsed as a function, but got {other:?}"), + } +} + +#[test] +fn json_object_only_returning_clause() { + match pg().verified_expr("JSON_OBJECT(RETURNING JSONB)") { + Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { args, clauses, .. }), + .. + }) => { + assert!(args.is_empty(), "Expected no arguments, got: {args:?}"); + assert_eq!( + clauses, + vec![FunctionArgumentClause::JsonReturningClause( + JsonReturningClause { + data_type: DataType::JSONB + } + )], + "Expected: RETURNING JSONB to be parsed as a returning clause, but got {clauses:?}" + ); + } + other => panic!( + "Expected: JSON_OBJECT(RETURNING jsonb) to be parsed as a function, but got {other:?}" + ), + } +} + #[test] fn parse_json_table_is_not_reserved() { // JSON_TABLE is not a reserved keyword in PostgreSQL, even though it is in SQL:2023