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
9 changes: 9 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8116,6 +8116,14 @@ pub enum FunctionArg {
},
/// An unnamed argument (positional), given by expression or wildcard.
Unnamed(FunctionArgExpr),
/// An argument carrying PostgreSQL's `VARIADIC` marker, e.g.
/// `concat(VARIADIC ARRAY['a', 'b'])`. Only the final argument of a call
/// may carry it.
///
/// Enabled when `Dialect::supports_variadic_function_argument` returns `true`.
///
/// [PostgreSQL](https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS)
Variadic(Box<FunctionArg>),
}

impl fmt::Display for FunctionArg {
Expand All @@ -8132,6 +8140,7 @@ impl fmt::Display for FunctionArg {
operator,
} => fmt_named_function_arg(f, name, operator, arg),
FunctionArg::Unnamed(unnamed_arg) => write!(f, "{unnamed_arg}"),
FunctionArg::Variadic(arg) => write!(f, "VARIADIC {arg}"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2209,6 +2209,7 @@ impl Spanned for FunctionArg {
arg,
operator: _,
} => name.span().union(&arg.span()),
FunctionArg::Variadic(arg) => arg.span(),

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 span starts at the operand, so for SELECT f(VARIADIC a, b) the "must be the last function argument" error points at a rather than at VARIADIC; capturing the keyword's span (or using self.peek_token_ref().span.start in the position check) would put it on the marker.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it, but span really is a mess of its own and we'll have to handle it some time after the tokenizer refactoring. Thanks for pointing this one out.

}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ impl Dialect for GenericDialect {
true
}

fn supports_variadic_function_argument(&self) -> bool {
true
}

fn supports_alter_column_position(&self) -> bool {
true
}
Expand Down
10 changes: 10 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,16 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports the `VARIADIC` marker on the final
/// argument of a function call, e.g. `concat(VARIADIC ARRAY['a', 'b'])`.
///
/// When this returns false, `VARIADIC` is treated as a regular identifier.
///
/// [PostgreSQL](https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS)
fn supports_variadic_function_argument(&self) -> bool {
false
}

/// Returns true if the dialect supports `USING <format>` in `CREATE TABLE`.
///
/// Example:
Expand Down
14 changes: 10 additions & 4 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,12 @@ impl Dialect for PostgreSqlDialect {
}

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

Expand Down Expand Up @@ -541,6 +543,10 @@ impl Dialect for PostgreSqlDialect {
true
}

fn supports_variadic_function_argument(&self) -> bool {
true
}

/// Postgres supports query optimizer hints via the `pg_hint_plan` extension,
/// using the same comment-prefixed-with-`+` syntax as MySQL and Oracle.
///
Expand Down
137 changes: 133 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19229,8 +19229,118 @@ impl<'a> Parser<'a> {
}
}

/// Parse a single function argument, handling named and unnamed variants.
/// Parse a single function argument, handling the `VARIADIC` marker as well
/// as named and unnamed variants.
///
/// Dialects that reserve `VARIADIC`, such as PostgreSQL, always mean the
/// marker. Where the keyword is unreserved, `f(variadic)` passes a column
/// of that name instead, so the ordinary reading is parsed first and kept
/// whenever it stands alone as an argument, and `f(VARIADIC a)` is left as
/// the only reading that takes the marker.
pub fn parse_function_args(&mut self) -> Result<FunctionArg, ParserError> {
if self.dialect.supports_variadic_function_argument()
&& self.peek_keyword(Keyword::VARIADIC)
{
if !self.dialect.is_reserved_for_identifier(Keyword::VARIADIC) {
let plain = self.maybe_parse(|p| {
let arg = p.parse_plain_function_args()?;
if p.peek_ends_function_arg() {
Ok(arg)
} else {
p.expected_ref("end of the function argument", p.peek_token_ref())
}
})?;
if let Some(arg) = plain {
return Ok(arg);
}
}

let loc = self.peek_token_ref().span.start;
self.advance_token();
let arg = self.parse_plain_function_args()?;
// PostgreSQL's VARIADIC productions require expression operands,
// so no wildcard argument form is valid.
let carries_wildcard = matches!(
&arg,
FunctionArg::Unnamed(arg)
| FunctionArg::Named { arg, .. }
| FunctionArg::ExprNamed { arg, .. }
if !matches!(arg, FunctionArgExpr::Expr(_))
);
if carries_wildcard {
return parser_err!("A VARIADIC argument cannot be a wildcard", loc);
}
return Ok(FunctionArg::Variadic(Box::new(arg)));
}
self.parse_plain_function_args()
}

