Skip to content

Commit b710612

Browse files

File tree

5 files changed

+238
-5
lines changed

5 files changed

+238
-5
lines changed

src/ast/mod.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ pub use self::ddl::{
3838
pub use self::operator::{BinaryOperator, UnaryOperator};
3939
pub use self::query::{
4040
Cte, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml,
41-
GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator, LateralView, LockClause,
42-
LockType, NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query,
43-
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
44-
SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor, TableVersion,
45-
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
41+
GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator, JsonTableColumn,
42+
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, NamedWindowDefinition,
43+
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement,
44+
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table,
45+
TableAlias, TableFactor, TableVersion, TableWithJoins, Top, Values, WildcardAdditionalOptions,
46+
With,
4647
};
4748
pub use self::value::{
4849
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,

src/ast/query.rs

+114
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,33 @@ pub enum TableFactor {
726726
with_offset: bool,
727727
with_offset_alias: Option<Ident>,
728728
},
729+
/// The `JSON_TABLE` table-valued function.
730+
/// Part of the SQL standard, but implemented only by MySQL, Oracle, and DB2.
731+
///
732+
/// <https://modern-sql.com/blog/2017-06/whats-new-in-sql-2016#json_table>
733+
/// <https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html#function_json-table>
734+
///
735+
/// ```sql
736+
/// SELECT * FROM JSON_TABLE(
737+
/// '[{"a": 1, "b": 2}, {"a": 3, "b": 4}]',
738+
/// '$[*]' COLUMNS(
739+
/// a INT PATH '$.a' DEFAULT '0' ON EMPTY,
740+
/// b INT PATH '$.b' NULL ON ERROR
741+
/// )
742+
/// ) AS jt;
743+
/// ````
744+
JsonTable {
745+
/// The JSON expression to be evaluated. It must evaluate to a json string
746+
json_expr: Expr,
747+
/// The path to the array or object to be iterated over.
748+
/// It must evaluate to a json array or object.
749+
json_path: Value,
750+
/// The columns to be extracted from each element of the array or object.
751+
/// Each column must have a name and a type.
752+
columns: Vec<JsonTableColumn>,
753+
/// The alias for the table.
754+
alias: Option<TableAlias>,
755+
},
729756
/// Represents a parenthesized table factor. The SQL spec only allows a
730757
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
731758
/// possibly several times.
@@ -848,6 +875,22 @@ impl fmt::Display for TableFactor {
848875
}
849876
Ok(())
850877
}
878+
TableFactor::JsonTable {
879+
json_expr,
880+
json_path,
881+
columns,
882+
alias,
883+
} => {
884+
write!(
885+
f,
886+
"JSON_TABLE({json_expr}, {json_path} COLUMNS({columns}))",
887+
columns = display_comma_separated(columns)
888+
)?;
889+
if let Some(alias) = alias {
890+
write!(f, " AS {alias}")?;
891+
}
892+
Ok(())
893+
}
851894
TableFactor::NestedJoin {
852895
table_with_joins,
853896
alias,
@@ -1443,3 +1486,74 @@ impl fmt::Display for ForJson {
14431486
}
14441487
}
14451488
}
1489+
1490+
/// A single column definition in MySQL's `JSON_TABLE` table valued function.
1491+
/// ```sql
1492+
/// SELECT *
1493+
/// FROM JSON_TABLE(
1494+
/// '["a", "b"]',
1495+
/// '$[*]' COLUMNS (
1496+
/// value VARCHAR(20) PATH '$'
1497+
/// )
1498+
/// ) AS jt;
1499+
/// ```
1500+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1501+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1502+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1503+
pub struct JsonTableColumn {
1504+
/// The name of the column to be extracted.
1505+
pub name: Ident,
1506+
/// The type of the column to be extracted.
1507+
pub r#type: DataType,
1508+
/// The path to the column to be extracted. Must be a literal string.
1509+
pub path: Value,
1510+
/// true if the column is a boolean set to true if the given path exists
1511+
pub exists: bool,
1512+
/// The empty handling clause of the column
1513+
pub on_empty: Option<JsonTableColumnErrorHandling>,
1514+
/// The error handling clause of the column
1515+
pub on_error: Option<JsonTableColumnErrorHandling>,
1516+
}
1517+
1518+
impl fmt::Display for JsonTableColumn {
1519+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1520+
write!(
1521+
f,
1522+
"{} {}{} PATH {}",
1523+
self.name,
1524+
self.r#type,
1525+
if self.exists { " EXISTS" } else { "" },
1526+
self.path
1527+
)?;
1528+
if let Some(on_empty) = &self.on_empty {
1529+
write!(f, " {} ON EMPTY", on_empty)?;
1530+
}
1531+
if let Some(on_error) = &self.on_error {
1532+
write!(f, " {} ON ERROR", on_error)?;
1533+
}
1534+
Ok(())
1535+
}
1536+
}
1537+
1538+
/// Stores the error handling clause of a `JSON_TABLE` table valued function:
1539+
/// {NULL | DEFAULT json_string | ERROR} ON {ERROR | EMPTY }
1540+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1541+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1542+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1543+
pub enum JsonTableColumnErrorHandling {
1544+
Null,
1545+
Default(Value),
1546+
Error,
1547+
}
1548+
1549+
impl fmt::Display for JsonTableColumnErrorHandling {
1550+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1551+
match self {
1552+
JsonTableColumnErrorHandling::Null => write!(f, "NULL"),
1553+
JsonTableColumnErrorHandling::Default(json_string) => {
1554+
write!(f, "DEFAULT {}", json_string)
1555+
}
1556+
JsonTableColumnErrorHandling::Error => write!(f, "ERROR"),
1557+
}
1558+
}
1559+
}

