Skip to content

Commit 62eaee6

Browse files
gaoqiangziffyio
andauthored
Add support for MSSQL's XQuery methods (#1500)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent 2bb8144 commit 62eaee6

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

src/ast/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,23 @@ pub enum Expr {
808808
},
809809
/// Scalar function call e.g. `LEFT(foo, 5)`
810810
Function(Function),
811+
/// Arbitrary expr method call
812+
///
813+
/// Syntax:
814+
///
815+
/// `<arbitrary-expr>.<function-call>.<function-call-expr>...`
816+
///
817+
/// > `arbitrary-expr` can be any expression including a function call.
818+
///
819+
/// Example:
820+
///
821+
/// ```sql
822+
/// SELECT (SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
823+
/// SELECT CONVERT(XML,'<Book>abc</Book>').value('.','NVARCHAR(MAX)').value('.','NVARCHAR(MAX)')
824+
/// ```
825+
///
826+
/// (mssql): <https://learn.microsoft.com/en-us/sql/t-sql/xml/xml-data-type-methods?view=sql-server-ver16>
827+
Method(Method),
811828
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
812829
///
813830
/// Note we only recognize a complete single expression as `<condition>`,
@@ -1464,6 +1481,7 @@ impl fmt::Display for Expr {
14641481
write!(f, " '{}'", &value::escape_single_quote_string(value))
14651482
}
14661483
Expr::Function(fun) => write!(f, "{fun}"),
1484+
Expr::Method(method) => write!(f, "{method}"),
14671485
Expr::Case {
14681486
operand,
14691487
conditions,
@@ -5609,6 +5627,27 @@ impl fmt::Display for FunctionArgumentClause {
56095627
}
56105628
}
56115629

5630+
/// A method call
5631+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5632+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5633+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5634+
pub struct Method {
5635+
pub expr: Box<Expr>,
5636+
// always non-empty
5637+
pub method_chain: Vec<Function>,
5638+
}
5639+
5640+
impl fmt::Display for Method {
5641+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5642+
write!(
5643+
f,
5644+
"{}.{}",
5645+
self.expr,
5646+
display_separated(&self.method_chain, ".")
5647+
)
5648+
}
5649+
}
5650+
56125651
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
56135652
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56145653
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/dialect/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,15 @@ pub trait Dialect: Debug + Any {
279279
false
280280
}
281281

282+
/// Returns true if the dialect supports method calls, for example:
283+
///
284+
/// ```sql
285+
/// SELECT (SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
286+
/// ```
287+
fn supports_methods(&self) -> bool {
288+
false
289+
}
290+
282291
/// Returns true if the dialect supports multiple variable assignment
283292
/// using parentheses in a `SET` variable declaration.
284293
///

src/dialect/mssql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,8 @@ impl Dialect for MsSqlDialect {
6262
fn supports_boolean_literals(&self) -> bool {
6363
false
6464
}
65+
66+
fn supports_methods(&self) -> bool {
67+
true
68+
}
6569
}

