agent_patch
runs/2026-02-28__18-27-22__gpt-5-1-codex-mini/flux-pr-1839/flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/agent-logs/agent.patch
15858 bytes
diff --git a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/ddl.rs b/app/src/ast/ddl.rs
index a8a1fdb..7f5a48a 100644
--- a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/ddl.rs
+++ b/app/src/ast/ddl.rs
@@ -30,11 +30,11 @@ use sqlparser_derive::{Visit, VisitMut};
use crate::ast::value::escape_single_quote_string;
use crate::ast::{
- display_comma_separated, display_separated, CommentDef, CreateFunctionBody,
- CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
- FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName,
- OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
- ValueWithSpan,
+ ColumnDef, CommentDef, CreateFunctionBody, CreateFunctionUsing, DataType, Expr,
+ FunctionBehavior, FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel,
+ Ident, MySQLColumnPosition, ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect,
+ SequenceOptions, SqlOption, Tag, Value, ValueWithSpan, display_comma_separated,
+ display_separated,
};
use crate::keywords::Keyword;
use crate::tokenizer::Token;
@@ -2258,7 +2258,7 @@ pub struct CreateFunction {
pub if_not_exists: bool,
pub name: ObjectName,
pub args: Option<Vec<OperateFunctionArg>>,
- pub return_type: Option<DataType>,
+ pub return_type: Option<CreateFunctionReturnType>,
/// The expression that defines the function.
///
/// Examples:
@@ -2311,6 +2311,49 @@ pub struct CreateFunction {
pub remote_connection: Option<ObjectName>,
}
+/// Describes the type of value a `CREATE FUNCTION` returns.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum CreateFunctionReturnType {
+ /// A scalar return type such as `RETURNS INT`.
+ Scalar(DataType),
+ /// A table-valued return type such as `RETURNS TABLE` or
+ /// `RETURNS @result TABLE (...)`.
+ Table(CreateFunctionReturnsTable),
+}
+
+impl fmt::Display for CreateFunctionReturnType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ CreateFunctionReturnType::Scalar(data_type) => write!(f, "{data_type}"),
+ CreateFunctionReturnType::Table(table) => write!(f, "{table}"),
+ }
+ }
+}
+
+/// Definition of a table-valued return type for `CREATE FUNCTION`.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateFunctionReturnsTable {
+ pub table_variable: Option<Ident>,
+ pub columns: Vec<ColumnDef>,
+}
+
+impl fmt::Display for CreateFunctionReturnsTable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(name) = &self.table_variable {
+ write!(f, "{name} ")?;
+ }
+ write!(f, "TABLE")?;
+ if !self.columns.is_empty() {
+ write!(f, " ({})", display_comma_separated(&self.columns))?;
+ }
+ Ok(())
+ }
+}
+
impl fmt::Display for CreateFunction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
diff --git a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 47b321d..c0339d2 100644
--- a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -4975,7 +4975,7 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?;
let return_type = if self.parse_keyword(Keyword::RETURNS) {
- Some(self.parse_data_type()?)
+ Some(CreateFunctionReturnType::Scalar(self.parse_data_type()?))
} else {
None
};
@@ -5129,7 +5129,7 @@ impl<'a> Parser<'a> {
let (name, args) = self.parse_create_function_name_and_params()?;
let return_type = if self.parse_keyword(Keyword::RETURNS) {
- Some(self.parse_data_type()?)
+ Some(CreateFunctionReturnType::Scalar(self.parse_data_type()?))
} else {
None
};
@@ -5204,19 +5204,24 @@ impl<'a> Parser<'a> {
let (name, args) = self.parse_create_function_name_and_params()?;
self.expect_keyword(Keyword::RETURNS)?;
- let return_type = Some(self.parse_data_type()?);
-
- self.expect_keyword_is(Keyword::AS)?;
+ let return_type = Some(self.parse_mssql_create_function_return_type()?);
- let begin_token = self.expect_keyword(Keyword::BEGIN)?;
- let statements = self.parse_statement_list(&[Keyword::END])?;
- let end_token = self.expect_keyword(Keyword::END)?;
+ let _ = self.parse_keyword(Keyword::AS);
- let function_body = Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements {
- begin_token: AttachedToken(begin_token),
- statements,
- end_token: AttachedToken(end_token),
- }));
+ let function_body = if self.peek_keyword(Keyword::BEGIN) {
+ let begin_token = self.expect_keyword(Keyword::BEGIN)?;
+ let statements = self.parse_statement_list(&[Keyword::END])?;
+ let end_token = self.expect_keyword(Keyword::END)?;
+ Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements {
+ begin_token: AttachedToken(begin_token),
+ statements,
+ end_token: AttachedToken(end_token),
+ }))
+ } else if self.parse_keyword(Keyword::RETURN) {
+ Some(CreateFunctionBody::Return(self.parse_expr()?))
+ } else {
+ return self.expected("BEGIN or RETURN", self.peek_token());
+ };
Ok(Statement::CreateFunction(CreateFunction {
or_alter,
@@ -5238,6 +5243,42 @@ impl<'a> Parser<'a> {
}))
}
+ fn parse_mssql_create_function_return_type(
+ &mut self,
+ ) -> Result<CreateFunctionReturnType, ParserError> {
+ let can_be_table_variable = matches!(
+ &self.peek_token().token,
+ Token::Word(word) if word.value.starts_with('@')
+ );
+ let table_variable = if can_be_table_variable {
+ Some(self.parse_identifier()?)
+ } else {
+ None
+ };
+
+ if self.parse_keyword(Keyword::TABLE) {
+ let columns = self.parse_optional_returns_table_columns()?;
+ Ok(CreateFunctionReturnType::Table(CreateFunctionReturnsTable {
+ table_variable,
+ columns,
+ }))
+ } else {
+ if table_variable.is_some() {
+ return self.expected("TABLE", self.peek_token());
+ }
+ Ok(CreateFunctionReturnType::Scalar(self.parse_data_type()?))
+ }
+ }
+
+ fn parse_optional_returns_table_columns(&mut self) -> Result<Vec<ColumnDef>, ParserError> {
+ if !self.consume_token(&Token::LParen) {
+ return Ok(vec![]);
+ }
+ let columns = self.parse_comma_separated(Parser::parse_column_def)?;
+ self.expect_token(&Token::RParen)?;
+ Ok(columns)
+ }
+
fn parse_create_function_name_and_params(
&mut self,
) -> Result<(ObjectName, Vec<OperateFunctionArg>), ParserError> {
@@ -9838,19 +9879,9 @@ impl<'a> Parser<'a> {
Ok((data, trailing_bracket))
}
- fn parse_returns_table_column(&mut self) -> Result<ColumnDef, ParserError> {
- let name = self.parse_identifier()?;
- let data_type = self.parse_data_type()?;
- Ok(ColumnDef {
- name,
- data_type,
- options: Vec::new(), // No constraints expected here
- })
- }
-
fn parse_returns_table_columns(&mut self) -> Result<Vec<ColumnDef>, ParserError> {
self.expect_token(&Token::LParen)?;
- let columns = self.parse_comma_separated(Parser::parse_returns_table_column)?;
+ let columns = self.parse_comma_separated(Parser::parse_column_def)?;
self.expect_token(&Token::RParen)?;
Ok(columns)
}
diff --git a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index 8f54f3c..dd00dd6 100644
--- a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -2144,7 +2144,7 @@ fn test_bigquery_create_function() {
Ident::new("myfunction"),
]),
args: Some(vec![OperateFunctionArg::with_name("x", DataType::Float64),]),
- return_type: Some(DataType::Float64),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Float64)),
function_body: Some(CreateFunctionBody::AsAfterOptions(Expr::Value(
number("42").with_empty_span()
))),
diff --git a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index 7b3769e..84c39ca 100644
--- a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -222,7 +222,7 @@ fn parse_create_function() {
default_expr: None,
},
]),
- return_type: Some(DataType::Int(None)),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Int(None))),
function_body: Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements {
begin_token: AttachedToken::empty(),
statements: vec![Statement::Return(ReturnStatement {
@@ -288,6 +288,87 @@ fn parse_create_function() {
END\
";
let _ = ms().verified_stmt(create_function_with_return_expression);
+
+ let create_function_without_as = "\
+ CREATE FUNCTION no_as_udf(@foo INT) \
+ RETURNS INT \
+ BEGIN \
+ RETURN @foo; \
+ END\
+ ";
+ let _ = ms().verified_stmt(create_function_without_as);
+}
+
+#[test]
+fn parse_mssql_inline_table_valued_function() {
+ let sql = "\
+ CREATE FUNCTION inline_tvf() \
+ RETURNS TABLE \
+ RETURN \
+ SELECT 1 AS value; \
+ ";
+
+ let stmt = ms().verified_stmt(sql);
+ match stmt {
+ Statement::CreateFunction(CreateFunction {
+ return_type: Some(CreateFunctionReturnType::Table(table_return)),
+ function_body: Some(CreateFunctionBody::Return(expr)),
+ ..
+ }) => {
+ assert_eq!(table_return.table_variable, None);
+ assert!(table_return.columns.is_empty());
+ match expr {
+ Expr::Query(_) => {}
+ other => panic!("expected RETURN to contain a query, got {other:?}"),
+ }
+ }
+ other => panic!("expected CREATE FUNCTION statement, got {other:?}"),
+ }
+}
+
+#[test]
+fn parse_mssql_multistatement_table_valued_function() {
+ let sql = "\
+ CREATE FUNCTION dbo.tvf_example(@input INT) \
+ RETURNS @result TABLE ( \
+ id INT NOT NULL, \
+ name VARCHAR(50) CONSTRAINT pk_result PRIMARY KEY \
+ ) \
+ AS \
+ BEGIN \
+ INSERT INTO @result (id, name) VALUES (@input, 'test'); \
+ RETURN; \
+ END \
+ ";
+
+ let stmt = ms().verified_stmt(sql);
+ match stmt {
+ Statement::CreateFunction(CreateFunction {
+ return_type: Some(CreateFunctionReturnType::Table(table_return)),
+ ..
+ }) => {
+ assert_eq!(table_return.table_variable, Some(Ident::new("@result")));
+ assert_eq!(table_return.columns.len(), 2);
+ assert_eq!(
+ table_return.columns[0].options,
+ vec![ColumnOptionDef {
+ name: None,
+ option: ColumnOption::NotNull,
+ }]
+ );
+ assert_eq!(
+ table_return.columns[1].options,
+ vec![ColumnOptionDef {
+ name: Some(Ident::new("pk_result")),
+ option: ColumnOption::Unique {
+ is_primary: true,
+ characteristics: None,
+ },
+ }]
+ );
+ }
+ other => panic!("expected CREATE FUNCTION statement, got {other:?}"),
+ }
}
#[test]
diff --git a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index 682c0d6..2a7df30 100644
--- a/tmp/agent-patch-flux-pr-1839.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -4135,7 +4135,7 @@ $$"#;
DataType::Varchar(None),
),
]),
- return_type: Some(DataType::Boolean),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Boolean)),
language: Some("plpgsql".into()),
behavior: None,
called_on_null: None,
@@ -4173,7 +4173,7 @@ $$"#;
DataType::Int(None)
)
]),
- return_type: Some(DataType::Boolean),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Boolean)),
language: Some("plpgsql".into()),
behavior: None,
called_on_null: None,
@@ -4215,7 +4215,7 @@ $$"#;
DataType::Int(None)
),
]),
- return_type: Some(DataType::Boolean),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Boolean)),
language: Some("plpgsql".into()),
behavior: None,
called_on_null: None,
@@ -4257,7 +4257,7 @@ $$"#;
DataType::Int(None)
),
]),
- return_type: Some(DataType::Boolean),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Boolean)),
language: Some("plpgsql".into()),
behavior: None,
called_on_null: None,
@@ -4292,7 +4292,7 @@ $$"#;
),
OperateFunctionArg::with_name("b", DataType::Varchar(None)),
]),
- return_type: Some(DataType::Boolean),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Boolean)),
language: Some("plpgsql".into()),
behavior: None,
called_on_null: None,
@@ -4330,7 +4330,7 @@ fn parse_create_function() {
OperateFunctionArg::unnamed(DataType::Integer(None)),
OperateFunctionArg::unnamed(DataType::Integer(None)),
]),
- return_type: Some(DataType::Integer(None)),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Integer(None))),
language: Some("SQL".into()),
behavior: Some(FunctionBehavior::Immutable),
called_on_null: Some(FunctionCalledOnNull::Strict),
@@ -5873,7 +5873,7 @@ fn parse_trigger_related_functions() {
if_not_exists: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
args: Some(vec![]),
- return_type: Some(DataType::Trigger),
+ return_type: Some(CreateFunctionReturnType::Scalar(DataType::Trigger)),
function_body: Some(
CreateFunctionBody::AsBeforeOptions(
Expr::Value((