diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 1e430171e..9697c448a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -94,12 +94,12 @@ pub use self::query::{ ForJson, ForXml, FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, - JsonTableNestedColumn, LateralView, LimitClause, LockClause, LockType, MatchRecognizePattern, - MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, - OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions, - PipeOperator, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, - RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, - SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SelectModifiers, + JsonTableNestedColumn, JsonTableOnErrorHandling, LateralView, LimitClause, LockClause, + LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, NamedWindowDefinition, + NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, + OrderByKind, OrderByOptions, PipeOperator, PivotValueSource, ProjectionSelect, Query, + RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, + Select, SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SelectModifiers, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints, TableIndexType, TableSample, TableSampleBucket, @@ -8004,6 +8004,20 @@ pub enum FunctionArgumentClause { /// /// [`JSON_OBJECT`](https://www.postgresql.org/docs/current/functions-json.html#:~:text=json_object) JsonReturningClause(JsonReturningClause), + /// The `PASSING` clause for SQL/JSON query functions in PostgreSQL. + JsonPassingClause(JsonPassingClause), + /// The SQL/JSON `... ON ERROR` clause for `JSON_EXISTS`. + JsonExistsOnErrorClause(JsonExistsOnErrorBehavior), + /// The SQL/JSON `... ON EMPTY`/`... ON ERROR` clause for `JSON_VALUE`. + JsonValueBehaviorClause(JsonValueBehaviorClause), + /// The SQL/JSON wrapper behavior clause for `JSON_QUERY`. + JsonQueryWrapperClause(JsonQueryWrapperClause), + /// The SQL/JSON quote handling clause for `JSON_QUERY`. + JsonQueryQuotesClause(JsonQueryQuotesClause), + /// The SQL/JSON `... ON EMPTY`/`... ON ERROR` clause for `JSON_QUERY`. + JsonQueryBehaviorClause(JsonQueryBehaviorClause), + /// The SQL/JSON format clause for JSON query functions. + JsonFormatClause(JsonFormatClause), } impl fmt::Display for FunctionArgumentClause { @@ -8023,6 +8037,27 @@ impl fmt::Display for FunctionArgumentClause { FunctionArgumentClause::JsonReturningClause(returning_clause) => { write!(f, "{returning_clause}") } + FunctionArgumentClause::JsonPassingClause(passing_clause) => { + write!(f, "{passing_clause}") + } + FunctionArgumentClause::JsonExistsOnErrorClause(on_error_clause) => { + write!(f, "{on_error_clause}") + } + FunctionArgumentClause::JsonValueBehaviorClause(behavior_clause) => { + write!(f, "{behavior_clause}") + } + FunctionArgumentClause::JsonQueryWrapperClause(wrapper_clause) => { + write!(f, "{wrapper_clause}") + } + FunctionArgumentClause::JsonQueryQuotesClause(quotes_clause) => { + write!(f, "{quotes_clause}") + } + FunctionArgumentClause::JsonQueryBehaviorClause(behavior_clause) => { + write!(f, "{behavior_clause}") + } + FunctionArgumentClause::JsonFormatClause(format_clause) => { + write!(f, "{format_clause}") + } } } } @@ -10700,6 +10735,315 @@ impl Display for JsonReturningClause { } } +/// SQL/JSON PASSING clause. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonPassingClause { + /// Arguments passed in the `PASSING` clause. + pub args: Vec, +} + +impl Display for JsonPassingClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PASSING {}", display_comma_separated(&self.args)) + } +} + +/// A single SQL/JSON `PASSING` argument. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonPassingArg { + /// Value expression to pass to the JSON path. + pub expr: Expr, + /// Variable name used in the path expression. + pub name: Ident, +} + +impl Display for JsonPassingArg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} AS {}", self.expr, self.name) + } +} + +/// SQL/JSON `... ON ERROR` behavior for `JSON_EXISTS`. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonExistsOnErrorBehavior { + /// `ERROR ON ERROR` + Error, + /// `TRUE ON ERROR` + True, + /// `FALSE ON ERROR` + False, + /// `UNKNOWN ON ERROR` + Unknown, +} + +impl Display for JsonExistsOnErrorBehavior { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonExistsOnErrorBehavior::Error => write!(f, "ERROR ON ERROR"), + JsonExistsOnErrorBehavior::True => write!(f, "TRUE ON ERROR"), + JsonExistsOnErrorBehavior::False => write!(f, "FALSE ON ERROR"), + JsonExistsOnErrorBehavior::Unknown => write!(f, "UNKNOWN ON ERROR"), + } + } +} + +/// SQL/JSON behavior target for `JSON_VALUE` and `JSON_QUERY`. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonBehaviorTarget { + /// `... ON EMPTY` + Empty, + /// `... ON ERROR` + Error, +} + +impl Display for JsonBehaviorTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonBehaviorTarget::Empty => write!(f, "EMPTY"), + JsonBehaviorTarget::Error => write!(f, "ERROR"), + } + } +} + +/// SQL/JSON behavior variant for `JSON_VALUE`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonValueBehavior { + /// `ERROR` + Error, + /// `NULL` + Null, + /// `DEFAULT ` + Default(Expr), +} + +impl Display for JsonValueBehavior { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonValueBehavior::Error => write!(f, "ERROR"), + JsonValueBehavior::Null => write!(f, "NULL"), + JsonValueBehavior::Default(expr) => write!(f, "DEFAULT {expr}"), + } + } +} + +/// SQL/JSON behavior clause for `JSON_VALUE`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonValueBehaviorClause { + /// Behavior applied on target condition. + pub behavior: JsonValueBehavior, + /// Target condition: `ON EMPTY` or `ON ERROR`. + pub target: JsonBehaviorTarget, +} + +impl Display for JsonValueBehaviorClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ON {}", self.behavior, self.target) + } +} + +/// SQL/JSON wrapper behavior clause for `JSON_QUERY`. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonQueryWrapperClause { + /// `WITHOUT WRAPPER` + WithoutWrapper, + /// `WITHOUT ARRAY WRAPPER` + WithoutArrayWrapper, + /// `WITH WRAPPER` + WithWrapper, + /// `WITH ARRAY WRAPPER` + WithArrayWrapper, + /// `WITH CONDITIONAL WRAPPER` + WithConditionalWrapper, + /// `WITH CONDITIONAL ARRAY WRAPPER` + WithConditionalArrayWrapper, + /// `WITH UNCONDITIONAL WRAPPER` + WithUnconditionalWrapper, + /// `WITH UNCONDITIONAL ARRAY WRAPPER` + WithUnconditionalArrayWrapper, +} + +impl Display for JsonQueryWrapperClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonQueryWrapperClause::WithoutWrapper => write!(f, "WITHOUT WRAPPER"), + JsonQueryWrapperClause::WithoutArrayWrapper => write!(f, "WITHOUT ARRAY WRAPPER"), + JsonQueryWrapperClause::WithWrapper => write!(f, "WITH WRAPPER"), + JsonQueryWrapperClause::WithArrayWrapper => write!(f, "WITH ARRAY WRAPPER"), + JsonQueryWrapperClause::WithConditionalWrapper => { + write!(f, "WITH CONDITIONAL WRAPPER") + } + JsonQueryWrapperClause::WithConditionalArrayWrapper => { + write!(f, "WITH CONDITIONAL ARRAY WRAPPER") + } + JsonQueryWrapperClause::WithUnconditionalWrapper => { + write!(f, "WITH UNCONDITIONAL WRAPPER") + } + JsonQueryWrapperClause::WithUnconditionalArrayWrapper => { + write!(f, "WITH UNCONDITIONAL ARRAY WRAPPER") + } + } + } +} + +/// SQL/JSON quote handling mode for `JSON_QUERY`. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonQueryQuotesMode { + /// `KEEP` + Keep, + /// `OMIT` + Omit, +} + +impl Display for JsonQueryQuotesMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonQueryQuotesMode::Keep => write!(f, "KEEP"), + JsonQueryQuotesMode::Omit => write!(f, "OMIT"), + } + } +} + +/// SQL/JSON quote handling clause for `JSON_QUERY`. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonQueryQuotesClause { + /// Whether to keep or omit quotes. + pub mode: JsonQueryQuotesMode, + /// Whether `ON SCALAR STRING` was specified. + pub on_scalar_string: bool, +} + +impl Display for JsonQueryQuotesClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} QUOTES", self.mode)?; + if self.on_scalar_string { + write!(f, " ON SCALAR STRING")?; + } + Ok(()) + } +} + +/// SQL/JSON behavior variant for `JSON_QUERY`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonQueryBehavior { + /// `ERROR` + Error, + /// `NULL` + Null, + /// `EMPTY` + Empty, + /// `EMPTY ARRAY` + EmptyArray, + /// `EMPTY OBJECT` + EmptyObject, + /// `DEFAULT ` + Default(Expr), +} + +impl Display for JsonQueryBehavior { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonQueryBehavior::Error => write!(f, "ERROR"), + JsonQueryBehavior::Null => write!(f, "NULL"), + JsonQueryBehavior::Empty => write!(f, "EMPTY"), + JsonQueryBehavior::EmptyArray => write!(f, "EMPTY ARRAY"), + JsonQueryBehavior::EmptyObject => write!(f, "EMPTY OBJECT"), + JsonQueryBehavior::Default(expr) => write!(f, "DEFAULT {expr}"), + } + } +} + +/// SQL/JSON behavior clause for `JSON_QUERY`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonQueryBehaviorClause { + /// Behavior applied on target condition. + pub behavior: JsonQueryBehavior, + /// Target condition: `ON EMPTY` or `ON ERROR`. + pub target: JsonBehaviorTarget, +} + +impl Display for JsonQueryBehaviorClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ON {}", self.behavior, self.target) + } +} + +/// SQL/JSON `FORMAT` clause. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct JsonFormatClause { + /// Format type. + pub format: JsonFormatType, + /// Optional encoding. + pub encoding: Option, +} + +impl Display for JsonFormatClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "FORMAT {}", self.format)?; + if let Some(encoding) = self.encoding { + write!(f, " ENCODING {encoding}")?; + } + Ok(()) + } +} + +/// SQL/JSON format type. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonFormatType { + /// `JSON` + Json, +} + +impl Display for JsonFormatType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonFormatType::Json => write!(f, "JSON"), + } + } +} + +/// SQL/JSON format encoding. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum JsonFormatEncoding { + /// `UTF8` + Utf8, +} + +impl Display for JsonFormatEncoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonFormatEncoding::Utf8 => write!(f, "UTF8"), + } + } +} + /// rename object definition #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/ast/query.rs b/src/ast/query.rs index 159f02a6c..fcfda4f50 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1556,6 +1556,8 @@ pub enum TableFactor { /// The columns to be extracted from each element of the array or object. /// Each column must have a name and a type. columns: Vec, + /// Optional table-level `ON ERROR` behavior. + on_error: Option, /// The alias for the table. alias: Option, }, @@ -2309,13 +2311,18 @@ impl fmt::Display for TableFactor { json_expr, json_path, columns, + on_error, alias, } => { write!( f, - "JSON_TABLE({json_expr}, {json_path} COLUMNS({columns}))", + "JSON_TABLE({json_expr}, {json_path} COLUMNS({columns})", columns = display_comma_separated(columns) )?; + if let Some(on_error) = on_error { + write!(f, " {on_error} ON ERROR")?; + } + write!(f, ")")?; if let Some(alias) = alias { write!(f, " {alias}")?; } @@ -4044,6 +4051,26 @@ impl fmt::Display for JsonTableColumnErrorHandling { } } +/// Table-level `ON ERROR` handling in `JSON_TABLE`. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum JsonTableOnErrorHandling { + /// `ERROR ON ERROR` + Error, + /// `EMPTY ARRAY ON ERROR` + EmptyArray, +} + +impl fmt::Display for JsonTableOnErrorHandling { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + JsonTableOnErrorHandling::Error => write!(f, "ERROR"), + JsonTableOnErrorHandling::EmptyArray => write!(f, "EMPTY ARRAY"), + } + } +} + /// A single column definition in MSSQL's `OPENJSON WITH` clause. /// /// ```sql diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 0b95c3ed7..be774bd09 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1763,6 +1763,13 @@ impl Spanned for FunctionArgumentClause { FunctionArgumentClause::Separator(value) => value.span(), FunctionArgumentClause::JsonNullClause(_) => Span::empty(), FunctionArgumentClause::JsonReturningClause(_) => Span::empty(), + FunctionArgumentClause::JsonPassingClause(_) => Span::empty(), + FunctionArgumentClause::JsonExistsOnErrorClause(_) => Span::empty(), + FunctionArgumentClause::JsonValueBehaviorClause(_) => Span::empty(), + FunctionArgumentClause::JsonQueryWrapperClause(_) => Span::empty(), + FunctionArgumentClause::JsonQueryQuotesClause(_) => Span::empty(), + FunctionArgumentClause::JsonQueryBehaviorClause(_) => Span::empty(), + FunctionArgumentClause::JsonFormatClause(_) => Span::empty(), } } } diff --git a/src/keywords.rs b/src/keywords.rs index cc2b9e9dd..08e38f307 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -243,6 +243,7 @@ define_keywords!( COMPUTE, CONCURRENTLY, CONDITION, + CONDITIONAL, CONFLICT, CONNECT, CONNECTION, @@ -552,6 +553,7 @@ define_keywords!( JSONFILE, JSON_TABLE, JULIAN, + KEEP, KEY, KEYS, KEY_BLOCK_SIZE, @@ -815,6 +817,7 @@ define_keywords!( QUERIES, QUERY, QUOTE, + QUOTES, RAISE, RAISERROR, RANGE, @@ -902,6 +905,7 @@ define_keywords!( SAFE_CAST, SAMPLE, SAVEPOINT, + SCALAR, SCHEMA, SCHEMAS, SCOPE, @@ -1087,6 +1091,7 @@ define_keywords!( UNBOUNDED, UNCACHE, UNCOMMITTED, + UNCONDITIONAL, UNDEFINED, UNFREEZE, UNION, @@ -1112,6 +1117,7 @@ define_keywords!( USER_RESOURCES, USING, USMALLINT, + UTF8, UTINYINT, UUID, VACUUM, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index bea566bbe..2b118aa12 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -183,6 +183,13 @@ pub enum WildcardExpr { Wildcard, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum SqlJsonFunctionKind { + Exists, + Value, + Query, +} + impl From for ParserError { fn from(e: TokenizerError) -> Self { ParserError::TokenizerError(e.to_string()) @@ -2424,7 +2431,12 @@ impl<'a> Parser<'a> { }); } - let mut args = self.parse_function_argument_list()?; + let sql_json_function = if dialect_of!(self is PostgreSqlDialect | GenericDialect) { + Self::json_function_kind(Some(&name)) + } else { + None + }; + let mut args = self.parse_function_argument_list(sql_json_function)?; let mut parameters = FunctionArguments::None; // ClickHouse aggregations support parametric functions like `HISTOGRAM(0.5, 0.6)(x, y)` // which (0.5, 0.6) is a parameter to the function. @@ -2432,7 +2444,7 @@ impl<'a> Parser<'a> { && self.consume_token(&Token::LParen) { parameters = FunctionArguments::List(args); - args = self.parse_function_argument_list()?; + args = self.parse_function_argument_list(sql_json_function)?; } let within_group = if self.parse_keywords(&[Keyword::WITHIN, Keyword::GROUP]) { @@ -2511,7 +2523,7 @@ impl<'a> Parser<'a> { /// Parse time-related function `name` possibly followed by `(...)` arguments. pub fn parse_time_functions(&mut self, name: ObjectName) -> Result { let args = if self.consume_token(&Token::LParen) { - FunctionArguments::List(self.parse_function_argument_list()?) + FunctionArguments::List(self.parse_function_argument_list(None)?) } else { FunctionArguments::None }; @@ -15526,12 +15538,14 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let columns = self.parse_comma_separated(Parser::parse_json_table_column_def)?; self.expect_token(&Token::RParen)?; + let on_error = self.parse_json_table_on_error_handling()?; self.expect_token(&Token::RParen)?; let alias = self.maybe_parse_table_alias()?; Ok(TableFactor::JsonTable { json_expr, json_path, columns, + on_error, alias, }) } else if self.parse_keyword_with_tokens(Keyword::OPENJSON, &[Token::LParen]) { @@ -16315,6 +16329,24 @@ impl<'a> Parser<'a> { Ok(Some(res)) } + /// Parse PostgreSQL table-level `JSON_TABLE` error behavior: + /// + /// `{ ERROR | EMPTY ARRAY } ON ERROR` + fn parse_json_table_on_error_handling( + &mut self, + ) -> Result, ParserError> { + let res = if self.parse_keyword(Keyword::ERROR) { + JsonTableOnErrorHandling::Error + } else if self.parse_keyword(Keyword::EMPTY) { + self.expect_keyword_is(Keyword::ARRAY)?; + JsonTableOnErrorHandling::EmptyArray + } else { + return Ok(None); + }; + self.expect_keywords(&[Keyword::ON, Keyword::ERROR])?; + Ok(Some(res)) + } + /// Parse a derived table factor (a parenthesized subquery), handling optional LATERAL. pub fn parse_derived_table_factor( &mut self, @@ -17667,7 +17699,10 @@ impl<'a> Parser<'a> { /// FIRST_VALUE(x ORDER BY 1,2,3); /// FIRST_VALUE(x IGNORE NULL); /// ``` - fn parse_function_argument_list(&mut self) -> Result { + fn parse_function_argument_list( + &mut self, + sql_json_function: Option, + ) -> Result { let mut clauses = vec![]; // Handle clauses that may exist with an empty argument list @@ -17735,14 +17770,18 @@ impl<'a> Parser<'a> { clauses.push(FunctionArgumentClause::OnOverflow(on_overflow)); } - if let Some(null_clause) = self.parse_json_null_clause() { - clauses.push(FunctionArgumentClause::JsonNullClause(null_clause)); - } + if let Some(sql_json_function) = sql_json_function { + self.parse_sql_json_function_clauses(sql_json_function, &mut clauses)?; + } else { + if let Some(null_clause) = self.parse_json_null_clause() { + clauses.push(FunctionArgumentClause::JsonNullClause(null_clause)); + } - if let Some(json_returning_clause) = self.maybe_parse_json_returning_clause()? { - clauses.push(FunctionArgumentClause::JsonReturningClause( - json_returning_clause, - )); + if let Some(json_returning_clause) = self.maybe_parse_json_returning_clause()? { + clauses.push(FunctionArgumentClause::JsonReturningClause( + json_returning_clause, + )); + } } self.expect_token(&Token::RParen)?; @@ -17774,6 +17813,376 @@ impl<'a> Parser<'a> { } } + /// Returns SQL/JSON function kind for function-aware clause parsing. + /// + /// We intentionally inspect only the last function name segment so + /// schema-qualified invocations like `pg_catalog.json_value(...)` are + /// treated the same as unqualified calls. + fn json_function_kind(function_name: Option<&ObjectName>) -> Option { + let ObjectName(parts) = function_name?; + let last = parts.last()?; + let ObjectNamePart::Identifier(ident) = last else { + return None; + }; + if ident.value.eq_ignore_ascii_case("JSON_EXISTS") { + Some(SqlJsonFunctionKind::Exists) + } else if ident.value.eq_ignore_ascii_case("JSON_VALUE") { + Some(SqlJsonFunctionKind::Value) + } else if ident.value.eq_ignore_ascii_case("JSON_QUERY") { + Some(SqlJsonFunctionKind::Query) + } else { + None + } + } + + /// Parse PostgreSQL SQL/JSON query-function clauses that appear after the + /// positional arguments and before the closing `)`. + /// + /// This helper is function-aware (`JSON_EXISTS`, `JSON_VALUE`, `JSON_QUERY`) + /// and enforces per-family single-occurrence constraints for conflicting + /// clause groups (for example, duplicate `... ON ERROR` behaviors). + /// + /// Parsing is done in a loop so clauses can appear in valid mixed + /// combinations (e.g. `PASSING`, `RETURNING`, `FORMAT JSON`, wrappers, + /// quotes, and `ON EMPTY` / `ON ERROR` behaviors). + fn parse_sql_json_function_clauses( + &mut self, + sql_json_function: SqlJsonFunctionKind, + clauses: &mut Vec, + ) -> Result<(), ParserError> { + let mut seen_passing = false; + let mut seen_returning = clauses + .iter() + .any(|clause| matches!(clause, FunctionArgumentClause::JsonReturningClause(_))); + let mut seen_format = false; + let mut seen_json_exists_on_error = false; + let mut seen_json_value_on_empty = false; + let mut seen_json_value_on_error = false; + let mut seen_json_query_wrapper = false; + let mut seen_json_query_quotes = false; + let mut seen_json_query_on_empty = false; + let mut seen_json_query_on_error = false; + + loop { + if !seen_passing { + if let Some(passing_clause) = self.maybe_parse_json_passing_clause()? { + clauses.push(FunctionArgumentClause::JsonPassingClause(passing_clause)); + seen_passing = true; + continue; + } + } + + if !seen_returning { + if let Some(json_returning_clause) = self.maybe_parse_json_returning_clause()? { + clauses.push(FunctionArgumentClause::JsonReturningClause( + json_returning_clause, + )); + seen_returning = true; + continue; + } + } + + if !seen_format { + if let Some(format_clause) = self.maybe_parse_json_format_clause()? { + clauses.push(FunctionArgumentClause::JsonFormatClause(format_clause)); + seen_format = true; + continue; + } + } + + match sql_json_function { + SqlJsonFunctionKind::Exists => { + if !seen_json_exists_on_error { + if let Some(on_error_clause) = + self.maybe_parse_json_exists_on_error_clause()? + { + clauses.push(FunctionArgumentClause::JsonExistsOnErrorClause( + on_error_clause, + )); + seen_json_exists_on_error = true; + continue; + } + } + } + SqlJsonFunctionKind::Value => { + if let Some(behavior_clause) = self.maybe_parse_json_value_behavior_clause()? { + match behavior_clause.target { + JsonBehaviorTarget::Empty if seen_json_value_on_empty => { + return Err(ParserError::ParserError( + "Duplicate JSON_VALUE ON EMPTY clause".to_string(), + )) + } + JsonBehaviorTarget::Error if seen_json_value_on_error => { + return Err(ParserError::ParserError( + "Duplicate JSON_VALUE ON ERROR clause".to_string(), + )) + } + JsonBehaviorTarget::Empty => seen_json_value_on_empty = true, + JsonBehaviorTarget::Error => seen_json_value_on_error = true, + } + clauses.push(FunctionArgumentClause::JsonValueBehaviorClause( + behavior_clause, + )); + continue; + } + } + SqlJsonFunctionKind::Query => { + if !seen_json_query_wrapper { + if let Some(wrapper_clause) = + self.maybe_parse_json_query_wrapper_clause()? + { + clauses.push(FunctionArgumentClause::JsonQueryWrapperClause( + wrapper_clause, + )); + seen_json_query_wrapper = true; + continue; + } + } + if !seen_json_query_quotes { + if let Some(quotes_clause) = self.maybe_parse_json_query_quotes_clause()? { + clauses + .push(FunctionArgumentClause::JsonQueryQuotesClause(quotes_clause)); + seen_json_query_quotes = true; + continue; + } + } + if let Some(behavior_clause) = self.maybe_parse_json_query_behavior_clause()? { + match behavior_clause.target { + JsonBehaviorTarget::Empty if seen_json_query_on_empty => { + return Err(ParserError::ParserError( + "Duplicate JSON_QUERY ON EMPTY clause".to_string(), + )) + } + JsonBehaviorTarget::Error if seen_json_query_on_error => { + return Err(ParserError::ParserError( + "Duplicate JSON_QUERY ON ERROR clause".to_string(), + )) + } + JsonBehaviorTarget::Empty => seen_json_query_on_empty = true, + JsonBehaviorTarget::Error => seen_json_query_on_error = true, + } + clauses.push(FunctionArgumentClause::JsonQueryBehaviorClause( + behavior_clause, + )); + continue; + } + } + } + + break; + } + + Ok(()) + } + + /// Parse a SQL/JSON `PASSING` clause: + /// + /// `PASSING AS [, ...]` + fn maybe_parse_json_passing_clause( + &mut self, + ) -> Result, ParserError> { + if !self.parse_keyword(Keyword::PASSING) { + return Ok(None); + } + + let mut args = vec![]; + loop { + let expr = self.parse_expr()?; + self.expect_keyword_is(Keyword::AS)?; + let name = self.parse_identifier()?; + args.push(JsonPassingArg { expr, name }); + if !self.consume_token(&Token::Comma) { + break; + } + } + + Ok(Some(JsonPassingClause { args })) + } + + /// Parse SQL/JSON `FORMAT JSON [ENCODING UTF8]`. + fn maybe_parse_json_format_clause(&mut self) -> Result, ParserError> { + if !self.parse_keyword(Keyword::FORMAT) { + return Ok(None); + } + + self.expect_keyword_is(Keyword::JSON)?; + let encoding = if self.parse_keyword(Keyword::ENCODING) { + if self.parse_keyword(Keyword::UTF8) { + Some(JsonFormatEncoding::Utf8) + } else { + return self.expected_ref("UTF8", self.peek_token_ref()); + } + } else { + None + }; + + Ok(Some(JsonFormatClause { + format: JsonFormatType::Json, + encoding, + })) + } + + /// Parse SQL/JSON `JSON_EXISTS` behavior: + /// + /// `{ ERROR | TRUE | FALSE | UNKNOWN } ON ERROR` + fn maybe_parse_json_exists_on_error_clause( + &mut self, + ) -> Result, ParserError> { + let on_error = if self.parse_keyword(Keyword::ERROR) { + JsonExistsOnErrorBehavior::Error + } else if self.parse_keyword(Keyword::TRUE) { + JsonExistsOnErrorBehavior::True + } else if self.parse_keyword(Keyword::FALSE) { + JsonExistsOnErrorBehavior::False + } else if self.parse_keyword(Keyword::UNKNOWN) { + JsonExistsOnErrorBehavior::Unknown + } else { + return Ok(None); + }; + + self.expect_keywords(&[Keyword::ON, Keyword::ERROR])?; + Ok(Some(on_error)) + } + + /// Parse SQL/JSON `JSON_VALUE` behavior: + /// + /// `{ ERROR | NULL | DEFAULT } ON { EMPTY | ERROR }` + fn maybe_parse_json_value_behavior_clause( + &mut self, + ) -> Result, ParserError> { + let behavior = if self.parse_keyword(Keyword::ERROR) { + JsonValueBehavior::Error + } else if self.parse_keyword(Keyword::NULL) { + JsonValueBehavior::Null + } else if self.parse_keyword(Keyword::DEFAULT) { + JsonValueBehavior::Default(self.parse_expr()?) + } else { + return Ok(None); + }; + + self.expect_keyword_is(Keyword::ON)?; + let target = if self.parse_keyword(Keyword::EMPTY) { + JsonBehaviorTarget::Empty + } else { + self.expect_keyword_is(Keyword::ERROR)?; + JsonBehaviorTarget::Error + }; + + Ok(Some(JsonValueBehaviorClause { behavior, target })) + } + + /// Parse SQL/JSON `JSON_QUERY` wrapper behavior: + /// + /// `WITHOUT [ARRAY] WRAPPER` + /// `WITH [CONDITIONAL|UNCONDITIONAL] [ARRAY] WRAPPER` + fn maybe_parse_json_query_wrapper_clause( + &mut self, + ) -> Result, ParserError> { + if self.parse_keyword(Keyword::WITHOUT) { + let has_array = self.parse_keyword(Keyword::ARRAY); + self.expect_keyword_is(Keyword::WRAPPER)?; + return Ok(Some(if has_array { + JsonQueryWrapperClause::WithoutArrayWrapper + } else { + JsonQueryWrapperClause::WithoutWrapper + })); + } + + if self.parse_keyword(Keyword::WITH) { + let wrapper_prefix = if self.parse_keyword(Keyword::CONDITIONAL) { + Some(Keyword::CONDITIONAL) + } else if self.parse_keyword(Keyword::UNCONDITIONAL) { + Some(Keyword::UNCONDITIONAL) + } else { + None + }; + let has_array = self.parse_keyword(Keyword::ARRAY); + self.expect_keyword_is(Keyword::WRAPPER)?; + return Ok(Some(match (wrapper_prefix, has_array) { + (Some(Keyword::CONDITIONAL), true) => { + JsonQueryWrapperClause::WithConditionalArrayWrapper + } + (Some(Keyword::CONDITIONAL), false) => { + JsonQueryWrapperClause::WithConditionalWrapper + } + (Some(Keyword::UNCONDITIONAL), true) => { + JsonQueryWrapperClause::WithUnconditionalArrayWrapper + } + (Some(Keyword::UNCONDITIONAL), false) => { + JsonQueryWrapperClause::WithUnconditionalWrapper + } + (None, true) => JsonQueryWrapperClause::WithArrayWrapper, + (None, false) => JsonQueryWrapperClause::WithWrapper, + (Some(unexpected_keyword), _) => { + return Err(ParserError::ParserError(format!( + "Internal parser error: unexpected keyword `{unexpected_keyword}` in JSON_QUERY wrapper clause" + ))) + } + })); + } + + Ok(None) + } + + /// Parse SQL/JSON `JSON_QUERY` quote behavior: + /// + /// `{ KEEP | OMIT } QUOTES [ON SCALAR STRING]` + fn maybe_parse_json_query_quotes_clause( + &mut self, + ) -> Result, ParserError> { + let mode = if self.parse_keyword(Keyword::KEEP) { + JsonQueryQuotesMode::Keep + } else if self.parse_keyword(Keyword::OMIT) { + JsonQueryQuotesMode::Omit + } else { + return Ok(None); + }; + + self.expect_keyword_is(Keyword::QUOTES)?; + let on_scalar_string = + self.parse_keywords(&[Keyword::ON, Keyword::SCALAR, Keyword::STRING]); + + Ok(Some(JsonQueryQuotesClause { + mode, + on_scalar_string, + })) + } + + /// Parse SQL/JSON `JSON_QUERY` behavior: + /// + /// `{ ERROR | NULL | EMPTY [ARRAY|OBJECT] | DEFAULT } ON { EMPTY | ERROR }` + fn maybe_parse_json_query_behavior_clause( + &mut self, + ) -> Result, ParserError> { + let behavior = if self.parse_keyword(Keyword::ERROR) { + JsonQueryBehavior::Error + } else if self.parse_keyword(Keyword::NULL) { + JsonQueryBehavior::Null + } else if self.parse_keyword(Keyword::EMPTY) { + if self.parse_keyword(Keyword::ARRAY) { + JsonQueryBehavior::EmptyArray + } else if self.parse_keyword(Keyword::OBJECT) { + JsonQueryBehavior::EmptyObject + } else { + JsonQueryBehavior::Empty + } + } else if self.parse_keyword(Keyword::DEFAULT) { + JsonQueryBehavior::Default(self.parse_expr()?) + } else { + return Ok(None); + }; + + self.expect_keyword_is(Keyword::ON)?; + let target = if self.parse_keyword(Keyword::EMPTY) { + JsonBehaviorTarget::Empty + } else { + self.expect_keyword_is(Keyword::ERROR)?; + JsonBehaviorTarget::Error + }; + + Ok(Some(JsonQueryBehaviorClause { behavior, target })) + } + fn parse_duplicate_treatment(&mut self) -> Result, ParserError> { let loc = self.peek_token_ref().span.start; match ( diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 30405623d..0c7bd1cb9 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -3772,6 +3772,7 @@ fn parse_json_table() { on_error: Some(JsonTableColumnErrorHandling::Null), }), ], + on_error: None, alias: table_alias(true, "t"), } ); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 7c19f51e5..680468789 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3553,6 +3553,163 @@ fn parse_json_table_is_not_reserved() { } } +fn parse_pg_select(sql: &str) -> Select { + let stmts = pg().parse_sql_statements(sql).unwrap(); + let stmt = stmts.into_iter().next().unwrap(); + let Statement::Query(query) = stmt else { + panic!("Expected query statement"); + }; + let SetExpr::Select(select) = *query.body else { + panic!("Expected select query body"); + }; + *select +} + +fn parse_pg_projection_expr(sql: &str) -> Expr { + let select = parse_pg_select(sql); + expr_from_projection(&select.projection[0]).clone() +} + +#[test] +fn parse_postgres_sql_json_query_functions() { + let expr = parse_pg_projection_expr("SELECT JSON_EXISTS(jsonb '1', '$.a' FALSE ON ERROR)"); + let Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { clauses, .. }), + .. + }) = expr + else { + panic!("Expected JSON_EXISTS to parse as a function call"); + }; + assert_eq!( + clauses, + vec![FunctionArgumentClause::JsonExistsOnErrorClause( + JsonExistsOnErrorBehavior::False + )] + ); + + let expr = parse_pg_projection_expr( + "SELECT JSON_VALUE(jsonb '{\"a\": 1}', '$.a ? (@ > $x)' PASSING 0 AS x RETURNING int DEFAULT 1 ON EMPTY ERROR ON ERROR)", + ); + let Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { clauses, .. }), + .. + }) = expr + else { + panic!("Expected JSON_VALUE to parse as a function call"); + }; + assert!(matches!( + &clauses[0], + FunctionArgumentClause::JsonPassingClause(JsonPassingClause { args }) + if args.len() == 1 && args[0].name.value == "x" + )); + assert!(matches!( + &clauses[1], + FunctionArgumentClause::JsonReturningClause(JsonReturningClause { + data_type: DataType::Int(None) + }) + )); + assert!(matches!( + &clauses[2], + FunctionArgumentClause::JsonValueBehaviorClause(JsonValueBehaviorClause { + behavior: JsonValueBehavior::Default(_), + target: JsonBehaviorTarget::Empty, + }) + )); + assert!(matches!( + &clauses[3], + FunctionArgumentClause::JsonValueBehaviorClause(JsonValueBehaviorClause { + behavior: JsonValueBehavior::Error, + target: JsonBehaviorTarget::Error, + }) + )); + + let expr = parse_pg_projection_expr( + "SELECT JSON_QUERY(jsonb '[\"1\"]', '$[*]' RETURNING bytea FORMAT JSON WITH UNCONDITIONAL ARRAY WRAPPER KEEP QUOTES ON SCALAR STRING EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR)", + ); + let Expr::Function(Function { + args: FunctionArguments::List(FunctionArgumentList { clauses, .. }), + .. + }) = expr + else { + panic!("Expected JSON_QUERY to parse as a function call"); + }; + assert!(matches!( + &clauses[0], + FunctionArgumentClause::JsonReturningClause(JsonReturningClause { + data_type: DataType::Bytea + }) + )); + assert!(matches!( + &clauses[1], + FunctionArgumentClause::JsonFormatClause(JsonFormatClause { + format: JsonFormatType::Json, + encoding: None, + }) + )); + assert!(matches!( + &clauses[2], + FunctionArgumentClause::JsonQueryWrapperClause( + JsonQueryWrapperClause::WithUnconditionalArrayWrapper + ) + )); + assert!(matches!( + &clauses[3], + FunctionArgumentClause::JsonQueryQuotesClause(JsonQueryQuotesClause { + mode: JsonQueryQuotesMode::Keep, + on_scalar_string: true, + }) + )); + assert!(matches!( + &clauses[4], + FunctionArgumentClause::JsonQueryBehaviorClause(JsonQueryBehaviorClause { + behavior: JsonQueryBehavior::EmptyArray, + target: JsonBehaviorTarget::Empty, + }) + )); + assert!(matches!( + &clauses[5], + FunctionArgumentClause::JsonQueryBehaviorClause(JsonQueryBehaviorClause { + behavior: JsonQueryBehavior::EmptyObject, + target: JsonBehaviorTarget::Error, + }) + )); +} + +#[test] +fn parse_postgres_json_table_on_error() { + let select = parse_pg_select( + "SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON ERROR) ERROR ON ERROR) jt", + ); + let relation = &select.from[0].relation; + assert!(matches!( + relation, + TableFactor::JsonTable { + on_error: Some(JsonTableOnErrorHandling::Error), + .. + } + )); + + let select = parse_pg_select( + "SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a') EMPTY ARRAY ON ERROR) jt", + ); + let relation = &select.from[0].relation; + assert!(matches!( + relation, + TableFactor::JsonTable { + on_error: Some(JsonTableOnErrorHandling::EmptyArray), + .. + } + )); +} + +#[test] +fn parse_postgres_sql_json_duplicate_behavior_clauses() { + let sql = "SELECT JSON_VALUE(jsonb '1', '$.a' NULL ON EMPTY ERROR ON EMPTY)"; + assert!(pg().parse_sql_statements(sql).is_err()); + let sql = "SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON ERROR EMPTY ARRAY ON ERROR)"; + assert!(pg().parse_sql_statements(sql).is_err()); +} + #[test] fn test_composite_value() { let sql = "SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9";