Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,11 +756,23 @@ pub struct With {
pub recursive: bool,
/// The list of CTEs declared by this `WITH` clause.
pub cte_tables: Vec<Cte>,
/// Optional XML namespace definitions (`WITH XMLNAMESPACES (...)`).
pub xml_namespaces: Vec<XmlNamespaceDefinition>,
Comment on lines 757 to +760

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feature looks similar to what was done here for clickhouse CSEs, such that I'm thinking we essentially want to introduce this feature in that style instead. is xml_namespaces looks like a regular expression (CSE) so that the enum changes might even be verbatim (similarly for the dialect method name)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed i use the WITH keyword as done with cte_tables, i hope i understood what you have meant.

}

impl fmt::Display for With {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("WITH ")?;
if !self.xml_namespaces.is_empty() {
write!(
f,
"XMLNAMESPACES ({})",
display_comma_separated(&self.xml_namespaces)
)?;
if !self.cte_tables.is_empty() {
f.write_str(", ")?;
}
}
if self.recursive {
f.write_str("RECURSIVE ")?;
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ impl Spanned for With {
with_token,
recursive: _, // bool
cte_tables,
xml_namespaces: _, // handled separately; no span tracking needed
} = self;

union_spans(
Expand Down
12 changes: 12 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,18 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports a leading `WITH XMLNAMESPACES (...)`
/// clause in queries.
///
/// Example:
/// ```sql
/// WITH XMLNAMESPACES ('urn:example' AS ns)
/// SELECT 1
/// ```
fn supports_with_xmlnamespaces_clause(&self) -> bool {
false
}

/// Returns true if the dialect supports aliased function arguments,
/// e.g. `XMLFOREST(a AS x)` in PostgreSQL.
fn supports_aliased_function_args(&self) -> bool {
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ impl Dialect for MsSqlDialect {
_ => None,
}
}

// see: https://learn.microsoft.com/en-us/sql/t-sql/xml/with-xmlnamespaces
fn supports_with_xmlnamespaces_clause(&self) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we include a link to the documentation of this syntax?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iffyio Added documentation link

true
}
}

impl MsSqlDialect {
Expand Down
38 changes: 32 additions & 6 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14501,12 +14501,38 @@ impl<'a> Parser<'a> {
pub fn parse_query(&mut self) -> Result<Box<Query>, ParserError> {
let _guard = self.recursion_counter.try_decrease()?;
let with = if self.parse_keyword(Keyword::WITH) {
let with_token = self.get_current_token();
Some(With {
with_token: with_token.clone().into(),
recursive: self.parse_keyword(Keyword::RECURSIVE),
cte_tables: self.parse_comma_separated(Parser::parse_cte)?,
})
let with_token = self.get_current_token().clone();
if self.dialect.supports_with_xmlnamespaces_clause()
&& self.parse_keyword(Keyword::XMLNAMESPACES)
{
self.expect_token(&Token::LParen)?;
let namespaces =
self.parse_comma_separated(Parser::parse_xml_namespace_definition)?;
self.expect_token(&Token::RParen)?;

if self.consume_token(&Token::Comma) {
Some(With {
with_token: with_token.clone().into(),
recursive: self.parse_keyword(Keyword::RECURSIVE),
cte_tables: self.parse_comma_separated(Parser::parse_cte)?,
xml_namespaces: namespaces,
})
} else {
Some(With {
with_token: with_token.clone().into(),
recursive: false,
cte_tables: vec![],
xml_namespaces: namespaces,
})
}
} else {
Some(With {
with_token: with_token.clone().into(),
recursive: self.parse_keyword(Keyword::RECURSIVE),
cte_tables: self.parse_comma_separated(Parser::parse_cte)?,
xml_namespaces: vec![],
})
}
} else {
None
};
Expand Down
7 changes: 7 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2925,3 +2925,10 @@ fn parse_mssql_money_constants() {
expr_from_projection(only(&select.projection)),
);
}

#[test]
fn parse_xmlnamespaces() {

ms().verified_stmt("WITH XMLNAMESPACES ('urn:test' AS ns) SELECT 1 AS [ns:Value] FOR XML PATH('ns:Root')");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merged tests and used verifies_stmt() @iffyio

ms().verified_stmt("WITH XMLNAMESPACES ('urn:example' AS ns), t AS (SELECT 1 AS id) SELECT id FROM t");
}