Skip to content

Commit bf6c5e8

Browse files
committed
Merge upstream main
2 parents d12c24b + 7076b79 commit bf6c5e8

6 files changed

Lines changed: 126 additions & 3 deletions

File tree

src/ast/mod.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8023,6 +8023,11 @@ pub enum FunctionArgOperator {
80238023
Colon,
80248024
/// function(arg1 VALUE value1)
80258025
Value,
8026+
/// function(arg1 value1), with no operator between the name and the value,
8027+
/// as in PostgreSQL `XMLPARSE(DOCUMENT value)`
8028+
///
8029+
/// [PostgreSQL](https://www.postgresql.org/docs/current/datatype-xml.html#DATATYPE-XML-CREATING)
8030+
Space,
80268031
}
80278032

80288033
impl fmt::Display for FunctionArgOperator {
@@ -8033,6 +8038,7 @@ impl fmt::Display for FunctionArgOperator {
80338038
FunctionArgOperator::Assignment => f.write_str(":="),
80348039
FunctionArgOperator::Colon => f.write_str(":"),
80358040
FunctionArgOperator::Value => f.write_str("VALUE"),
8041+
FunctionArgOperator::Space => Ok(()),
80368042
}
80378043
}
80388044
}
@@ -8075,17 +8081,31 @@ impl fmt::Display for FunctionArg {
80758081
name,
80768082
arg,
80778083
operator,
8078-
} => write!(f, "{name} {operator} {arg}"),
8084+
} => fmt_named_function_arg(f, name, operator, arg),
80798085
FunctionArg::ExprNamed {
80808086
name,
80818087
arg,
80828088
operator,
8083-
} => write!(f, "{name} {operator} {arg}"),
8089+
} => fmt_named_function_arg(f, name, operator, arg),
80848090
FunctionArg::Unnamed(unnamed_arg) => write!(f, "{unnamed_arg}"),
80858091
}
80868092
}
80878093
}
80888094

8095+
/// `FunctionArgOperator::Space` has no token of its own, so the name and the
8096+
/// value are separated by a single space instead.
8097+
fn fmt_named_function_arg(
8098+
f: &mut fmt::Formatter,
8099+
name: &impl fmt::Display,
8100+
operator: &FunctionArgOperator,
8101+
arg: &FunctionArgExpr,
8102+
) -> fmt::Result {
8103+
match operator {
8104+
FunctionArgOperator::Space => write!(f, "{name} {arg}"),
8105+
_ => write!(f, "{name} {operator} {arg}"),
8106+
}
8107+
}
8108+
80898109
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
80908110
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
80918111
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/dialect/bigquery.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ impl Dialect for BigQueryDialect {
136136
true
137137
}
138138

139+
/// BigQuery allows a query to start with `FROM` (e.g. `FROM t`, and the
140+
/// entry form for pipe syntax, `FROM t |> ...`).
141+
///
142+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#from_queries>
143+
fn supports_from_first_select(&self) -> bool {
144+
true
145+
}
146+
139147
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
140148
fn supports_execute_immediate(&self) -> bool {
141149
true

src/parser/mod.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2531,6 +2531,23 @@ impl<'a> Parser<'a> {
25312531
})
25322532
}
25332533

2534+
/// Parse the argument list of `XMLPARSE({ DOCUMENT | CONTENT } value)`,
2535+
/// including the closing parenthesis. The mode word becomes the name of the
2536+
/// single argument, with no operator between it and the value.
2537+
fn parse_xmlparse_argument_list(&mut self) -> Result<FunctionArgumentList, ParserError> {
2538+
let arg = FunctionArg::Named {
2539+
name: self.parse_identifier()?,
2540+
arg: FunctionArgExpr::Expr(self.parse_expr()?),
2541+
operator: FunctionArgOperator::Space,
2542+
};
2543+
self.expect_token(&Token::RParen)?;
2544+
Ok(FunctionArgumentList {
2545+
duplicate_treatment: None,
2546+
args: vec![arg],
2547+
clauses: vec![],
2548+
})
2549+
}
2550+
25342551
/// Parse a function call expression named by `name` and return it as an `Expr`.
25352552
pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
25362553
self.parse_function_call(name).map(Expr::Function)
@@ -2556,7 +2573,13 @@ impl<'a> Parser<'a> {
25562573
});
25572574
}
25582575

2559-
let mut args = self.parse_function_argument_list()?;
2576+
let mut args = if self.dialect.supports_xml_expressions()
2577+
&& Self::is_simple_unquoted_object_name(&name, "xmlparse")
2578+
{
2579+
self.parse_xmlparse_argument_list()?
2580+
} else {
2581+
self.parse_function_argument_list()?
2582+
};
25602583
let mut parameters = FunctionArguments::None;
25612584
// ClickHouse aggregations support parametric functions like `HISTOGRAM(0.5, 0.6)(x, y)`
25622585
// which (0.5, 0.6) is a parameter to the function.

