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/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8378,6 +8378,14 @@ pub enum FunctionArgumentClause {
///
/// [`JSON_OBJECT`](https://www.postgresql.org/docs/current/functions-json.html#:~:text=json_object)
JsonReturningClause(JsonReturningClause),
/// The `PASSING` clause, used e.g. in `XMLEXISTS`.
Passing(XmlPassingClause),
/// The `AS <data_type>` clause, used e.g. in `XMLSERIALIZE`.
As(DataType),
/// The `INDENT` clause in `XMLSERIALIZE`.
Indent,
/// The `NO INDENT` clause in `XMLSERIALIZE`.
NoIndent,
}

impl fmt::Display for FunctionArgumentClause {
Expand All @@ -8398,6 +8406,10 @@ impl fmt::Display for FunctionArgumentClause {
FunctionArgumentClause::JsonReturningClause(returning_clause) => {
write!(f, "{returning_clause}")
}
FunctionArgumentClause::Passing(passing) => write!(f, "{passing}"),
FunctionArgumentClause::As(data_type) => write!(f, "AS {data_type}"),
FunctionArgumentClause::Indent => write!(f, "INDENT"),
FunctionArgumentClause::NoIndent => write!(f, "NO INDENT"),
}
}
}
Expand Down
26 changes: 19 additions & 7 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2497,11 +2497,19 @@ impl fmt::Display for TableFactor {
display_comma_separated(namespaces)
)?;
}
write!(
f,
"{row_expression}{passing} COLUMNS {columns})",
columns = display_comma_separated(columns)
)?;
if !passing.arguments.is_empty() {
write!(
f,
"{row_expression} {passing} COLUMNS {columns})",
columns = display_comma_separated(columns)
)?;
} else {
write!(
f,
"{row_expression} COLUMNS {columns})",
columns = display_comma_separated(columns)
)?;
}
if let Some(alias) = alias {
write!(f, " {alias}")?;
}
Expand Down Expand Up @@ -4338,11 +4346,15 @@ pub struct XmlPassingArgument {
pub alias: Option<Ident>,
/// `true` if `BY VALUE` is specified for the argument.
pub by_value: bool,
/// `true` if `BY REF` is specified for the argument.
pub by_ref: bool,
}

impl fmt::Display for XmlPassingArgument {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.by_value {
if self.by_ref {
write!(f, "BY REF ")?;
} else if self.by_value {
write!(f, "BY VALUE ")?;
}
write!(f, "{}", self.expr)?;
Expand All @@ -4366,7 +4378,7 @@ pub struct XmlPassingClause {
impl fmt::Display for XmlPassingClause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.arguments.is_empty() {
write!(f, " PASSING {}", display_comma_separated(&self.arguments))?;
write!(f, "PASSING {}", display_comma_separated(&self.arguments))?;
}
Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,9 @@ impl Spanned for FunctionArgumentClause {
FunctionArgumentClause::Separator(value) => value.span(),
FunctionArgumentClause::JsonNullClause(_) => Span::empty(),
FunctionArgumentClause::JsonReturningClause(_) => Span::empty(),
FunctionArgumentClause::Passing(_) => Span::empty(),
FunctionArgumentClause::As(_) => Span::empty(),
FunctionArgumentClause::Indent | FunctionArgumentClause::NoIndent => Span::empty(),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ define_keywords!(
INCLUDING,
INCREMENT,
INCREMENTAL,
INDENT,
INDEX,
INDICATOR,
INHERIT,
Expand Down
135 changes: 129 additions & 6 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2546,6 +2546,102 @@ impl<'a> Parser<'a> {
})
}

/// Parse the argument list of `XMLELEMENT(NAME name [, XMLATTRIBUTES(...) ] [, content [, ...]])`
/// or `XMLPI(NAME name [, content ])`.
fn parse_xmlelement_or_xmlpi_argument_list(
&mut self,
) -> Result<FunctionArgumentList, ParserError> {
let name_kw = self.parse_identifier()?;
let name_val = self.parse_identifier()?;
let mut args = vec![FunctionArg::Named {
name: name_kw,
arg: FunctionArgExpr::Expr(Expr::Identifier(name_val)),
operator: FunctionArgOperator::Space,
}];
if self.consume_token(&Token::Comma) {
args.extend(self.parse_comma_separated(Parser::parse_function_args)?);
}
self.expect_token(&Token::RParen)?;
Ok(FunctionArgumentList {
duplicate_treatment: None,
args,
clauses: vec![],
})
}

/// Parse the argument list of `XMLROOT(xml, VERSION {text | NO VALUE} [, STANDALONE {YES | NO | NO VALUE} ])`.
fn parse_xmlroot_argument_list(&mut self) -> Result<FunctionArgumentList, ParserError> {
let xml_expr = self.parse_expr()?;
let mut args = vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(xml_expr))];
self.expect_token(&Token::Comma)?;
let version_kw = self.parse_identifier()?;
let version_val = if self.parse_keywords(&[Keyword::NO, Keyword::VALUE]) {
Expr::Identifier(Ident::new("NO VALUE"))
} else {
self.parse_expr()?
};
args.push(FunctionArg::Named {
name: version_kw,
arg: FunctionArgExpr::Expr(version_val),
operator: FunctionArgOperator::Space,
});
if self.consume_token(&Token::Comma) {
let standalone_kw = self.parse_identifier()?;
let standalone_val = if self.parse_keywords(&[Keyword::NO, Keyword::VALUE]) {
Expr::Identifier(Ident::new("NO VALUE"))
} else {
Expr::Identifier(self.parse_identifier()?)
};
args.push(FunctionArg::Named {
name: standalone_kw,
arg: FunctionArgExpr::Expr(standalone_val),
operator: FunctionArgOperator::Space,
});
}
self.expect_token(&Token::RParen)?;
Ok(FunctionArgumentList {
duplicate_treatment: None,
args,
clauses: vec![],
})
}

/// Parse the argument list of `XMLSERIALIZE({ DOCUMENT | CONTENT } value AS type [ [ NO ] INDENT ])`.
fn parse_xmlserialize_argument_list(&mut self) -> Result<FunctionArgumentList, ParserError> {
let mode = self.parse_identifier()?;
let value = self.parse_expr()?;
self.expect_keyword_is(Keyword::AS)?;
let data_type = self.parse_data_type()?;
let mut clauses = vec![FunctionArgumentClause::As(data_type)];
if self.parse_keywords(&[Keyword::NO, Keyword::INDENT]) {
clauses.push(FunctionArgumentClause::NoIndent);
} else if self.parse_keyword(Keyword::INDENT) {
clauses.push(FunctionArgumentClause::Indent);
}
self.expect_token(&Token::RParen)?;
Ok(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Named {
name: mode,
arg: FunctionArgExpr::Expr(value),
operator: FunctionArgOperator::Space,
}],
clauses,
})
}

