STET

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

Back to adjudication
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((