From bd6a6b156ee672414202400e94951aa60d8160c4 Mon Sep 17 00:00:00 2001 From: Robert Jensen Date: Sat, 8 Nov 2025 15:27:22 -0500 Subject: [PATCH 1/5] fix: parse error on unnamed arg with default syntax --- src/parser/mod.rs | 19 ++++++++++-------- tests/sqlparser_postgres.rs | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0b2158e6f..ac7f1b5ef 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5522,16 +5522,19 @@ impl<'a> Parser<'a> { // peek the next token, which if it is another type keyword, then the // first token is a name and not a type in itself. let data_type_idx = self.get_current_index(); - if let Some(next_data_type) = self.maybe_parse(|parser| parser.parse_data_type())? { - let token = self.token_at(data_type_idx); + // DEFAULT will be parsed as `DataType::Custom`, which is undesirable in this context + if !self.peek_keyword(Keyword::DEFAULT) { + if let Some(next_data_type) = self.maybe_parse(|parser| parser.parse_data_type())? { + let token = self.token_at(data_type_idx); - // We ensure that the token is a `Word` token, and not other special tokens. - if !matches!(token.token, Token::Word(_)) { - return self.expected("a name or type", token.clone()); - } + // We ensure that the token is a `Word` token, and not other special tokens. + if !matches!(token.token, Token::Word(_)) { + return self.expected("a name or type", token.clone()); + } - name = Some(Ident::new(token.to_string())); - data_type = next_data_type; + name = Some(Ident::new(token.to_string())); + data_type = next_data_type; + } } let default_expr = if self.parse_keyword(Keyword::DEFAULT) || self.consume_token(&Token::Eq) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 75d567c10..5ddee6dab 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4476,6 +4476,46 @@ fn parse_create_function_detailed() { pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION no_arg() RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN DELETE FROM my_table; END; $$"#); pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION return_table(i INTEGER) RETURNS TABLE(id UUID, is_active BOOLEAN) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT NULL::UUID, NULL::BOOLEAN; END; $$"#); } + +#[test] +fn parse_create_function_unnamed_default_syntax() { + let sql = + "CREATE FUNCTION add(INTEGER, INTEGER DEFAULT 1) RETURNS INTEGER AS 'select $1 + $2;'"; + let canonical = + "CREATE FUNCTION add(INTEGER, INTEGER = 1) RETURNS INTEGER AS 'select $1 + $2;'"; + assert_eq!( + pg_and_generic().one_statement_parses_to(sql, canonical), + Statement::CreateFunction(CreateFunction { + or_alter: false, + or_replace: false, + temporary: false, + name: ObjectName::from(vec![Ident::new("add")]), + args: Some(vec![ + OperateFunctionArg::unnamed(DataType::Integer(None)), + OperateFunctionArg { + mode: None, + name: None, + data_type: DataType::Integer(None), + default_expr: Some(Expr::Value(number("1").with_empty_span())), + }, + ]), + return_type: Some(DataType::Integer(None)), + language: None, + behavior: None, + called_on_null: None, + parallel: None, + function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( + (Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span() + ))), + if_not_exists: false, + using: None, + determinism_specifier: None, + options: None, + remote_connection: None, + }) + ); +} + #[test] fn parse_incorrect_create_function_parallel() { let sql = "CREATE FUNCTION add(INTEGER, INTEGER) RETURNS INTEGER LANGUAGE SQL PARALLEL BLAH AS 'select $1 + $2;'"; From 07c697577c3de82c046055e4636cf4123ce4f058 Mon Sep 17 00:00:00 2001 From: Robert Jensen Date: Wed, 12 Nov 2025 17:43:52 -0500 Subject: [PATCH 2/5] simplify test --- tests/sqlparser_postgres.rs | 41 +++---------------------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 5ddee6dab..3bdf6d189 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4475,44 +4475,9 @@ fn parse_create_function_detailed() { pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION increment(i INTEGER) RETURNS INTEGER LANGUAGE plpgsql AS $$ BEGIN RETURN i + 1; END; $$"#); pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION no_arg() RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN DELETE FROM my_table; END; $$"#); pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION return_table(i INTEGER) RETURNS TABLE(id UUID, is_active BOOLEAN) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT NULL::UUID, NULL::BOOLEAN; END; $$"#); -} - -#[test] -fn parse_create_function_unnamed_default_syntax() { - let sql = - "CREATE FUNCTION add(INTEGER, INTEGER DEFAULT 1) RETURNS INTEGER AS 'select $1 + $2;'"; - let canonical = - "CREATE FUNCTION add(INTEGER, INTEGER = 1) RETURNS INTEGER AS 'select $1 + $2;'"; - assert_eq!( - pg_and_generic().one_statement_parses_to(sql, canonical), - Statement::CreateFunction(CreateFunction { - or_alter: false, - or_replace: false, - temporary: false, - name: ObjectName::from(vec![Ident::new("add")]), - args: Some(vec![ - OperateFunctionArg::unnamed(DataType::Integer(None)), - OperateFunctionArg { - mode: None, - name: None, - data_type: DataType::Integer(None), - default_expr: Some(Expr::Value(number("1").with_empty_span())), - }, - ]), - return_type: Some(DataType::Integer(None)), - language: None, - behavior: None, - called_on_null: None, - parallel: None, - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span() - ))), - if_not_exists: false, - using: None, - determinism_specifier: None, - options: None, - remote_connection: None, - }) + pg_and_generic().one_statement_parses_to( + "CREATE FUNCTION add(INTEGER, INTEGER DEFAULT 1) RETURNS INTEGER AS 'select $1 + $2;'", + "CREATE FUNCTION add(INTEGER, INTEGER = 1) RETURNS INTEGER AS 'select $1 + $2;'", ); } From 9db7d864df72cdf14e787851850c78898c6b988b Mon Sep 17 00:00:00 2001 From: Robert Jensen Date: Wed, 12 Nov 2025 18:26:50 -0500 Subject: [PATCH 3/5] pull out fn for readability, move hack to maybe_parse --- src/parser/mod.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ac7f1b5ef..79440fc80 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5522,19 +5522,32 @@ impl<'a> Parser<'a> { // peek the next token, which if it is another type keyword, then the // first token is a name and not a type in itself. let data_type_idx = self.get_current_index(); - // DEFAULT will be parsed as `DataType::Custom`, which is undesirable in this context - if !self.peek_keyword(Keyword::DEFAULT) { - if let Some(next_data_type) = self.maybe_parse(|parser| parser.parse_data_type())? { - let token = self.token_at(data_type_idx); - // We ensure that the token is a `Word` token, and not other special tokens. - if !matches!(token.token, Token::Word(_)) { - return self.expected("a name or type", token.clone()); - } + // FIXME: DEFAULT will be parsed as `DataType::Custom`, which is undesirable in this context + fn parse_data_type_no_default( + parser: &mut Parser, + ) -> Result { + if parser.peek_keyword(Keyword::DEFAULT) { + // This dummy error is ignored in `maybe_parse` + parser_err!( + "The DEFAULT keyword is not a type", + parser.peek_token().span.start + ) + } else { + parser.parse_data_type() + } + } - name = Some(Ident::new(token.to_string())); - data_type = next_data_type; + if let Some(next_data_type) = self.maybe_parse(parse_data_type_no_default)? { + let token = self.token_at(data_type_idx); + + // We ensure that the token is a `Word` token, and not other special tokens. + if !matches!(token.token, Token::Word(_)) { + return self.expected("a name or type", token.clone()); } + + name = Some(Ident::new(token.to_string())); + data_type = next_data_type; } let default_expr = if self.parse_keyword(Keyword::DEFAULT) || self.consume_token(&Token::Eq) From c92a9f117c9cfb9e4f1bb8c98994fb2a5f9e676f Mon Sep 17 00:00:00 2001 From: Robert Jensen Date: Thu, 13 Nov 2025 17:56:07 -0500 Subject: [PATCH 4/5] remove FIXME --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 79440fc80..45cc9b751 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5523,7 +5523,7 @@ impl<'a> Parser<'a> { // first token is a name and not a type in itself. let data_type_idx = self.get_current_index(); - // FIXME: DEFAULT will be parsed as `DataType::Custom`, which is undesirable in this context + // DEFAULT will be parsed as `DataType::Custom`, which is undesirable in this context fn parse_data_type_no_default( parser: &mut Parser, ) -> Result { From 1e9908f674d5a671050d1ced1ac915953106f759 Mon Sep 17 00:00:00 2001 From: Robert Jensen Date: Fri, 14 Nov 2025 16:21:53 -0500 Subject: [PATCH 5/5] fix formatting --- src/parser/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 45cc9b751..1ab4626f6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5524,9 +5524,7 @@ impl<'a> Parser<'a> { let data_type_idx = self.get_current_index(); // DEFAULT will be parsed as `DataType::Custom`, which is undesirable in this context - fn parse_data_type_no_default( - parser: &mut Parser, - ) -> Result { + fn parse_data_type_no_default(parser: &mut Parser) -> Result { if parser.peek_keyword(Keyword::DEFAULT) { // This dummy error is ignored in `maybe_parse` parser_err!(