Skip to content

Commit 9082448

Browse files
authored
Add support for MSSQL's OPENJSON WITH clause (#1498)
1 parent e857787 commit 9082448

File tree

5 files changed

+476
-6
lines changed

5 files changed

+476
-6
lines changed

src/ast/mod.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ pub use self::query::{
5656
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn,
5757
JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn, LateralView,
5858
LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
59-
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OrderBy, OrderByExpr,
60-
PivotValueSource, ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier,
61-
ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr,
62-
SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableFactor,
63-
TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode, Values,
64-
WildcardAdditionalOptions, With, WithFill,
59+
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
60+
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
61+
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
62+
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table,
63+
TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity,
64+
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
6565
};
6666

6767
pub use self::trigger::{

src/ast/query.rs

+74
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,27 @@ pub enum TableFactor {
10361036
/// The alias for the table.
10371037
alias: Option<TableAlias>,
10381038
},
1039+
/// The MSSQL's `OPENJSON` table-valued function.
1040+
///
1041+
/// ```sql
1042+
/// OPENJSON( jsonExpression [ , path ] ) [ <with_clause> ]
1043+
///
1044+
/// <with_clause> ::= WITH ( { colName type [ column_path ] [ AS JSON ] } [ ,...n ] )
1045+
/// ````
1046+
///
1047+
/// Reference: <https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16#syntax>
1048+
OpenJsonTable {
1049+
/// The JSON expression to be evaluated. It must evaluate to a json string
1050+
json_expr: Expr,
1051+
/// The path to the array or object to be iterated over.
1052+
/// It must evaluate to a json array or object.
1053+
json_path: Option<Value>,
1054+
/// The columns to be extracted from each element of the array or object.
1055+
/// Each column must have a name and a type.
1056+
columns: Vec<OpenJsonTableColumn>,
1057+
/// The alias for the table.
1058+
alias: Option<TableAlias>,
1059+
},
10391060
/// Represents a parenthesized table factor. The SQL spec only allows a
10401061
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
10411062
/// possibly several times.
@@ -1461,6 +1482,25 @@ impl fmt::Display for TableFactor {
14611482
}
14621483
Ok(())
14631484
}
1485+
TableFactor::OpenJsonTable {
1486+
json_expr,
1487+
json_path,
1488+
columns,
1489+
alias,
1490+
} => {
1491+
write!(f, "OPENJSON({json_expr}")?;
1492+
if let Some(json_path) = json_path {
1493+
write!(f, ", {json_path}")?;
1494+
}
1495+
write!(f, ")")?;
1496+
if !columns.is_empty() {
1497+
write!(f, " WITH ({})", display_comma_separated(columns))?;
1498+
}
1499+
if let Some(alias) = alias {
1500+
write!(f, " AS {alias}")?;
1501+
}
1502+
Ok(())
1503+
}
14641504
TableFactor::NestedJoin {
14651505
table_with_joins,
14661506
alias,
@@ -2421,6 +2461,40 @@ impl fmt::Display for JsonTableColumnErrorHandling {
24212461
}
24222462
}
24232463