fn parse_function_arg_in_list(
&mut self,
first_variadic_location: &mut Option<Location>,
) -> Result<FunctionArg, ParserError> {
let loc = self.peek_token_ref().span.start;
let arg = self.parse_function_args()?;
if matches!(arg, FunctionArg::Variadic(_)) && first_variadic_location.is_none() {
*first_variadic_location = Some(loc);
}
Ok(arg)
}

/// Returns true when the next token can follow a complete function
/// argument, for example the argument separator, the end of the list, or a
/// clause that [`Parser::parse_function_argument_list`] accepts after the
/// arguments. A clause keyword whose own syntax does not follow is an
/// operand instead, as in `f(VARIADIC separator)`.
fn peek_ends_function_arg(&self) -> bool {
let word = match &self.peek_token_ref().token {
Token::Comma | Token::RParen => return true,
Token::Word(word) => word,
_ => return false,
};
let next = &self.peek_nth_token_ref(1).token;
let next_is = |keyword| matches!(next, Token::Word(next) if next.keyword == keyword);
match word.keyword {
Keyword::ORDER => next_is(Keyword::BY),
Keyword::IGNORE | Keyword::RESPECT => next_is(Keyword::NULLS),
// `ON OVERFLOW`, and the `ABSENT ON NULL` / `NULL ON NULL` clauses.
Keyword::ON => next_is(Keyword::OVERFLOW),
Keyword::ABSENT | Keyword::NULL => next_is(Keyword::ON),
Keyword::HAVING => next_is(Keyword::MIN) || next_is(Keyword::MAX),
// These take an expression, a value or a type of their own.
Keyword::LIMIT | Keyword::RETURNING | Keyword::SEPARATOR | Keyword::WHERE => {
!matches!(next, Token::Comma | Token::RParen)
}
_ => false,
}
}

/// Rejects non-final `VARIADIC` arguments at the marker's source location.
fn verify_variadic_argument_is_last(
args: &[FunctionArg],
first_variadic_location: Option<Location>,
) -> Result<(), ParserError> {
let Some((_, preceding)) = args.split_last() else {
return Ok(());
};
match first_variadic_location {
Some(loc)
if preceding
.iter()
.any(|arg| matches!(arg, FunctionArg::Variadic(_))) =>
{
parser_err!(
"A VARIADIC argument must be the last function argument",
loc
)
}
_ => Ok(()),
}
}

/// Parse a single function argument carrying no `VARIADIC` marker,
/// handling named and unnamed variants.
fn parse_plain_function_args(&mut self) -> Result<FunctionArg, ParserError> {
// Parse the argument expression once, then check for a named-arg
// operator. Parsing it speculatively and re-parsing on the unnamed
// path is O(2^depth) on nested calls like `CAST(CASE (CAST(CASE (…`.
Expand Down Expand Up @@ -19354,7 +19464,11 @@ impl<'a> Parser<'a> {
if self.consume_token(&Token::RParen) {
Ok(vec![])
} else {
let args = self.parse_comma_separated(Parser::parse_function_args)?;
let mut first_variadic_location = None;
let args = self.parse_comma_separated(|p| {
p.parse_function_arg_in_list(&mut first_variadic_location)
})?;
Self::verify_variadic_argument_is_last(&args, first_variadic_location)?;
self.expect_token(&Token::RParen)?;
Ok(args)
}
Expand All @@ -19368,15 +19482,17 @@ impl<'a> Parser<'a> {
});
}
let mut args = vec![];
let mut first_variadic_location = None;
let settings = loop {
if let Some(settings) = self.parse_settings()? {
break Some(settings);
}
args.push(self.parse_function_args()?);
args.push(self.parse_function_arg_in_list(&mut first_variadic_location)?);
if self.is_parse_comma_separated_end() {
break None;
}
};
Self::verify_variadic_argument_is_last(&args, first_variadic_location)?;
self.expect_token(&Token::RParen)?;
Ok(TableFunctionArgs { args, settings })
}
Expand Down Expand Up @@ -19412,8 +19528,21 @@ impl<'a> Parser<'a> {
});
}

let loc = self.peek_token_ref().span.start;
let duplicate_treatment = self.parse_duplicate_treatment()?;
let args = self.parse_comma_separated(Parser::parse_function_args)?;
let mut first_variadic_location = None;
let args = self.parse_comma_separated(|p| {
p.parse_function_arg_in_list(&mut first_variadic_location)
})?;
Self::verify_variadic_argument_is_last(&args, first_variadic_location)?;

// PostgreSQL pairs the marker with neither `DISTINCT` nor `ALL`.
if duplicate_treatment.is_some() && matches!(args.last(), Some(FunctionArg::Variadic(_))) {
return parser_err!(
"A VARIADIC argument cannot be combined with DISTINCT or ALL",
loc
);
}

if self.parse_keyword(Keyword::WHERE) {
clauses.push(FunctionArgumentClause::Where(self.parse_expr()?));
Expand Down
Loading
Loading