add support for FOR ORDINALITY and NESTED in JSON_TABLE (#1493)

This commit is contained in:
Ophir LOJKINE 2024-11-06 22:04:13 +01:00 committed by GitHub
parent a5b0092506
commit fc0e13b80e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 14 deletions

View file

@ -54,13 +54,14 @@ pub use self::query::{
ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml,
FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolate,
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,
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,
};
pub use self::trigger::{

View file

@ -2286,19 +2286,84 @@ impl fmt::Display for ForJson {
}
/// A single column definition in MySQL's `JSON_TABLE` table valued function.
///
/// See
/// - [MySQL's JSON_TABLE documentation](https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table)
/// - [Oracle's JSON_TABLE documentation](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/JSON_TABLE.html)
/// - [MariaDB's JSON_TABLE documentation](https://mariadb.com/kb/en/json_table/)
///
/// ```sql
/// SELECT *
/// FROM JSON_TABLE(
/// '["a", "b"]',
/// '$[*]' COLUMNS (
/// value VARCHAR(20) PATH '$'
/// name FOR ORDINALITY,
/// value VARCHAR(20) PATH '$',
/// NESTED PATH '$[*]' COLUMNS (
/// value VARCHAR(20) PATH '$'
/// )
/// )
/// ) AS jt;
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct JsonTableColumn {
pub enum JsonTableColumn {
/// A named column with a JSON path
Named(JsonTableNamedColumn),
/// The FOR ORDINALITY column, which is a special column that returns the index of the current row in a JSON array.
ForOrdinality(Ident),
/// A set of nested columns, which extracts data from a nested JSON array.
Nested(JsonTableNestedColumn),
}
impl fmt::Display for JsonTableColumn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JsonTableColumn::Named(json_table_named_column) => {
write!(f, "{json_table_named_column}")
}
JsonTableColumn::ForOrdinality(ident) => write!(f, "{} FOR ORDINALITY", ident),
JsonTableColumn::Nested(json_table_nested_column) => {
write!(f, "{json_table_nested_column}")
}
}
}
}
/// A nested column in a JSON_TABLE column list
///
/// See <https://mariadb.com/kb/en/json_table/#nested-paths>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct JsonTableNestedColumn {
pub path: Value,
pub columns: Vec<JsonTableColumn>,
}
impl fmt::Display for JsonTableNestedColumn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"NESTED PATH {} COLUMNS ({})",
self.path,
display_comma_separated(&self.columns)
)
}
}
/// A single column definition in MySQL's `JSON_TABLE` table valued function.
///
/// See <https://mariadb.com/kb/en/json_table/#path-columns>
///
/// ```sql
/// value VARCHAR(20) PATH '$'
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct JsonTableNamedColumn {
/// The name of the column to be extracted.
pub name: Ident,
/// The type of the column to be extracted.
@ -2313,7 +2378,7 @@ pub struct JsonTableColumn {
pub on_error: Option<JsonTableColumnErrorHandling>,
}
impl fmt::Display for JsonTableColumn {
impl fmt::Display for JsonTableNamedColumn {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,

View file

@ -10466,7 +10466,23 @@ impl<'a> Parser<'a> {
/// Parses MySQL's JSON_TABLE column definition.
/// For example: `id INT EXISTS PATH '$' DEFAULT '0' ON EMPTY ERROR ON ERROR`
pub fn parse_json_table_column_def(&mut self) -> Result<JsonTableColumn, ParserError> {
if self.parse_keyword(Keyword::NESTED) {
let _has_path_keyword = self.parse_keyword(Keyword::PATH);
let path = self.parse_value()?;
self.expect_keyword(Keyword::COLUMNS)?;
let columns = self.parse_parenthesized(|p| {
p.parse_comma_separated(Self::parse_json_table_column_def)
})?;
return Ok(JsonTableColumn::Nested(JsonTableNestedColumn {
path,
columns,
}));
}
let name = self.parse_identifier(false)?;
if self.parse_keyword(Keyword::FOR) {
self.expect_keyword(Keyword::ORDINALITY)?;
return Ok(JsonTableColumn::ForOrdinality(name));
}
let r#type = self.parse_data_type()?;
let exists = self.parse_keyword(Keyword::EXISTS);
self.expect_keyword(Keyword::PATH)?;
@ -10481,14 +10497,14 @@ impl<'a> Parser<'a> {
on_error = Some(error_handling);
}
}
Ok(JsonTableColumn {
Ok(JsonTableColumn::Named(JsonTableNamedColumn {
name,
r#type,
path,
exists,
on_empty,
on_error,
})
}))
}
fn parse_json_table_column_error_handling(

View file

@ -2773,6 +2773,12 @@ fn parse_json_table() {
r#"SELECT * FROM JSON_TABLE('[1,2]', '$[*]' COLUMNS(x INT PATH '$' ERROR ON EMPTY)) AS t"#,
);
mysql().verified_only_select(r#"SELECT * FROM JSON_TABLE('[1,2]', '$[*]' COLUMNS(x INT PATH '$' ERROR ON EMPTY DEFAULT '0' ON ERROR)) AS t"#);
mysql().verified_only_select(
r#"SELECT jt.* FROM JSON_TABLE('["Alice", "Bob", "Charlie"]', '$[*]' COLUMNS(row_num FOR ORDINALITY, name VARCHAR(50) PATH '$')) AS jt"#,
);
mysql().verified_only_select(
r#"SELECT * FROM JSON_TABLE('[ {"a": 1, "b": [11,111]}, {"a": 2, "b": [22,222]}, {"a":3}]', '$[*]' COLUMNS(a INT PATH '$.a', NESTED PATH '$.b[*]' COLUMNS (b INT PATH '$'))) AS jt"#,
);
assert_eq!(
mysql()
.verified_only_select(
@ -2784,14 +2790,14 @@ fn parse_json_table() {
json_expr: Expr::Value(Value::SingleQuotedString("[1,2]".to_string())),
json_path: Value::SingleQuotedString("$[*]".to_string()),
columns: vec![
JsonTableColumn {
JsonTableColumn::Named(JsonTableNamedColumn {
name: Ident::new("x"),
r#type: DataType::Int(None),
path: Value::SingleQuotedString("$".to_string()),
exists: false,
on_empty: Some(JsonTableColumnErrorHandling::Default(Value::SingleQuotedString("0".to_string()))),
on_error: Some(JsonTableColumnErrorHandling::Null),
},
}),
],
alias: Some(TableAlias {
name: Ident::new("t"),