diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ab917dc4c..3929d228b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -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, diff --git a/src/ast/query.rs b/src/ast/query.rs index 38128dffe..d103f589e 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -45,6 +45,10 @@ pub struct Query { pub fetch: Option, /// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]` pub locks: Vec, + /// `FOR XML { RAW | AUTO | EXPLICIT | PATH } [ , ELEMENTS ]` + /// `FOR JSON { AUTO | PATH } [ , INCLUDE_NULL_VALUES ]` + /// (MSSQL-specific) + pub for_clause: Option, } 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, + include_null_values: bool, + without_array_wrapper: bool, + }, + Xml { + for_xml: ForXml, + elements: bool, + binary_base64: bool, + root: Option, + 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), + Auto, + Explicit, + Path(Option), +} + +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"), + } + } +} diff --git a/src/keywords.rs b/src/keywords.rs index 2941c8176..2de36562f 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -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, ]; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1cf47f5c8..bad0470c1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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, 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 { + 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 { + 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 { let name = self.parse_identifier()?; diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 16e6bbec0..0400b21c5 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -534,13 +534,7 @@ impl<'a> Tokenizer<'a> { /// Tokenize the statement and produce a vector of tokens pub fn tokenize(&mut self) -> Result, TokenizerError> { let twl = self.tokenize_with_location()?; - - let mut tokens: Vec = 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 diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index c4499731a..71c00a256 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -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 { diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 73994681a..59a68d2c8 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -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 ); diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 2788dfabe..c80003b7d 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -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, })) ) } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index c1fba8200..d01322301 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -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)), );