src/keywords.rs

+2
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ define_keywords!(
237237
ELEMENT,
238238
ELEMENTS,
239239
ELSE,
240+
EMPTY,
240241
ENCODING,
241242
ENCRYPTION,
242243
END,
@@ -353,6 +354,7 @@ define_keywords!(
353354
JOIN,
354355
JSON,
355356
JSONFILE,
357+
JSON_TABLE,
356358
JULIAN,
357359
KEY,
358360
KILL,

src/parser/mod.rs

+62
Original file line numberDiff line numberDiff line change
@@ -6886,6 +6886,7 @@ impl<'a> Parser<'a> {
68866886
| TableFactor::Table { alias, .. }
68876887
| TableFactor::Function { alias, .. }
68886888
| TableFactor::UNNEST { alias, .. }
6889+
| TableFactor::JsonTable { alias, .. }
68896890
| TableFactor::TableFunction { alias, .. }
68906891
| TableFactor::Pivot { alias, .. }
68916892
| TableFactor::Unpivot { alias, .. }
@@ -6944,6 +6945,23 @@ impl<'a> Parser<'a> {
69446945
with_offset,
69456946
with_offset_alias,
69466947
})
6948+
} else if self.parse_keyword(Keyword::JSON_TABLE) {
6949+
self.expect_token(&Token::LParen)?;
6950+
let json_expr = self.parse_expr()?;
6951+
self.expect_token(&Token::Comma)?;
6952+
let json_path = self.parse_value()?;
6953+
self.expect_keyword(Keyword::COLUMNS)?;
6954+
self.expect_token(&Token::LParen)?;
6955+
let columns = self.parse_comma_separated(Parser::parse_json_table_column_def)?;
6956+
self.expect_token(&Token::RParen)?;
6957+
self.expect_token(&Token::RParen)?;
6958+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
6959+
Ok(TableFactor::JsonTable {
6960+
json_expr,
6961+
json_path,
6962+
columns,
6963+
alias,
6964+
})
69476965
} else {
69486966
let name = self.parse_object_name()?;
69496967

@@ -7014,6 +7032,50 @@ impl<'a> Parser<'a> {
70147032
}
70157033
}
70167034

7035+
/// Parses MySQL's JSON_TABLE column definition.
7036+
/// For example: `id INT EXISTS PATH '$' DEFAULT '0' ON EMPTY ERROR ON ERROR`
7037+
pub fn parse_json_table_column_def(&mut self) -> Result<JsonTableColumn, ParserError> {
7038+
let name = self.parse_identifier()?;
7039+
let r#type = self.parse_data_type()?;
7040+
let exists = self.parse_keyword(Keyword::EXISTS);
7041+
self.expect_keyword(Keyword::PATH)?;
7042+
let path = self.parse_value()?;
7043+
let mut on_empty = None;
7044+
let mut on_error = None;
7045+
while let Some(error_handling) = self.parse_json_table_column_error_handling()? {
7046+
if self.parse_keyword(Keyword::EMPTY) {
7047+
on_empty = Some(error_handling);
7048+
} else {
7049+
self.expect_keyword(Keyword::ERROR)?;
7050+
on_error = Some(error_handling);
7051+
}
7052+
}
7053+
Ok(JsonTableColumn {
7054+
name,
7055+
r#type,
7056+
path,
7057+
exists,
7058+
on_empty,
7059+
on_error,
7060+
})
7061+
}
7062+
7063+
fn parse_json_table_column_error_handling(
7064+
&mut self,
7065+
) -> Result<Option<JsonTableColumnErrorHandling>, ParserError> {
7066+
let res = if self.parse_keyword(Keyword::NULL) {
7067+
JsonTableColumnErrorHandling::Null
7068+
} else if self.parse_keyword(Keyword::ERROR) {
7069+
JsonTableColumnErrorHandling::Error
7070+
} else if self.parse_keyword(Keyword::DEFAULT) {
7071+
JsonTableColumnErrorHandling::Default(self.parse_value()?)
7072+
} else {
7073+
return Ok(None);
7074+
};
7075+
self.expect_keyword(Keyword::ON)?;
7076+
Ok(Some(res))
7077+
}
7078+
70177079
pub fn parse_derived_table_factor(
70187080
&mut self,
70197081
lateral: IsLateral,

tests/sqlparser_mysql.rs

+54
Original file line numberDiff line numberDiff line change
@@ -1870,3 +1870,57 @@ fn parse_convert_using() {
18701870
// with a type + a charset
18711871
mysql().verified_only_select("SELECT CONVERT('test', CHAR CHARACTER SET utf8mb4)");
18721872
}
1873+
1874+
#[test]
1875+
fn parse_json_table() {
1876+
mysql().verified_only_select("SELECT * FROM JSON_TABLE('[[1, 2], [3, 4]]', '$[*]' COLUMNS(a INT PATH '$[0]', b INT PATH '$[1]')) AS t");
1877+
mysql().verified_only_select(
1878+
r#"SELECT * FROM JSON_TABLE('["x", "y"]', '$[*]' COLUMNS(a VARCHAR(20) PATH '$')) AS t"#,
1879+
);
1880+
// with a bound parameter
1881+
mysql().verified_only_select(
1882+
r#"SELECT * FROM JSON_TABLE(?, '$[*]' COLUMNS(a VARCHAR(20) PATH '$')) AS t"#,
1883+
);
1884+
// quote escaping
1885+
mysql().verified_only_select(r#"SELECT * FROM JSON_TABLE('{"''": [1,2,3]}', '$."''"[*]' COLUMNS(a VARCHAR(20) PATH '$')) AS t"#);
1886+
// double quotes
1887+
mysql().verified_only_select(
1888+
r#"SELECT * FROM JSON_TABLE("[]", "$[*]" COLUMNS(a VARCHAR(20) PATH "$")) AS t"#,
1889+
);
1890+
// exists
1891+
mysql().verified_only_select(r#"SELECT * FROM JSON_TABLE('[{}, {"x":1}]', '$[*]' COLUMNS(x INT EXISTS PATH '$.x')) AS t"#);
1892+
// error handling
1893+
mysql().verified_only_select(
1894+
r#"SELECT * FROM JSON_TABLE('[1,2]', '$[*]' COLUMNS(x INT PATH '$' ERROR ON ERROR)) AS t"#,
1895+
);
1896+
mysql().verified_only_select(
1897+
r#"SELECT * FROM JSON_TABLE('[1,2]', '$[*]' COLUMNS(x INT PATH '$' ERROR ON EMPTY)) AS t"#,
1898+
);
1899+
mysql().verified_only_select(r#"SELECT * FROM JSON_TABLE('[1,2]', '$[*]' COLUMNS(x INT PATH '$' ERROR ON EMPTY DEFAULT '0' ON ERROR)) AS t"#);
1900+
assert_eq!(
1901+
mysql()
1902+
.verified_only_select(
1903+
r#"SELECT * FROM JSON_TABLE('[1,2]', '$[*]' COLUMNS(x INT PATH '$' DEFAULT '0' ON EMPTY NULL ON ERROR)) AS t"#
1904+
)
1905+
.from[0]
1906+
.relation,
1907+
TableFactor::JsonTable {
1908+
json_expr: Expr::Value(Value::SingleQuotedString("[1,2]".to_string())),
1909+
json_path: Value::SingleQuotedString("$[*]".to_string()),
1910+
columns: vec![
1911+
JsonTableColumn {
1912+
name: Ident::new("x"),
1913+
r#type: DataType::Int(None),
1914+
path: Value::SingleQuotedString("$".to_string()),
1915+
exists: false,
1916+
on_empty: Some(JsonTableColumnErrorHandling::Default(Value::SingleQuotedString("0".to_string()))),
1917+
on_error: Some(JsonTableColumnErrorHandling::Null),
1918+
},
1919+
],
1920+
alias: Some(TableAlias {
1921+
name: Ident::new("t"),
1922+
columns: vec![],
1923+
}),
1924+
}
1925+
);
1926+
}

0 commit comments

Comments
 (0)