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
1 change: 1 addition & 0 deletions fuzz/fuzz_seeds/generic_2484_variadic_as_identifier.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT f(variadic), f(variadic + 1) FROM t
6 changes: 6 additions & 0 deletions fuzz/fuzz_seeds/postgres_2484_variadic_function_argument.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SELECT concat(VARIADIC ARRAY[1, 2]);
SELECT concat_ws('-', VARIADIC ARRAY['a']);
SELECT * FROM pg_get_publication_tables(VARIADIC ARRAY['pub'::TEXT]) AS gpt;
SELECT f(VARIADIC a => ARRAY[1]);
SELECT concat(VARIADIC -a);
SELECT f(VARIADIC separator)
3 changes: 3 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8138,6 +8138,8 @@ pub enum FunctionArg {
},
/// An unnamed argument (positional), given by expression or wildcard.
Unnamed(FunctionArgExpr),
/// A final argument prefixed with PostgreSQL's [`VARIADIC`](https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS) marker.
Variadic(Box<FunctionArg>),
}

impl fmt::Display for FunctionArg {
Expand All @@ -8154,6 +8156,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 @@ -2215,6 +2215,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 @@ -357,6 +357,10 @@ impl Dialect for GenericDialect {
true
}

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

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

/// Whether the dialect supports `VARIADIC` on the final function-call argument.
fn supports_variadic_function_argument(&self) -> bool {
false
}

/// Returns true if the dialect supports `USING <format>` in `CREATE TABLE`.
///
/// Example:
Expand Down
12 changes: 8 additions & 4 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,10 @@ 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,
Keyword::VARIADIC => true,
_ => RESERVED_FOR_IDENTIFIER.contains(&kw),
}
}

Expand Down Expand Up @@ -553,6 +553,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
96 changes: 92 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19494,8 +19494,81 @@ impl<'a> Parser<'a> {
}
}

/// Parse a single function argument, handling named and unnamed variants.
/// Parse a named, positional, or `VARIADIC` function argument.
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 index = self.index;
if let Some(arg) = self.maybe_parse(|p| p.parse_plain_function_args())? {
if self.peek_ends_function_arg() {
return Ok(arg);
}
self.index = index;
}
}

let loc = self.peek_token_ref().span.start;
self.advance_token();
let arg = self.parse_plain_function_args()?;
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 let Some(loc) = *first_variadic_location {
return parser_err!(
"A VARIADIC argument must be the last function argument",
loc
);
}
if matches!(arg, FunctionArg::Variadic(_)) {
*first_variadic_location = Some(loc);
}
Ok(arg)
}

/// Whether the next tokens end a plain argument before a delimiter or function clause.
fn peek_ends_function_arg(&self) -> bool {
let word = match &self.peek_token_ref().token {
Token::Comma | Token::RParen | Token::EOF => 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),
Keyword::ON => next_is(Keyword::OVERFLOW),
Keyword::ABSENT | Keyword::NULL => next_is(Keyword::ON),
Keyword::HAVING => next_is(Keyword::MIN) || next_is(Keyword::MAX),
Keyword::LIMIT | Keyword::RETURNING | Keyword::SEPARATOR | Keyword::WHERE => {
!matches!(next, Token::Comma | Token::RParen)
}
_ => false,
}
}

/// Parse a named or positional argument without a `VARIADIC` marker.
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 @@ -19619,7 +19692,10 @@ 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.expect_token(&Token::RParen)?;
Ok(args)
}
Expand All @@ -19633,11 +19709,12 @@ 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;
}
Expand Down Expand Up @@ -19677,16 +19754,27 @@ impl<'a> Parser<'a> {
});
}

let loc = self.peek_token_ref().span.start;
let duplicate_treatment = self.parse_duplicate_treatment()?;
let mut first_variadic_location = None;
let args = if duplicate_treatment != Some(DuplicateTreatment::Distinct)
&& self.dialect.supports_aggregate_order_by_with_empty_args()
&& self.peek_keywords(&[Keyword::ORDER, Keyword::BY])
{
vec![]
} else {
self.parse_comma_separated(Parser::parse_function_args)?
self.parse_comma_separated(|p| {
p.parse_function_arg_in_list(&mut first_variadic_location)
})?
};

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