src/parser/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,7 @@ impl<'a> Parser<'a> {
13171317
}
13181318
};
13191319
self.expect_token(&Token::RParen)?;
1320+
let expr = self.try_parse_method(expr)?;
13201321
if !self.consume_token(&Token::Period) {
13211322
Ok(expr)
13221323
} else {
@@ -1346,6 +1347,9 @@ impl<'a> Parser<'a> {
13461347
}
13471348
_ => self.expected("an expression", next_token),
13481349
}?;
1350+
1351+
let expr = self.try_parse_method(expr)?;
1352+
13491353
if self.parse_keyword(Keyword::COLLATE) {
13501354
Ok(Expr::Collate {
13511355
expr: Box::new(expr),
@@ -1403,6 +1407,41 @@ impl<'a> Parser<'a> {
14031407
})
14041408
}
14051409

1410+
/// Parses method call expression
1411+
fn try_parse_method(&mut self, expr: Expr) -> Result<Expr, ParserError> {
1412+
if !self.dialect.supports_methods() {
1413+
return Ok(expr);
1414+
}
1415+
let method_chain = self.maybe_parse(|p| {
1416+
let mut method_chain = Vec::new();
1417+
while p.consume_token(&Token::Period) {
1418+
let tok = p.next_token();
1419+
let name = match tok.token {
1420+
Token::Word(word) => word.to_ident(),
1421+
_ => return p.expected("identifier", tok),
1422+
};
1423+
let func = match p.parse_function(ObjectName(vec![name]))? {
1424+
Expr::Function(func) => func,
1425+
_ => return p.expected("function", p.peek_token()),
1426+
};
1427+
method_chain.push(func);
1428+
}
1429+
if !method_chain.is_empty() {
1430+
Ok(method_chain)
1431+
} else {
1432+
p.expected("function", p.peek_token())
1433+
}
1434+
})?;
1435+
if let Some(method_chain) = method_chain {
1436+
Ok(Expr::Method(Method {
1437+
expr: Box::new(expr),
1438+
method_chain,
1439+
}))
1440+
} else {
1441+
Ok(expr)
1442+
}
1443+
}
1444+
14061445
pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
14071446
self.expect_token(&Token::LParen)?;
14081447

tests/sqlparser_common.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11403,6 +11403,76 @@ fn test_try_convert() {
1140311403
dialects.verified_expr("TRY_CONVERT('foo', VARCHAR(MAX))");
1140411404
}
1140511405

11406+
#[test]
11407+
fn parse_method_select() {
11408+
let dialects = all_dialects_where(|d| d.supports_methods());
11409+
let _ = dialects.verified_only_select(
11410+
"SELECT LEFT('abc', 1).value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)') AS T",
11411+
);
11412+
let _ = dialects.verified_only_select("SELECT STUFF((SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS T");
11413+
let _ = dialects
11414+
.verified_only_select("SELECT CAST(column AS XML).value('.', 'NVARCHAR(MAX)') AS T");
11415+
11416+
// `CONVERT` support
11417+
let dialects = all_dialects_where(|d| {
11418+
d.supports_methods() && d.supports_try_convert() && d.convert_type_before_value()
11419+
});
11420+
let _ = dialects.verified_only_select("SELECT CONVERT(XML, '<Book>abc</Book>').value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)') AS T");
11421+
}
11422+
11423+
#[test]
11424+
fn parse_method_expr() {
11425+
let dialects = all_dialects_where(|d| d.supports_methods());
11426+
let expr = dialects
11427+
.verified_expr("LEFT('abc', 1).value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)')");
11428+
match expr {
11429+
Expr::Method(Method { expr, method_chain }) => {
11430+
assert!(matches!(*expr, Expr::Function(_)));
11431+
assert!(matches!(
11432+
method_chain[..],
11433+
[Function { .. }, Function { .. }]
11434+
));
11435+
}
11436+
_ => unreachable!(),
11437+
}
11438+
let expr = dialects.verified_expr(
11439+
"(SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')",
11440+
);
11441+
match expr {
11442+
Expr::Method(Method { expr, method_chain }) => {
11443+
assert!(matches!(*expr, Expr::Subquery(_)));
11444+
assert!(matches!(method_chain[..], [Function { .. }]));
11445+
}
11446+
_ => unreachable!(),
11447+
}
11448+
let expr = dialects.verified_expr("CAST(column AS XML).value('.', 'NVARCHAR(MAX)')");
11449+
match expr {
11450+
Expr::Method(Method { expr, method_chain }) => {
11451+
assert!(matches!(*expr, Expr::Cast { .. }));
11452+
assert!(matches!(method_chain[..], [Function { .. }]));
11453+
}
11454+
_ => unreachable!(),
11455+
}
11456+
11457+
// `CONVERT` support
11458+
let dialects = all_dialects_where(|d| {
11459+
d.supports_methods() && d.supports_try_convert() && d.convert_type_before_value()
11460+
});
11461+
let expr = dialects.verified_expr(
11462+
"CONVERT(XML, '<Book>abc</Book>').value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)')",
11463+
);
11464+
match expr {
11465+
Expr::Method(Method { expr, method_chain }) => {
11466+
assert!(matches!(*expr, Expr::Convert { .. }));
11467+
assert!(matches!(
11468+
method_chain[..],
11469+
[Function { .. }, Function { .. }]
11470+
));
11471+
}
11472+
_ => unreachable!(),
11473+
}
11474+
}
11475+
1140611476
#[test]
1140711477
fn test_show_dbs_schemas_tables_views() {
1140811478
// These statements are parsed the same by all dialects

0 commit comments

Comments
 (0)