tests/sqlparser_bigquery.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,3 +2950,10 @@ fn test_create_snapshot_table() {
29502950
"CREATE SNAPSHOT TABLE IF NOT EXISTS dataset_id.table1 CLONE dataset_id.table2 FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR) OPTIONS(expiration_timestamp = TIMESTAMP '2025-01-01 00:00:00 UTC')",
29512951
);
29522952
}
2953+
2954+
#[test]
2955+
fn parse_from_first_select() {
2956+
bigquery().verified_stmt("FROM t");
2957+
bigquery().verified_stmt("FROM t SELECT a, b");
2958+
bigquery().verified_stmt("FROM t |> WHERE a > 1 |> SELECT a");
2959+
}

tests/sqlparser_common.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19604,6 +19604,56 @@ fn parse_aliased_function_args() {
1960419604
.is_err());
1960519605
}
1960619606

19607+
#[test]
19608+
fn parse_xmlparse() {
19609+
let dialects = all_dialects_where(|d| d.supports_xml_expressions());
19610+
19611+
for (sql, mode) in [
19612+
("SELECT xmlparse(content '<a/>')", "content"),
19613+
("SELECT xmlparse(document '<a/>')", "document"),
19614+
] {
19615+
let select = dialects.verified_only_select(sql);
19616+
match expr_from_projection(&select.projection[0]) {
19617+
Expr::Function(Function {
19618+
name,
19619+
args: FunctionArguments::List(list),
19620+
..
19621+
}) => {
19622+
assert_eq!(name.to_string(), "xmlparse");
19623+
assert_eq!(
19624+
list.args,
19625+
vec![FunctionArg::Named {
19626+
name: Ident::new(mode),
19627+
arg: FunctionArgExpr::Expr(Expr::Value(
19628+
Value::SingleQuotedString("<a/>".to_string()).into()
19629+
)),
19630+
operator: FunctionArgOperator::Space,
19631+
}]
19632+
);
19633+
}
19634+
expr => panic!("expected an XMLPARSE function call, got {expr:?}"),
19635+
}
19636+
}
19637+
19638+
// XMLPARSE needs both a mode word and a value.
19639+
assert!(dialects
19640+
.parse_sql_statements("SELECT xmlparse('<a/>')")
19641+
.is_err());
19642+
19643+
// Going through the ordinary function-call path, XMLPARSE accepts the same
19644+
// trailing clauses as any other function.
19645+
dialects.verified_stmt("SELECT xmlparse(document x) FILTER (WHERE y > 1)");
19646+
dialects.verified_stmt("SELECT xmlparse(document x) OVER (PARTITION BY y)");
19647+
19648+
// On dialects without XML support, `xmlparse` stays a regular function
19649+
// and the special `CONTENT <expr>` syntax is rejected.
19650+
let others = all_dialects_except(|d| d.supports_xml_expressions());
19651+
others.verified_only_select("SELECT xmlparse(1)");
19652+
assert!(others
19653+
.parse_sql_statements("SELECT xmlparse(content '<a/>')")
19654+
.is_err());
19655+
}
19656+
1960719657
/// Regression test for the 2^N parse-time blowup in `parse_compound_expr` on
1960819658
/// inputs like `IF a0.a1...aN.#`. The parse is run on a worker thread and the
1960919659
/// main thread asserts that it reports back within a generous timeout. Post-fix

tests/sqlparser_postgres.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4130,6 +4130,21 @@ fn parse_xmlforest_aliased_arguments() {
41304130
);
41314131
}
41324132

4133+
#[test]
4134+
fn parse_xmlparse() {
4135+
// The parser only distinguishes the two modes, so the corpus covers those
4136+
// plus a non-literal argument.
4137+
let statements = [
4138+
"SELECT XMLPARSE(CONTENT '')",
4139+
"SELECT XMLPARSE(CONTENT '<abc>x</abc>')",
4140+
"SELECT XMLPARSE(DOCUMENT '<abc>x</abc>')",
4141+
"SELECT XMLPARSE(DOCUMENT col || '</abc>')",
4142+
];
4143+
for sql in statements {
4144+
pg().verified_stmt(sql);
4145+
}
4146+
}
4147+
41334148
#[test]
41344149
fn parse_xml_typed_string() {
41354150
// xml '...' should parse as a TypedString on PostgreSQL and Generic

0 commit comments

Comments
 (0)