2464+
/// A single column definition in MSSQL's `OPENJSON WITH` clause.
2465+
///
2466+
/// ```sql
2467+
/// colName type [ column_path ] [ AS JSON ]
2468+
/// ```
2469+
///
2470+
/// Reference: <https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16#syntax>
2471+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2472+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2473+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2474+
pub struct OpenJsonTableColumn {
2475+
/// The name of the column to be extracted.
2476+
pub name: Ident,
2477+
/// The type of the column to be extracted.
2478+
pub r#type: DataType,
2479+
/// The path to the column to be extracted. Must be a literal string.
2480+
pub path: Option<String>,
2481+
/// The `AS JSON` option.
2482+
pub as_json: bool,
2483+
}
2484+
2485+
impl fmt::Display for OpenJsonTableColumn {
2486+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2487+
write!(f, "{} {}", self.name, self.r#type)?;
2488+
if let Some(path) = &self.path {
2489+
write!(f, " '{}'", value::escape_single_quote_string(path))?;
2490+
}
2491+
if self.as_json {
2492+
write!(f, " AS JSON")?;
2493+
}
2494+
Ok(())
2495+
}
2496+
}
2497+
24242498
/// BigQuery supports ValueTables which have 2 modes:
24252499
/// `SELECT AS STRUCT`
24262500
/// `SELECT AS VALUE`

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ define_keywords!(
537537
ONE,
538538
ONLY,
539539
OPEN,
540+
OPENJSON,
540541
OPERATOR,
541542
OPTIMIZE,
542543
OPTIMIZER_COSTS,

src/parser/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -10049,6 +10049,7 @@ impl<'a> Parser<'a> {
1004910049
| TableFactor::Function { alias, .. }
1005010050
| TableFactor::UNNEST { alias, .. }
1005110051
| TableFactor::JsonTable { alias, .. }
10052+
| TableFactor::OpenJsonTable { alias, .. }
1005210053
| TableFactor::TableFunction { alias, .. }
1005310054
| TableFactor::Pivot { alias, .. }
1005410055
| TableFactor::Unpivot { alias, .. }
@@ -10162,6 +10163,9 @@ impl<'a> Parser<'a> {
1016210163
columns,
1016310164
alias,
1016410165
})
10166+
} else if self.parse_keyword_with_tokens(Keyword::OPENJSON, &[Token::LParen]) {
10167+
self.prev_token();
10168+
self.parse_open_json_table_factor()
1016510169
} else {
1016610170
let name = self.parse_object_name(true)?;
1016710171

@@ -10227,6 +10231,34 @@ impl<'a> Parser<'a> {
1022710231
}
1022810232
}
1022910233

10234+
/// Parses `OPENJSON( jsonExpression [ , path ] ) [ <with_clause> ]` clause,
10235+
/// assuming the `OPENJSON` keyword was already consumed.
10236+
fn parse_open_json_table_factor(&mut self) -> Result<TableFactor, ParserError> {
10237+
self.expect_token(&Token::LParen)?;
10238+
let json_expr = self.parse_expr()?;
10239+
let json_path = if self.consume_token(&Token::Comma) {
10240+
Some(self.parse_value()?)
10241+
} else {
10242+
None
10243+
};
10244+
self.expect_token(&Token::RParen)?;
10245+
let columns = if self.parse_keyword(Keyword::WITH) {
10246+
self.expect_token(&Token::LParen)?;
10247+
let columns = self.parse_comma_separated(Parser::parse_openjson_table_column_def)?;
10248+
self.expect_token(&Token::RParen)?;
10249+
columns
10250+
} else {
10251+
Vec::new()
10252+
};
10253+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
10254+
Ok(TableFactor::OpenJsonTable {
10255+
json_expr,
10256+
json_path,
10257+
columns,
10258+
alias,
10259+
})
10260+
}
10261+
1023010262
fn parse_match_recognize(&mut self, table: TableFactor) -> Result<TableFactor, ParserError> {
1023110263
self.expect_token(&Token::LParen)?;
1023210264

@@ -10513,6 +10545,34 @@ impl<'a> Parser<'a> {
1051310545
}))
1051410546
}
1051510547

10548+
/// Parses MSSQL's `OPENJSON WITH` column definition.
10549+
///
10550+
/// ```sql
10551+
/// colName type [ column_path ] [ AS JSON ]
10552+
/// ```
10553+
///
10554+
/// Reference: <https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16#syntax>
10555+
pub fn parse_openjson_table_column_def(&mut self) -> Result<OpenJsonTableColumn, ParserError> {
10556+
let name = self.parse_identifier(false)?;
10557+
let r#type = self.parse_data_type()?;
10558+
let path = if let Token::SingleQuotedString(path) = self.peek_token().token {
10559+
self.next_token();
10560+
Some(path)
10561+
} else {
10562+
None
10563+
};
10564+
let as_json = self.parse_keyword(Keyword::AS);
10565+
if as_json {
10566+
self.expect_keyword(Keyword::JSON)?;
10567+
}
10568+
Ok(OpenJsonTableColumn {
10569+
name,
10570+
r#type,
10571+
path,
10572+
as_json,
10573+
})
10574+
}
10575+
1051610576
fn parse_json_table_column_error_handling(
1051710577
&mut self,
1051810578
) -> Result<Option<JsonTableColumnErrorHandling>, ParserError> {

0 commit comments

Comments
 (0)