/// Parse the argument list of `XMLEXISTS(text PASSING [BY {REF|VALUE}] xml [BY {REF|VALUE}])`.
fn parse_xmlexists_argument_list(&mut self) -> Result<FunctionArgumentList, ParserError> {
let xpath_expr = self.parse_expr()?;
let passing = self.parse_xml_passing_clause()?;
self.expect_token(&Token::RParen)?;
Ok(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(xpath_expr))],
clauses: vec![FunctionArgumentClause::Passing(passing)],
})
}

/// Parse a function call expression named by `name` and return it as an `Expr`.
pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
self.parse_function_call(name).map(Expr::Function)
Expand All @@ -2571,10 +2667,22 @@ impl<'a> Parser<'a> {
});
}

let mut args = if self.dialect.supports_xml_expressions()
&& Self::is_simple_unquoted_object_name(&name, "xmlparse")
{
self.parse_xmlparse_argument_list()?
let mut args = if self.dialect.supports_xml_expressions() {
if Self::is_simple_unquoted_object_name(&name, "xmlparse") {
self.parse_xmlparse_argument_list()?
} else if Self::is_simple_unquoted_object_name(&name, "xmlelement")
|| Self::is_simple_unquoted_object_name(&name, "xmlpi")
{
self.parse_xmlelement_or_xmlpi_argument_list()?
} else if Self::is_simple_unquoted_object_name(&name, "xmlroot") {
self.parse_xmlroot_argument_list()?
} else if Self::is_simple_unquoted_object_name(&name, "xmlserialize") {
self.parse_xmlserialize_argument_list()?
} else if Self::is_simple_unquoted_object_name(&name, "xmlexists") {
self.parse_xmlexists_argument_list()?
} else {
self.parse_function_argument_list()?
}
} else {
self.parse_function_argument_list()?
};
Expand Down Expand Up @@ -17088,18 +17196,33 @@ impl<'a> Parser<'a> {
let mut arguments = vec![];
if self.parse_keyword(Keyword::PASSING) {
loop {
let by_value =
self.parse_keyword(Keyword::BY) && self.expect_keyword(Keyword::VALUE).is_ok();
let mut by_ref = false;
let mut by_value = false;
if self.parse_keyword(Keyword::BY) {
if self.parse_keyword(Keyword::REF) {
by_ref = true;
} else if self.parse_keyword(Keyword::VALUE) {
by_value = true;
}
}
let expr = self.parse_expr()?;
let alias = if self.parse_keyword(Keyword::AS) {
Some(self.parse_identifier()?)
} else {
None
};
if self.parse_keyword(Keyword::BY) {
if self.parse_keyword(Keyword::REF) {
by_ref = true;
} else if self.parse_keyword(Keyword::VALUE) {
by_value = true;
}
}
arguments.push(XmlPassingArgument {
expr,
alias,
by_value,
by_ref,
});
if !self.consume_token(&Token::Comma) {
break;
Expand Down
30 changes: 30 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19837,6 +19837,36 @@ fn parse_xmlparse() {
.is_err());
}

#[test]
fn parse_xml_functions() {
let dialects = all_dialects_where(|d| d.supports_xml_expressions());

dialects.verified_stmt("SELECT XMLELEMENT(NAME foo, 'bar')");
dialects.verified_stmt("SELECT XMLELEMENT(NAME foo, 'bar'), * FROM customers");
dialects.verified_stmt("SELECT XMLELEMENT(NAME foo, XMLATTRIBUTES('v' AS attr), 'bar')");
dialects.verified_stmt(r#"SELECT XMLELEMENT(NAME "foo$bar", XMLATTRIBUTES('xyz' AS "a&b"))"#);
dialects.verified_stmt(r#"SELECT XMLPI(NAME php, 'echo "hello world";')"#);
dialects.verified_stmt("SELECT XMLPI(NAME php)");
dialects.verified_stmt("SELECT XMLROOT('<a/>'::xml, VERSION '1.0')");
dialects.verified_stmt("SELECT XMLROOT('<a/>'::xml, VERSION '1.0', STANDALONE YES)");
dialects.verified_stmt("SELECT XMLROOT('<a/>'::xml, VERSION NO VALUE, STANDALONE NO VALUE)");
dialects.verified_stmt("SELECT XMLSERIALIZE(DOCUMENT '<a/>'::xml AS TEXT)");
dialects.verified_stmt("SELECT XMLSERIALIZE(CONTENT '<a/>'::xml AS VARCHAR(100) INDENT)");
dialects.verified_stmt("SELECT XMLSERIALIZE(DOCUMENT '<a/>'::xml AS TEXT NO INDENT)");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY REF '<a/>')");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING '<a/>')");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY VALUE '<a/>')");

let others = all_dialects_except(|d| d.supports_xml_expressions());
others.verified_only_select("SELECT xmlelement(1)");
assert!(others
.parse_sql_statements("SELECT xmlelement(NAME foo, 'bar')")
.is_err());
assert!(others
.parse_sql_statements("SELECT xmlexists('/a' PASSING BY REF '<a/>')")
.is_err());
}

/// Regression test for the 2^N parse-time blowup in `parse_compound_expr` on
/// inputs like `IF a0.a1...aN.#`. The parse is run on a worker thread and the
/// main thread asserts that it reports back within a generous timeout. Post-fix
Expand Down
61 changes: 61 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4158,6 +4158,67 @@ fn parse_xmlparse() {
}
}

#[test]
fn parse_xmlelement() {
let statements = [
"SELECT XMLELEMENT(NAME foo, 'bar')",
"SELECT XMLELEMENT(NAME foo, 'bar'), * FROM customers",
"SELECT XMLELEMENT(NAME foo, XMLATTRIBUTES('v' AS attr), 'bar')",
r#"SELECT XMLELEMENT(NAME "foo$bar", XMLATTRIBUTES('xyz' AS "a&b"))"#,
];
for sql in statements {
pg().verified_stmt(sql);
}
}

#[test]
fn parse_xmlpi() {
let statements = [
"SELECT XMLPI(NAME php, 'echo \"hello world\";')",
"SELECT XMLPI(NAME php)",
];
for sql in statements {
pg().verified_stmt(sql);
}
}

#[test]
fn parse_xmlroot() {
let statements = [
"SELECT XMLROOT('<a/>'::xml, VERSION '1.0')",
"SELECT XMLROOT('<a/>'::xml, VERSION '1.0', STANDALONE YES)",
"SELECT XMLROOT('<a/>'::xml, VERSION NO VALUE, STANDALONE NO VALUE)",
"SELECT XMLROOT('<a/>'::xml, VERSION NO VALUE)",
];
for sql in statements {
pg().verified_stmt(sql);
}
}

#[test]
fn parse_xmlserialize() {
let statements = [
"SELECT XMLSERIALIZE(DOCUMENT '<a/>'::xml AS TEXT)",
"SELECT XMLSERIALIZE(CONTENT '<a/>'::xml AS VARCHAR(100) INDENT)",
"SELECT XMLSERIALIZE(DOCUMENT '<a/>'::xml AS TEXT NO INDENT)",
];
for sql in statements {
pg().verified_stmt(sql);
}
}

#[test]
fn parse_xmlexists() {
let statements = [
"SELECT XMLEXISTS('/a' PASSING BY REF '<a/>')",
"SELECT XMLEXISTS('/a' PASSING '<a/>')",
"SELECT XMLEXISTS('/a' PASSING BY VALUE '<a/>')",
];
for sql in statements {
pg().verified_stmt(sql);
}
}

#[test]
fn parse_xml_typed_string() {
// xml '...' should parse as a TypedString on PostgreSQL and Generic
Expand Down
Loading