From d173e82c3e490e26b4bb1bb1a4cf1c8b5f1c6fdb Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 15:11:01 +0100 Subject: [PATCH 1/8] Enhance CREATE FUNCTION parsing to support C functions with multiple arguments and flexible attribute order --- src/ast/ddl.rs | 10 ++++++++- src/parser/mod.rs | 35 ++++++++++++++++++++--------- tests/sqlparser_postgres.rs | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index fd481213f..1860cd1d6 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2768,7 +2768,15 @@ impl fmt::Display for CreateFunction { write!(f, " REMOTE WITH CONNECTION {remote_connection}")?; } if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body { - write!(f, " AS {function_body}")?; + write!(f, " AS ")?; + // Special handling for tuple expressions to format without parentheses + // PostgreSQL C functions use: AS 'obj_file', 'link_symbol' + // rather than: AS ('obj_file', 'link_symbol') + if let Expr::Tuple(exprs) = function_body { + write!(f, "{}", display_comma_separated(exprs))?; + } else { + write!(f, "{function_body}")?; + } } if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body { write!(f, " RETURN {function_body}")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9a01e510b..bd0ab0329 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10219,17 +10219,32 @@ impl<'a> Parser<'a> { /// Parse the body of a `CREATE FUNCTION` specified as a string. /// e.g. `CREATE FUNCTION ... AS $$ body $$`. fn parse_create_function_body_string(&mut self) -> Result { - let peek_token = self.peek_token(); - let span = peek_token.span; - match peek_token.token { - Token::DollarQuotedString(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) => - { - self.next_token(); - Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span))) + // Helper closure to parse a single string value (quoted or dollar-quoted) + let parse_string_expr = |parser: &mut Parser| -> Result { + let peek_token = parser.peek_token(); + let span = peek_token.span; + match peek_token.token { + Token::DollarQuotedString(s) if dialect_of!(parser is PostgreSqlDialect | GenericDialect) => + { + parser.next_token(); + Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span))) + } + _ => Ok(Expr::Value( + Value::SingleQuotedString(parser.parse_literal_string()?).with_span(span), + )), } - _ => Ok(Expr::Value( - Value::SingleQuotedString(self.parse_literal_string()?).with_span(span), - )), + }; + + let first_expr = parse_string_expr(self)?; + + // Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol') + // This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol' + if self.consume_token(&Token::Comma) { + let mut exprs = vec![first_expr]; + exprs.extend(self.parse_comma_separated(parse_string_expr)?); + Ok(Expr::Tuple(exprs)) + } else { + Ok(first_expr) } } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 9ba0fb978..4cdb7355f 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4482,6 +4482,50 @@ fn parse_incorrect_create_function_parallel() { assert!(pg().parse_sql_statements(sql).is_err()); } +#[test] +fn parse_create_function_c_with_module_pathname() { + let sql = "CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'"; + assert_eq!( + pg_and_generic().verified_stmt(sql), + Statement::CreateFunction(CreateFunction { + or_alter: false, + or_replace: false, + temporary: false, + name: ObjectName::from(vec![Ident::new("cas_in")]), + args: Some(vec![OperateFunctionArg::with_name( + "input", + DataType::Custom(ObjectName::from(vec![Ident::new("cstring")]), vec![]), + ),]), + return_type: Some(DataType::Custom( + ObjectName::from(vec![Ident::new("cas")]), + vec![] + )), + language: Some("c".into()), + behavior: Some(FunctionBehavior::Immutable), + called_on_null: None, + parallel: Some(FunctionParallel::Safe), + function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Tuple(vec![ + Expr::Value( + (Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span() + ), + Expr::Value((Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span()), + ]))), + if_not_exists: false, + using: None, + determinism_specifier: None, + options: None, + remote_connection: None, + }) + ); + + // Test that attribute order flexibility works (IMMUTABLE before LANGUAGE) + let sql_alt_order = "CREATE FUNCTION cas_in(input cstring) RETURNS cas IMMUTABLE PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', 'cas_in_wrapper'"; + pg_and_generic().one_statement_parses_to( + sql_alt_order, + "CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'" + ); +} + #[test] fn parse_drop_function() { let sql = "DROP FUNCTION IF EXISTS test_func"; From b2acc60fcf5b23954ad5ff79b669bf98d096f8d5 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 9 Nov 2025 15:19:31 +0100 Subject: [PATCH 2/8] Resolved code smell --- src/ast/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 176d36545..95a98a18d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2787,10 +2787,11 @@ impl fmt::Display for Declare { } /// Sql options of a `CREATE TABLE` statement. -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum CreateTableOptions { + #[default] None, /// Options specified using the `WITH` keyword. /// e.g. `WITH (description = "123")` @@ -2819,12 +2820,6 @@ pub enum CreateTableOptions { TableProperties(Vec), } -impl Default for CreateTableOptions { - fn default() -> Self { - Self::None - } -} - impl fmt::Display for CreateTableOptions { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { From e856d898eeb7a79b6427fb88ce4e5314b7e8f44e Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Fri, 14 Nov 2025 15:56:51 +0100 Subject: [PATCH 3/8] Update src/parser/mod.rs Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 847aea25a..dcef6754b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10245,9 +10245,10 @@ impl<'a> Parser<'a> { // Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol') // This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol' if self.consume_token(&Token::Comma) { - let mut exprs = vec![first_expr]; - exprs.extend(self.parse_comma_separated(parse_string_expr)?); - Ok(Expr::Tuple(exprs)) + Ok(Expr::Tuple(vec![ + first_expr, + self.parse_comma_separated(parse_string_expr)? + ])) } else { Ok(first_expr) } From 716efdc85d0691168ee04b6ebe0e2b8f21b44fda Mon Sep 17 00:00:00 2001 From: Luca Date: Fri, 14 Nov 2025 15:59:29 +0100 Subject: [PATCH 4/8] Reverted change --- src/parser/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b27b7e844..02baf28a4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10245,10 +10245,9 @@ impl<'a> Parser<'a> { // Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol') // This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol' if self.consume_token(&Token::Comma) { - Ok(Expr::Tuple(vec![ - first_expr, - self.parse_comma_separated(parse_string_expr)? - ])) + let mut exprs = vec![first_expr]; + exprs.extend(self.parse_comma_separated(parse_string_expr)?); + Ok(Expr::Tuple(exprs)) } else { Ok(first_expr) } From fc284664774b063070086adbd15e3847722639b9 Mon Sep 17 00:00:00 2001 From: Luca Date: Tue, 25 Nov 2025 20:05:02 +0100 Subject: [PATCH 5/8] Refactored introducing `link_symbol` --- src/ast/ddl.rs | 14 +++---- src/ast/mod.rs | 13 +++++- src/parser/mod.rs | 32 +++++++-------- tests/sqlparser_hive.rs | 11 ++++-- tests/sqlparser_postgres.rs | 79 +++++++++++++++++++++++-------------- 5 files changed, 90 insertions(+), 59 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index daea00a9e..a4884a5e9 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -3099,15 +3099,11 @@ impl fmt::Display for CreateFunction { if let Some(remote_connection) = &self.remote_connection { write!(f, " REMOTE WITH CONNECTION {remote_connection}")?; } - if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body { - write!(f, " AS ")?; - // Special handling for tuple expressions to format without parentheses - // PostgreSQL C functions use: AS 'obj_file', 'link_symbol' - // rather than: AS ('obj_file', 'link_symbol') - if let Expr::Tuple(exprs) = function_body { - write!(f, "{}", display_comma_separated(exprs))?; - } else { - write!(f, "{function_body}")?; + if let Some(CreateFunctionBody::AsBeforeOptions { body, link_symbol }) = &self.function_body + { + write!(f, " AS {body}")?; + if let Some(link_symbol) = link_symbol { + write!(f, ", {link_symbol}")?; } } if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2c452a699..2cb3ab8cf 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -9099,8 +9099,19 @@ pub enum CreateFunctionBody { /// OPTIONS(description="desc"); /// ``` /// + /// For PostgreSQL C functions with object file and link symbol: + /// ```sql + /// CREATE FUNCTION cas_in(input cstring) RETURNS cas + /// AS 'MODULE_PATHNAME', 'cas_in_wrapper' + /// ``` + /// /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11 - AsBeforeOptions(Expr), + AsBeforeOptions { + /// The primary expression (object file path for C functions, or regular expression) + body: Expr, + /// Optional link symbol for C language functions + link_symbol: Option, + }, /// A function body expression using the 'AS' keyword and shows up /// after any `OPTIONS` clause. /// diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 640bfd5f2..7b3b7b032 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5204,9 +5204,7 @@ impl<'a> Parser<'a> { } if self.parse_keyword(Keyword::AS) { ensure_not_set(&body.function_body, "AS")?; - body.function_body = Some(CreateFunctionBody::AsBeforeOptions( - self.parse_create_function_body_string()?, - )); + body.function_body = Some(self.parse_create_function_body_string()?); } else if self.parse_keyword(Keyword::LANGUAGE) { ensure_not_set(&body.language, "LANGUAGE")?; body.language = Some(self.parse_identifier()?); @@ -5298,7 +5296,7 @@ impl<'a> Parser<'a> { let name = self.parse_object_name(false)?; self.expect_keyword_is(Keyword::AS)?; - let as_ = self.parse_create_function_body_string()?; + let body = self.parse_create_function_body_string()?; let using = self.parse_optional_create_function_using()?; Ok(Statement::CreateFunction(CreateFunction { @@ -5306,7 +5304,7 @@ impl<'a> Parser<'a> { or_replace, temporary, name, - function_body: Some(CreateFunctionBody::AsBeforeOptions(as_)), + function_body: Some(body), using, if_not_exists: false, args: None, @@ -5368,7 +5366,10 @@ impl<'a> Parser<'a> { let expr = self.parse_expr()?; if options.is_none() { options = self.maybe_parse_options(Keyword::OPTIONS)?; - Some(CreateFunctionBody::AsBeforeOptions(expr)) + Some(CreateFunctionBody::AsBeforeOptions { + body: expr, + link_symbol: None, + }) } else { Some(CreateFunctionBody::AsAfterOptions(expr)) } @@ -10521,7 +10522,7 @@ impl<'a> Parser<'a> { /// Parse the body of a `CREATE FUNCTION` specified as a string. /// e.g. `CREATE FUNCTION ... AS $$ body $$`. - fn parse_create_function_body_string(&mut self) -> Result { + fn parse_create_function_body_string(&mut self) -> Result { // Helper closure to parse a single string value (quoted or dollar-quoted) let parse_string_expr = |parser: &mut Parser| -> Result { let peek_token = parser.peek_token(); @@ -10538,17 +10539,16 @@ impl<'a> Parser<'a> { } }; - let first_expr = parse_string_expr(self)?; - // Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol') // This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol' - if self.consume_token(&Token::Comma) { - let mut exprs = vec![first_expr]; - exprs.extend(self.parse_comma_separated(parse_string_expr)?); - Ok(Expr::Tuple(exprs)) - } else { - Ok(first_expr) - } + Ok(CreateFunctionBody::AsBeforeOptions { + body: parse_string_expr(self)?, + link_symbol: if self.consume_token(&Token::Comma) { + Some(parse_string_expr(self)?) + } else { + None + }, + }) } /// Parse a literal string diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 56a72ec84..386bab7f0 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -408,10 +408,13 @@ fn parse_create_function() { assert_eq!(name.to_string(), "mydb.myfunc"); assert_eq!( function_body, - Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::SingleQuotedString("org.random.class.Name".to_string())) - .with_empty_span() - ))) + Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::SingleQuotedString("org.random.class.Name".to_string())) + .with_empty_span() + ), + link_symbol: None, + }) ); assert_eq!( using, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index f38b2d4a8..6390a9abf 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4259,9 +4259,12 @@ $$"#; behavior: None, called_on_null: None, parallel: None, - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF str1 <> str2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() - ))), + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF str1 <> str2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() + ), + link_symbol: None, + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -4297,9 +4300,12 @@ $$"#; behavior: None, called_on_null: None, parallel: None, - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> 0 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() - ))), + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> 0 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() + ), + link_symbol: None, + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -4339,9 +4345,12 @@ $$"#; behavior: None, called_on_null: None, parallel: None, - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF a <> b THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() - ))), + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF a <> b THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() + ), + link_symbol: None, + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -4381,9 +4390,12 @@ $$"#; behavior: None, called_on_null: None, parallel: None, - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> int2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() - ))), + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> int2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span() + ), + link_symbol: None, + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -4416,13 +4428,16 @@ $$"#; behavior: None, called_on_null: None, parallel: None, - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::DollarQuotedString(DollarQuotedString { - value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(), - tag: None - })) - .with_empty_span() - ))), + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::DollarQuotedString(DollarQuotedString { + value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(), + tag: None + })) + .with_empty_span() + ), + link_symbol: None, + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -4454,9 +4469,12 @@ fn parse_create_function() { behavior: Some(FunctionBehavior::Immutable), called_on_null: Some(FunctionCalledOnNull::Strict), parallel: Some(FunctionParallel::Safe), - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value( - (Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span() - ))), + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( + (Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span() + ), + link_symbol: None, + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -4509,12 +4527,14 @@ fn parse_create_function_c_with_module_pathname() { behavior: Some(FunctionBehavior::Immutable), called_on_null: None, parallel: Some(FunctionParallel::Safe), - function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Tuple(vec![ - Expr::Value( + function_body: Some(CreateFunctionBody::AsBeforeOptions { + body: Expr::Value( (Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span() ), - Expr::Value((Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span()), - ]))), + link_symbol: Some(Expr::Value( + (Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span() + )), + }), if_not_exists: false, using: None, determinism_specifier: None, @@ -6118,8 +6138,8 @@ fn parse_trigger_related_functions() { args: Some(vec![]), return_type: Some(DataType::Trigger), function_body: Some( - CreateFunctionBody::AsBeforeOptions( - Expr::Value(( + CreateFunctionBody::AsBeforeOptions { + body: Expr::Value(( Value::DollarQuotedString( DollarQuotedString { value: "\n BEGIN\n -- Check that empname and salary are given\n IF NEW.empname IS NULL THEN\n RAISE EXCEPTION 'empname cannot be null';\n END IF;\n IF NEW.salary IS NULL THEN\n RAISE EXCEPTION '% cannot have null salary', NEW.empname;\n END IF;\n\n -- Who works for us when they must pay for it?\n IF NEW.salary < 0 THEN\n RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;\n END IF;\n\n -- Remember who changed the payroll when\n NEW.last_date := current_timestamp;\n NEW.last_user := current_user;\n RETURN NEW;\n END;\n ".to_owned(), @@ -6129,7 +6149,8 @@ fn parse_trigger_related_functions() { }, ) ).with_empty_span()), - ), + link_symbol: None, + }, ), behavior: None, called_on_null: None, From 7789817293be308a70f2a5f20b4087bf1a3facc4 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Wed, 26 Nov 2025 10:48:27 +0100 Subject: [PATCH 6/8] Update src/parser/mod.rs Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7b3b7b032..cd9920b53 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10539,8 +10539,6 @@ impl<'a> Parser<'a> { } }; - // Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol') - // This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol' Ok(CreateFunctionBody::AsBeforeOptions { body: parse_string_expr(self)?, link_symbol: if self.consume_token(&Token::Comma) { From 4f4fcbf4c9d0e3b363a85fa4f145b42662adcf74 Mon Sep 17 00:00:00 2001 From: Luca Cappelletti Date: Wed, 26 Nov 2025 10:48:36 +0100 Subject: [PATCH 7/8] Update src/parser/mod.rs Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index cd9920b53..3f4aab837 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10523,7 +10523,6 @@ impl<'a> Parser<'a> { /// Parse the body of a `CREATE FUNCTION` specified as a string. /// e.g. `CREATE FUNCTION ... AS $$ body $$`. fn parse_create_function_body_string(&mut self) -> Result { - // Helper closure to parse a single string value (quoted or dollar-quoted) let parse_string_expr = |parser: &mut Parser| -> Result { let peek_token = parser.peek_token(); let span = peek_token.span; From 5db8eac18d3b0ada5ee121aa4865f173a6894746 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 26 Nov 2025 11:16:01 +0100 Subject: [PATCH 8/8] Applied proposed change to documentation --- src/ast/mod.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 7c0b56701..2d768c246 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -9107,17 +9107,19 @@ pub enum CreateFunctionBody { /// OPTIONS(description="desc"); /// ``` /// - /// For PostgreSQL C functions with object file and link symbol: - /// ```sql - /// CREATE FUNCTION cas_in(input cstring) RETURNS cas - /// AS 'MODULE_PATHNAME', 'cas_in_wrapper' - /// ``` - /// /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11 + /// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html AsBeforeOptions { - /// The primary expression (object file path for C functions, or regular expression) + /// The primary expression. body: Expr, - /// Optional link symbol for C language functions + /// Link symbol if the primary expression contains the name of shared library file. + /// + /// Example: + /// ```sql + /// CREATE FUNCTION cas_in(input cstring) RETURNS cas + /// AS 'MODULE_PATHNAME', 'cas_in_wrapper' + /// ``` + /// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html link_symbol: Option, }, /// A function body expression using the 'AS' keyword and shows up