Skip to content

Commit e358745

Browse files
PostgreSQL: Support the VARIADIC function call argument marker
`pg_get_viewdef` writes the marker into stock system views, so introspecting `pg_catalog` failed on `pg_publication_tables`: SELECT concat(VARIADIC a) -- Expected: ), found: a at Line: 1, Column: 24 The marker is represented additively as `FunctionArg::Variadic`, which wraps any argument so the named form `f(VARIADIC a => ARRAY[1])` that PostgreSQL's grammar accepts is preserved too. Parsing is gated on the new `Dialect::supports_variadic_function_argument`, enabled for PostgreSQL and the generic dialect. PostgreSQL reserves the keyword, so there the marker is unambiguous. Where it is unreserved, the ordinary reading is parsed first and kept whenever the argument stands on its own, leaving `f(variadic)` and `f(variadic + 1)` untouched. A clause keyword only ends that reading when its own syntax follows, so `f(VARIADIC separator)` still marks the operand while `array_agg(variadic ORDER BY x)` does not. As in PostgreSQL, the marker is rejected on a wildcard operand, in combination with `DISTINCT` or `ALL`, and on any but the final argument. The last check runs in the enclosing argument-list parsers, so the comma before a table function's `SETTINGS` clause is not read as an argument.
1 parent f354ee2 commit e358745

7 files changed

Lines changed: 351 additions & 5 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8088,6 +8088,14 @@ pub enum FunctionArg {
80888088
},
80898089
/// An unnamed argument (positional), given by expression or wildcard.
80908090
Unnamed(FunctionArgExpr),
8091+
/// An argument carrying PostgreSQL's `VARIADIC` marker, e.g.
8092+
/// `concat(VARIADIC ARRAY['a', 'b'])`. Only the final argument of a call
8093+
/// may carry it.
8094+
///
8095+
/// Enabled when `Dialect::supports_variadic_function_argument` returns `true`.
8096+
///
8097+
/// [PostgreSQL](https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS)
8098+
Variadic(Box<FunctionArg>),
80918099
}
80928100

80938101
impl fmt::Display for FunctionArg {
@@ -8104,6 +8112,7 @@ impl fmt::Display for FunctionArg {
81048112
operator,
81058113
} => fmt_named_function_arg(f, name, operator, arg),
81068114
FunctionArg::Unnamed(unnamed_arg) => write!(f, "{unnamed_arg}"),
8115+
FunctionArg::Variadic(arg) => write!(f, "VARIADIC {arg}"),
81078116
}
81088117
}
81098118
}

‎src/ast/spans.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,6 +2194,7 @@ impl Spanned for FunctionArg {
21942194
arg,
21952195
operator: _,
21962196
} => name.span().union(&arg.span()),
2197+
FunctionArg::Variadic(arg) => arg.span(),
21972198
}
21982199
}
21992200
}

‎src/dialect/generic.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,8 @@ impl Dialect for GenericDialect {
320320
fn supports_aliased_function_args(&self) -> bool {
321321
true
322322
}
323+
324+
fn supports_variadic_function_argument(&self) -> bool {
325+
true
326+
}
323327
}

‎src/dialect/mod.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,6 +1852,16 @@ pub trait Dialect: Debug + Any {
18521852
false
18531853
}
18541854

1855+
/// Returns true if the dialect supports the `VARIADIC` marker on the final
1856+
/// argument of a function call, e.g. `concat(VARIADIC ARRAY['a', 'b'])`.
1857+
///
1858+
/// When this returns false, `VARIADIC` is treated as a regular identifier.
1859+
///
1860+
/// [PostgreSQL](https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS)
1861+
fn supports_variadic_function_argument(&self) -> bool {
1862+
false
1863+
}
1864+
18551865
/// Returns true if the dialect supports `USING <format>` in `CREATE TABLE`.
18561866
///
18571867
/// Example:

‎src/dialect/postgresql.rs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,12 @@ impl Dialect for PostgreSqlDialect {
9898
}
9999

