Support mssql json and xml extensions (#1043)

This commit is contained in:
Ophir LOJKINE 2023-11-18 13:03:12 +01:00 committed by GitHub
parent ff8312bfd8
commit 953c833a4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 318 additions and 15 deletions

View file

@ -37,12 +37,12 @@ pub use self::ddl::{
};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, GroupByExpr, IdentWithAlias, Join,
JoinConstraint, JoinOperator, LateralView, LockClause, LockType, NamedWindowDefinition,
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement,
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table,
TableAlias, TableFactor, TableVersion, TableWithJoins, Top, Values, WildcardAdditionalOptions,
With,
Cte, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml,
GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator, LateralView, LockClause,
LockType, NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query,
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor, TableVersion,
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
};
pub use self::value::{
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,

View file

@ -45,6 +45,10 @@ pub struct Query {
pub fetch: Option<Fetch>,
/// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]`
pub locks: Vec<LockClause>,
/// `FOR XML { RAW | AUTO | EXPLICIT | PATH } [ , ELEMENTS ]`
/// `FOR JSON { AUTO | PATH } [ , INCLUDE_NULL_VALUES ]`
/// (MSSQL-specific)
pub for_clause: Option<ForClause>,
}
impl fmt::Display for Query {
@ -71,6 +75,9 @@ impl fmt::Display for Query {
if !self.locks.is_empty() {
write!(f, " {}", display_separated(&self.locks, " "))?;
}
if let Some(ref for_clause) = self.for_clause {
write!(f, " {}", for_clause)?;
}
Ok(())
}
}
@ -1315,3 +1322,125 @@ impl fmt::Display for GroupByExpr {
}
}
}
/// FOR XML or FOR JSON clause, specific to MSSQL
/// (formats the output of a query as XML or JSON)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum ForClause {
Browse,
Json {
for_json: ForJson,
root: Option<String>,
include_null_values: bool,
without_array_wrapper: bool,
},
Xml {
for_xml: ForXml,
elements: bool,
binary_base64: bool,
root: Option<String>,
r#type: bool,
},
}
impl fmt::Display for ForClause {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ForClause::Browse => write!(f, "FOR BROWSE"),
ForClause::Json {
for_json,
root,
include_null_values,
without_array_wrapper,
} => {
write!(f, "FOR JSON ")?;
write!(f, "{}", for_json)?;
if let Some(root) = root {
write!(f, ", ROOT('{}')", root)?;
}
if *include_null_values {
write!(f, ", INCLUDE_NULL_VALUES")?;
}
if *without_array_wrapper {
write!(f, ", WITHOUT_ARRAY_WRAPPER")?;
}
Ok(())
}
ForClause::Xml {
for_xml,
elements,
binary_base64,
root,
r#type,
} => {
write!(f, "FOR XML ")?;
write!(f, "{}", for_xml)?;
if *binary_base64 {
write!(f, ", BINARY BASE64")?;
}
if *r#type {
write!(f, ", TYPE")?;
}
if let Some(root) = root {
write!(f, ", ROOT('{}')", root)?;
}
if *elements {
write!(f, ", ELEMENTS")?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum ForXml {
Raw(Option<String>),
Auto,
Explicit,
Path(Option<String>),
}
impl fmt::Display for ForXml {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ForXml::Raw(root) => {
write!(f, "RAW")?;
if let Some(root) = root {
write!(f, "('{}')", root)?;
}
Ok(())
}
ForXml::Auto => write!(f, "AUTO"),
ForXml::Explicit => write!(f, "EXPLICIT"),
ForXml::Path(root) => {
write!(f, "PATH")?;
if let Some(root) = root {
write!(f, "('{}')", root)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ForJson {
Auto,
Path,
}
impl fmt::Display for ForJson {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ForJson::Auto => write!(f, "AUTO"),
ForJson::Path => write!(f, "PATH"),
}
}
}

View file

@ -97,11 +97,13 @@ define_keywords!(
ATOMIC,
ATTACH,
AUTHORIZATION,
AUTO,
AUTOINCREMENT,
AUTO_INCREMENT,
AVG,
AVRO,
BACKWARD,
BASE64,
BEGIN,
BEGIN_FRAME,
BEGIN_PARTITION,
@ -116,6 +118,7 @@ define_keywords!(
BOOL,
BOOLEAN,
BOTH,
BROWSE,
BTREE,
BY,
BYPASSRLS,
@ -232,6 +235,7 @@ define_keywords!(
DYNAMIC,
EACH,
ELEMENT,
ELEMENTS,
ELSE,
ENCODING,
ENCRYPTION,
@ -256,6 +260,7 @@ define_keywords!(
EXP,
EXPANSION,
EXPLAIN,
EXPLICIT,
EXTENDED,
EXTERNAL,
EXTRACT,
@ -319,6 +324,7 @@ define_keywords!(
IMMUTABLE,
IN,
INCLUDE,
INCLUDE_NULL_VALUES,
INCREMENT,
INDEX,
INDICATOR,
@ -463,6 +469,7 @@ define_keywords!(
PARTITIONED,
PARTITIONS,
PASSWORD,
PATH,
PATTERN,
PERCENT,
PERCENTILE_CONT,
@ -494,6 +501,7 @@ define_keywords!(
QUOTE,
RANGE,
RANK,
RAW,
RCFILE,
READ,
READS,
@ -535,6 +543,7 @@ define_keywords!(
ROLE,
ROLLBACK,
ROLLUP,
ROOT,
ROW,
ROWID,
ROWS,
@ -682,8 +691,10 @@ define_keywords!(
WITH,
WITHIN,
WITHOUT,
WITHOUT_ARRAY_WRAPPER,
WORK,
WRITE,
XML,
XOR,
YEAR,
ZONE,
@ -732,6 +743,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
Keyword::QUALIFY,
Keyword::WINDOW,
Keyword::END,
Keyword::FOR,
// for MYSQL PARTITION SELECTION
Keyword::PARTITION,
];

View file

@ -5825,6 +5825,7 @@ impl<'a> Parser<'a> {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
})
} else if self.parse_keyword(Keyword::UPDATE) {
let update = self.parse_update()?;
@ -5837,6 +5838,7 @@ impl<'a> Parser<'a> {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
})
} else {
let body = Box::new(self.parse_query_body(0)?);
@ -5888,9 +5890,15 @@ impl<'a> Parser<'a> {
None
};
let mut for_clause = None;
let mut locks = Vec::new();
while self.parse_keyword(Keyword::FOR) {
locks.push(self.parse_lock()?);
if let Some(parsed_for_clause) = self.parse_for_clause()? {
for_clause = Some(parsed_for_clause);
break;
} else {
locks.push(self.parse_lock()?);
}
}
Ok(Query {
@ -5902,10 +5910,113 @@ impl<'a> Parser<'a> {
offset,
fetch,
locks,
for_clause,
})
}
}
/// Parse a mssql `FOR [XML | JSON | BROWSE]` clause
pub fn parse_for_clause(&mut self) -> Result<Option<ForClause>, ParserError> {
if self.parse_keyword(Keyword::XML) {
Ok(Some(self.parse_for_xml()?))
} else if self.parse_keyword(Keyword::JSON) {
Ok(Some(self.parse_for_json()?))
} else if self.parse_keyword(Keyword::BROWSE) {
Ok(Some(ForClause::Browse))
} else {
Ok(None)
}
}
/// Parse a mssql `FOR XML` clause
pub fn parse_for_xml(&mut self) -> Result<ForClause, ParserError> {
let for_xml = if self.parse_keyword(Keyword::RAW) {
let mut element_name = None;
if self.peek_token().token == Token::LParen {
self.expect_token(&Token::LParen)?;
element_name = Some(self.parse_literal_string()?);
self.expect_token(&Token::RParen)?;
}
ForXml::Raw(element_name)
} else if self.parse_keyword(Keyword::AUTO) {
ForXml::Auto
} else if self.parse_keyword(Keyword::EXPLICIT) {
ForXml::Explicit
} else if self.parse_keyword(Keyword::PATH) {
let mut element_name = None;
if self.peek_token().token == Token::LParen {
self.expect_token(&Token::LParen)?;
element_name = Some(self.parse_literal_string()?);
self.expect_token(&Token::RParen)?;
}
ForXml::Path(element_name)
} else {
return Err(ParserError::ParserError(
"Expected FOR XML [RAW | AUTO | EXPLICIT | PATH ]".to_string(),
));
};
let mut elements = false;
let mut binary_base64 = false;
let mut root = None;
let mut r#type = false;
while self.peek_token().token == Token::Comma {
self.next_token();
if self.parse_keyword(Keyword::ELEMENTS) {
elements = true;
} else if self.parse_keyword(Keyword::BINARY) {
self.expect_keyword(Keyword::BASE64)?;
binary_base64 = true;
} else if self.parse_keyword(Keyword::ROOT) {
self.expect_token(&Token::LParen)?;
root = Some(self.parse_literal_string()?);
self.expect_token(&Token::RParen)?;
} else if self.parse_keyword(Keyword::TYPE) {
r#type = true;
}
}
Ok(ForClause::Xml {
for_xml,
elements,
binary_base64,
root,
r#type,
})
}
/// Parse a mssql `FOR JSON` clause
pub fn parse_for_json(&mut self) -> Result<ForClause, ParserError> {
let for_json = if self.parse_keyword(Keyword::AUTO) {
ForJson::Auto
} else if self.parse_keyword(Keyword::PATH) {
ForJson::Path
} else {
return Err(ParserError::ParserError(
"Expected FOR JSON [AUTO | PATH ]".to_string(),
));
};
let mut root = None;
let mut include_null_values = false;
let mut without_array_wrapper = false;
while self.peek_token().token == Token::Comma {
self.next_token();
if self.parse_keyword(Keyword::ROOT) {
self.expect_token(&Token::LParen)?;
root = Some(self.parse_literal_string()?);
self.expect_token(&Token::RParen)?;
} else if self.parse_keyword(Keyword::INCLUDE_NULL_VALUES) {
include_null_values = true;
} else if self.parse_keyword(Keyword::WITHOUT_ARRAY_WRAPPER) {
without_array_wrapper = true;
}
}
Ok(ForClause::Json {
for_json,
root,
include_null_values,
without_array_wrapper,
})
}
/// Parse a CTE (`alias [( col1, col2, ... )] AS (subquery)`)
pub fn parse_cte(&mut self) -> Result<Cte, ParserError> {
let name = self.parse_identifier()?;

View file

@ -534,13 +534,7 @@ impl<'a> Tokenizer<'a> {
/// Tokenize the statement and produce a vector of tokens
pub fn tokenize(&mut self) -> Result<Vec<Token>, TokenizerError> {
let twl = self.tokenize_with_location()?;
let mut tokens: Vec<Token> = vec![];
tokens.reserve(twl.len());
for token_with_location in twl {
tokens.push(token_with_location.token);
}
Ok(tokens)
Ok(twl.into_iter().map(|t| t.token).collect())
}
/// Tokenize the statement and produce a vector of tokens with location information

View file

@ -270,6 +270,7 @@ fn parse_update_set_from() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
alias: Some(TableAlias {
name: Ident::new("t2"),
@ -2756,6 +2757,7 @@ fn parse_create_table_as_table() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
});
match verified_stmt(sql1) {
@ -2780,6 +2782,7 @@ fn parse_create_table_as_table() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
});
match verified_stmt(sql2) {
@ -4082,6 +4085,7 @@ fn parse_interval_and_or_xor() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}))];
assert_eq!(actual_ast, expected_ast);
@ -6656,6 +6660,7 @@ fn parse_merge() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
alias: Some(TableAlias {
name: Ident {

View file

@ -96,6 +96,7 @@ fn parse_create_procedure() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
order_by: vec![],
body: Box::new(SetExpr::Select(Box::new(Select {
distinct: None,
@ -431,6 +432,44 @@ fn parse_like() {
chk(true);
}
#[test]
fn parse_for_clause() {
ms_and_generic().verified_stmt("SELECT a FROM t FOR JSON PATH");
ms_and_generic().verified_stmt("SELECT b FROM t FOR JSON AUTO");
ms_and_generic().verified_stmt("SELECT c FROM t FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER");
ms_and_generic().verified_stmt("SELECT 1 FROM t FOR JSON PATH, ROOT('x'), INCLUDE_NULL_VALUES");
ms_and_generic().verified_stmt("SELECT 2 FROM t FOR XML AUTO");
ms_and_generic().verified_stmt("SELECT 3 FROM t FOR XML AUTO, TYPE, ELEMENTS");
ms_and_generic().verified_stmt("SELECT * FROM t WHERE x FOR XML AUTO, ELEMENTS");
ms_and_generic().verified_stmt("SELECT x FROM t ORDER BY y FOR XML AUTO, ELEMENTS");
ms_and_generic().verified_stmt("SELECT y FROM t FOR XML PATH('x'), ROOT('y'), ELEMENTS");
ms_and_generic().verified_stmt("SELECT z FROM t FOR XML EXPLICIT, BINARY BASE64");
ms_and_generic().verified_stmt("SELECT * FROM t FOR XML RAW('x')");
ms_and_generic().verified_stmt("SELECT * FROM t FOR BROWSE");
}
#[test]
fn dont_parse_trailing_for() {
assert!(ms()
.run_parser_method("SELECT * FROM foo FOR", |p| p.parse_query())
.is_err());
}
#[test]
fn parse_for_json_expect_ast() {
assert_eq!(
ms().verified_query("SELECT * FROM t FOR JSON PATH, ROOT('root')")
.for_clause
.unwrap(),
ForClause::Json {
for_json: ForJson::Path,
root: Some("root".into()),
without_array_wrapper: false,
include_null_values: false,
}
);
}
#[test]
fn parse_cast_varchar_max() {
ms_and_generic().verified_expr("CAST('foo' AS VARCHAR(MAX))");
@ -545,6 +584,7 @@ fn parse_substring_in_select() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
query
);

View file

@ -566,6 +566,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}))
);
}
@ -609,6 +610,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}))
);
}
@ -649,6 +651,7 @@ fn parse_escaped_backticks_with_escape() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}))
);
}
@ -689,6 +692,7 @@ fn parse_escaped_backticks_with_no_escape() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}))
);
}
@ -964,6 +968,7 @@ fn parse_simple_insert() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
source
);
@ -1004,7 +1009,8 @@ fn parse_ignore_insert() {
limit_by: vec![],
offset: None,
fetch: None,
locks: vec![]
locks: vec![],
for_clause: None,
}),
source
);
@ -1041,6 +1047,7 @@ fn parse_empty_row_insert() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
source
);
@ -1100,6 +1107,7 @@ fn parse_insert_with_on_duplicate_update() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
source
);
@ -1490,6 +1498,7 @@ fn parse_substring_in_select() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}),
query
);
@ -1785,6 +1794,7 @@ fn parse_hex_string_introducer() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
}))
)
}

View file

@ -1010,6 +1010,7 @@ fn parse_copy_to() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
})),
to: true,
target: CopyTarget::File {
@ -2060,6 +2061,7 @@ fn parse_array_subquery_expr() {
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
})),
expr_from_projection(only(&select.projection)),
);