Skip to content

add support for FOR ORDINALITY and NESTED in JSON_TABLE #1493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
71 changes: 68 additions & 3 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2276,19 +2276,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.
Expand All @@ -2303,7 +2368,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,
Expand Down
20 changes: 18 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10401,7 +10401,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)?;
Expand All @@ -10416,14 +10432,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(
Expand Down
10 changes: 8 additions & 2 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2758,6 +2758,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(
Expand All @@ -2769,14 +2775,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"),
Expand Down
Loading