100100
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
101-
if matches!(kw, Keyword::INTERVAL) {
102-
false
103-
} else {
104-
RESERVED_FOR_IDENTIFIER.contains(&kw)
101+
match kw {
102+
Keyword::INTERVAL => false,
103+
// PostgreSQL reserves `VARIADIC`, so inside a call it always
104+
// introduces the argument marker rather than a column of that name.
105+
Keyword::VARIADIC => true,
106+
_ => RESERVED_FOR_IDENTIFIER.contains(&kw),
105107
}
106108
}
107109

@@ -361,6 +363,10 @@ impl Dialect for PostgreSqlDialect {
361363
true
362364
}
363365

366+
fn supports_variadic_function_argument(&self) -> bool {
367+
true
368+
}
369+
364370
/// Postgres supports query optimizer hints via the `pg_hint_plan` extension,
365371
/// using the same comment-prefixed-with-`+` syntax as MySQL and Oracle.
366372
///

‎src/parser/mod.rs‎

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18828,8 +18828,100 @@ impl<'a> Parser<'a> {
1882818828
}
1882918829
}
1883018830

18831-
/// Parse a single function argument, handling named and unnamed variants.
18831+
/// Parse a single function argument, handling the `VARIADIC` marker as well
18832+
/// as named and unnamed variants.
18833+
///
18834+
/// Dialects that reserve `VARIADIC`, such as PostgreSQL, always mean the
18835+
/// marker. Where the keyword is unreserved, `f(variadic)` passes a column
18836+
/// of that name instead, so the ordinary reading is parsed first and kept
18837+
/// whenever it stands as an argument on its own; `f(VARIADIC a)` is left as
18838+
/// the only reading that takes the marker.
1883218839
pub fn parse_function_args(&mut self) -> Result<FunctionArg, ParserError> {
18840+
if self.dialect.supports_variadic_function_argument()
18841+
&& self.peek_keyword(Keyword::VARIADIC)
18842+
{
18843+
if !self.dialect.is_reserved_for_identifier(Keyword::VARIADIC) {
18844+
let plain = self.maybe_parse(|p| {
18845+
let arg = p.parse_plain_function_args()?;
18846+
if p.peek_ends_function_arg() {
18847+
Ok(arg)
18848+
} else {
18849+
p.expected_ref("end of the function argument", p.peek_token_ref())
18850+
}
18851+
})?;
18852+
if let Some(arg) = plain {
18853+
return Ok(arg);
18854+
}
18855+
}
18856+
18857+
let loc = self.peek_token_ref().span.start;
18858+
self.expect_keyword(Keyword::VARIADIC)?;
18859+
let arg = self.parse_plain_function_args()?;
18860+
// PostgreSQL's VARIADIC productions require expression operands,
18861+
// so no wildcard argument form is valid.
18862+
let carries_wildcard = match &arg {
18863+
FunctionArg::Unnamed(arg)
18864+
| FunctionArg::Named { arg, .. }
18865+
| FunctionArg::ExprNamed { arg, .. } => !matches!(arg, FunctionArgExpr::Expr(_)),
18866+
FunctionArg::Variadic(_) => false,
18867+
};
18868+
if carries_wildcard {
18869+
return parser_err!("A VARIADIC argument cannot be a wildcard", loc);
18870+
}
18871+
return Ok(FunctionArg::Variadic(Box::new(arg)));
18872+
}
18873+
self.parse_plain_function_args()
18874+
}
18875+
18876+
/// Returns true when the next token can follow a complete function
18877+
/// argument: the argument separator, the end of the list, or a complete
18878+
/// clause that [`Parser::parse_function_argument_list`] accepts after the
18879+
/// arguments. A clause keyword whose own syntax does not follow is an
18880+
/// operand instead, as in `f(VARIADIC separator)`.
18881+
fn peek_ends_function_arg(&self) -> bool {
18882+
let word = match &self.peek_token_ref().token {
18883+
Token::Comma | Token::RParen => return true,
18884+
Token::Word(word) => word,
18885+
_ => return false,
18886+
};
18887+
let next = &self.peek_nth_token_ref(1).token;
18888+
let next_is = |keyword| matches!(next, Token::Word(next) if next.keyword == keyword);
18889+
match word.keyword {
18890+
Keyword::ORDER => next_is(Keyword::BY),
18891+
Keyword::IGNORE | Keyword::RESPECT => next_is(Keyword::NULLS),
18892+
// `ON OVERFLOW`, and the `ABSENT ON NULL` / `NULL ON NULL` clauses.
18893+
Keyword::ON => next_is(Keyword::OVERFLOW),
18894+
Keyword::ABSENT | Keyword::NULL => next_is(Keyword::ON),
18895+
Keyword::HAVING => next_is(Keyword::MIN) || next_is(Keyword::MAX),
18896+
// These take an expression, a value or a type of their own.
18897+
Keyword::LIMIT | Keyword::RETURNING | Keyword::SEPARATOR | Keyword::WHERE => {
18898+
!matches!(next, Token::Comma | Token::RParen)
18899+
}
18900+
_ => false,
18901+
}
18902+
}
18903+
18904+
/// Rejects a `VARIADIC` marker on any but the final argument, as
18905+
/// PostgreSQL's grammar carries it on the last one only.
18906+
fn verify_variadic_argument_is_last(args: &[FunctionArg]) -> Result<(), ParserError> {
18907+
let Some((_, preceding)) = args.split_last() else {
18908+
return Ok(());
18909+
};
18910+
match preceding
18911+
.iter()
18912+
.find(|arg| matches!(arg, FunctionArg::Variadic(_)))
18913+
{
18914+
Some(arg) => parser_err!(
18915+
"A VARIADIC argument must be the last function argument",
18916+
arg.span().start
18917+
),
18918+
None => Ok(()),
18919+
}
18920+
}
18921+
18922+
/// Parse a single function argument carrying no `VARIADIC` marker,
18923+
/// handling named and unnamed variants.
18924+
fn parse_plain_function_args(&mut self) -> Result<FunctionArg, ParserError> {
1883318925
// Parse the argument expression once, then check for a named-arg
1883418926
// operator. Parsing it speculatively and re-parsing on the unnamed
1883518927
// path is O(2^depth) on nested calls like `CAST(CASE (CAST(CASE (…`.
@@ -18954,6 +19046,7 @@ impl<'a> Parser<'a> {
1895419046
Ok(vec![])
1895519047
} else {
1895619048
let args = self.parse_comma_separated(Parser::parse_function_args)?;
19049+
Self::verify_variadic_argument_is_last(&args)?;
1895719050
self.expect_token(&Token::RParen)?;
1895819051
Ok(args)
1895919052
}
@@ -18976,6 +19069,7 @@ impl<'a> Parser<'a> {
1897619069
break None;
1897719070
}
1897819071
};
19072+
Self::verify_variadic_argument_is_last(&args)?;
1897919073
self.expect_token(&Token::RParen)?;
1898019074
Ok(TableFunctionArgs { args, settings })
1898119075
}
@@ -19011,8 +19105,18 @@ impl<'a> Parser<'a> {
1901119105
});
1901219106
}
1901319107

19108+
let loc = self.peek_token_ref().span.start;
1901419109
let duplicate_treatment = self.parse_duplicate_treatment()?;
1901519110
let args = self.parse_comma_separated(Parser::parse_function_args)?;
19111+
Self::verify_variadic_argument_is_last(&args)?;
19112+
19113+
// PostgreSQL pairs the marker with neither `DISTINCT` nor `ALL`.
19114+
if duplicate_treatment.is_some() && matches!(args.last(), Some(FunctionArg::Variadic(_))) {
19115+
return parser_err!(
19116+
"A VARIADIC argument cannot be combined with DISTINCT or ALL",
19117+
loc
19118+
);
19119+
}
1901619120

1901719121
if self.parse_keyword(Keyword::WHERE) {
1901819122
clauses.push(FunctionArgumentClause::Where(self.parse_expr()?));

0 commit comments

Comments
 (0)