Skip to content

Commit 1cc84fe

Browse files
Keep the quoting and span of a function argument name
1 parent 7076b79 commit 1cc84fe

3 files changed

Lines changed: 166 additions & 10 deletions

File tree

src/parser/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6268,14 +6268,14 @@ impl<'a> Parser<'a> {
62686268
}
62696269

62706270
if let Some(next_data_type) = self.maybe_parse(parse_data_type_no_default)? {
6271-
let token = self.token_at(data_type_idx);
6271+
let token = self.token_at(data_type_idx).clone();
62726272

62736273
// We ensure that the token is a `Word` token, and not other special tokens.
6274-
if !matches!(token.token, Token::Word(_)) {
6275-
return self.expected("a name or type", token.clone());
6274+
match token.token {
6275+
Token::Word(word) => name = Some(word.into_ident(token.span)),
6276+
_ => return self.expected("a name or type", token),
62766277
}
62776278

6278-
name = Some(Ident::new(token.to_string()));
62796279
data_type = next_data_type;
62806280
}
62816281

@@ -6332,12 +6332,12 @@ impl<'a> Parser<'a> {
63326332
}
63336333

63346334
if let Some(next_data_type) = self.maybe_parse(parse_data_type_for_aggregate_arg)? {
6335-
let token = self.token_at(data_type_idx);
6336-
if !matches!(token.token, Token::Word(_)) {
6337-
return self.expected("a name or type", token.clone());
6335+
let token = self.token_at(data_type_idx).clone();
6336+
match token.token {
6337+
Token::Word(word) => name = Some(word.into_ident(token.span)),
6338+
_ => return self.expected("a name or type", token),
63386339
}
63396340

6340-
name = Some(Ident::new(token.to_string()));
63416341
data_type = next_data_type;
63426342
}
63436343

tests/sqlparser_mssql.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2925,3 +2925,19 @@ fn parse_mssql_money_constants() {
29252925
expr_from_projection(only(&select.projection)),
29262926
);
29272927
}
2928+
2929+
#[test]
2930+
fn parse_bracket_quoted_function_argument_name() {
2931+
let Statement::DropFunction(drop) = ms().verified_stmt("DROP FUNCTION f([Role] INT)") else {
2932+
panic!("expected a DROP FUNCTION statement");
2933+
};
2934+
assert_eq!(
2935+
drop.func_desc[0].args,
2936+
Some(vec![OperateFunctionArg {
2937+
mode: None,
2938+
name: Some(Ident::with_quote('[', "Role")),
2939+
data_type: Int(None),
2940+
default_expr: None,
2941+
}])
2942+
);
2943+
}

tests/sqlparser_postgres.rs

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ mod test_utils;
2525
use helpers::attached_token::AttachedToken;
2626
use sqlparser::ast::*;
2727
use sqlparser::dialect::{Dialect, GenericDialect, MySqlDialect, PostgreSqlDialect, SQLiteDialect};
28-
use sqlparser::parser::ParserError;
29-
use sqlparser::tokenizer::Span;
28+
use sqlparser::parser::{Parser, ParserError};
29+
use sqlparser::tokenizer::{Location, Span};
3030
use test_utils::*;
3131

3232
#[test]
@@ -9678,3 +9678,143 @@ fn parse_right_deep_join_chain() {
96789678
// NATURAL JOIN followed by a constrained join must stay left-associative.
96799679
pg().verified_stmt("SELECT * FROM t0 NATURAL JOIN t1 INNER JOIN t2 ON true");
96809680
}
9681+
9682+
#[test]
9683+
fn parse_quoted_function_argument_name() {
9684+
let statement = pg_and_generic().verified_stmt(
9685+
r#"CREATE FUNCTION is_member("Role" TEXT) RETURNS BOOLEAN LANGUAGE SQL AS 'SELECT true'"#,
9686+
);
9687+
let Statement::CreateFunction(function) = statement else {
9688+
panic!("expected a CREATE FUNCTION statement");
9689+
};
9690+
assert_eq!(
9691+
function.args,
9692+
Some(vec![OperateFunctionArg {
9693+
mode: None,
9694+
name: Some(Ident::with_quote('"', "Role")),
9695+
data_type: DataType::Text,
9696+
default_expr: None,
9697+
}])
9698+
);
9699+
9700+
// An embedded quote is doubled in the input and belongs to the name once.
9701+
let statement = pg_and_generic().verified_stmt(
9702+
r#"CREATE FUNCTION f("we""ird" INT) RETURNS BOOLEAN LANGUAGE SQL RETURN true"#,
9703+
);
9704+
let Statement::CreateFunction(function) = statement else {
9705+
panic!("expected a CREATE FUNCTION statement");
9706+
};
9707+
assert_eq!(
9708+
function.args,
9709+
Some(vec![OperateFunctionArg {
9710+
mode: None,
9711+
name: Some(Ident::with_quote('"', r#"we"ird"#)),
9712+
data_type: DataType::Int(None),
9713+
default_expr: None,
9714+
}])
9715+
);
9716+
9717+
// A name outside ASCII is quoted for the same reason and survives the same way.
9718+
pg_and_generic().verified_stmt(
9719+
r#"CREATE FUNCTION f("Rôle" TEXT) RETURNS BOOLEAN LANGUAGE SQL RETURN true"#,
9720+
);
9721+
}
9722+
9723+
#[test]
9724+
fn parse_quoted_function_argument_name_span() {
9725+
let sql =
9726+
r#"CREATE FUNCTION is_member("Role" TEXT) RETURNS BOOLEAN LANGUAGE SQL AS 'SELECT true'"#;
9727+
// Parsed directly rather than through the test helpers, which tokenize
9728+
// without locations.
9729+
let mut statements = Parser::parse_sql(&PostgreSqlDialect {}, sql).unwrap();
9730+
let Some(Statement::CreateFunction(function)) = statements.pop() else {
9731+
panic!("expected a CREATE FUNCTION statement");
9732+
};
9733+
let name = function.args.as_ref().unwrap()[0].name.as_ref().unwrap();
9734+
assert_eq!(
9735+
name.span,
9736+
Span::new(Location::new(1, 27), Location::new(1, 33)),
9737+
"the span covers the quoted name in the input"
9738+
);
9739+
}
9740+
9741+
#[test]
9742+
fn parse_function_argument_modes_and_defaults_keep_quoted_names() {
9743+
let sql = r#"CREATE FUNCTION f(IN "A" INT = 1, OUT "B" TEXT, INOUT "C" BOOLEAN, VARIADIC "D" INT[]) RETURNS INT LANGUAGE SQL AS 'x'"#;
9744+
let Statement::CreateFunction(function) = pg_and_generic().verified_stmt(sql) else {
9745+
panic!("expected a CREATE FUNCTION statement");
9746+
};
9747+
assert_eq!(
9748+
function.args,
9749+
Some(vec![
9750+
OperateFunctionArg {
9751+
mode: Some(ArgMode::In),
9752+
name: Some(Ident::with_quote('"', "A")),
9753+
data_type: DataType::Int(None),
9754+
default_expr: Some(Expr::Value(
9755+
Value::Number("1".parse().unwrap(), false).with_empty_span()
9756+
)),
9757+
},
9758+
OperateFunctionArg {
9759+
mode: Some(ArgMode::Out),
9760+
name: Some(Ident::with_quote('"', "B")),
9761+
data_type: DataType::Text,
9762+
default_expr: None,
9763+
},
9764+
OperateFunctionArg {
9765+
mode: Some(ArgMode::InOut),
9766+
name: Some(Ident::with_quote('"', "C")),
9767+
data_type: DataType::Boolean,
9768+
default_expr: None,
9769+
},
9770+
OperateFunctionArg {
9771+
mode: Some(ArgMode::Variadic),
9772+
name: Some(Ident::with_quote('"', "D")),
9773+
data_type: DataType::Array(ArrayElemTypeDef::SquareBracket(
9774+
Box::new(DataType::Int(None)),
9775+
None
9776+
)),
9777+
default_expr: None,
9778+
},
9779+
])
9780+
);
9781+
9782+
// The `DEFAULT` spelling of the same argument list renders as `=`.
9783+
pg_and_generic().one_statement_parses_to(
9784+
r#"CREATE FUNCTION f("A" INT DEFAULT 1) RETURNS INT LANGUAGE SQL AS 'x'"#,
9785+
r#"CREATE FUNCTION f("A" INT = 1) RETURNS INT LANGUAGE SQL AS 'x'"#,
9786+
);
9787+
}
9788+
9789+
#[test]
9790+
fn parse_quoted_argument_names_in_function_signatures() {
9791+
pg_and_generic().verified_stmt(r#"DROP FUNCTION f("Role" TEXT)"#);
9792+
pg_and_generic().verified_stmt(r#"DROP PROCEDURE p("Role" TEXT)"#);
9793+
pg_and_generic().verified_stmt(r#"ALTER FUNCTION f("Role" TEXT) RENAME TO g"#);
9794+
pg_and_generic().verified_stmt(r#"ALTER AGGREGATE my_agg("Role" TEXT) RENAME TO other_agg"#);
9795+
9796+
// An aggregate's `ORDER BY` arguments are parsed by the same reader as its
9797+
// direct ones.
9798+
let sql = r#"ALTER AGGREGATE my_agg("A" INT ORDER BY "B" INT) OWNER TO some_role"#;
9799+
let Statement::AlterFunction(alter) = pg_and_generic().verified_stmt(sql) else {
9800+
panic!("expected an ALTER AGGREGATE statement");
9801+
};
9802+
assert_eq!(
9803+
alter.function.args,
9804+
Some(vec![OperateFunctionArg {
9805+
mode: None,
9806+
name: Some(Ident::with_quote('"', "A")),
9807+
data_type: DataType::Int(None),
9808+
default_expr: None,
9809+
}])
9810+
);
9811+
assert_eq!(
9812+
alter.aggregate_order_by,
9813+
Some(vec![OperateFunctionArg {
9814+
mode: None,
9815+
name: Some(Ident::with_quote('"', "B")),
9816+
data_type: DataType::Int(None),
9817+
default_expr: None,
9818+
}])
9819+
);
9820+
}

0 commit comments

Comments
 (0)