STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-1-codex-mini/flux-pr-2011/flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/agent-logs/agent.patch

10327 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index cd93785..39177aa 100644
--- a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -4218,8 +4218,8 @@ pub enum Statement {
         on: Box<Expr>,
         /// Specifies the actions to perform when values match or do not match.
         clauses: Vec<MergeClause>,
-        // Specifies the output to save changes in MSSQL
-        output: Option<OutputClause>,
+        /// Specifies the output or returning clause to save changes
+        output: Option<MergeOutputClause>,
     },
     /// ```sql
     /// CACHE [ FLAG ] TABLE <table_name> [ OPTIONS('K1' = 'V1', 'K2' = V2) ] [ AS ] [ <query> ]
@@ -9099,32 +9099,46 @@ impl Display for MergeClause {
     }
 }
 
-/// A Output Clause in the end of a 'MERGE' Statement
+/// Output or returning clause at the end of a `MERGE` statement.
 ///
-/// Example:
-/// OUTPUT $action, deleted.* INTO dbo.temp_products;
-/// [mssql](https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql)
+/// Examples:
+/// ```sql
+/// OUTPUT inserted.*, deleted.* INTO dbo.temp_products;
+/// ```
+/// ```sql
+/// RETURNING target_table.id;
+/// ```
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
-pub struct OutputClause {
+pub struct MergeOutputClause {
     pub select_items: Vec<SelectItem>,
-    pub into_table: SelectInto,
+    pub into_table: Option<SelectInto>,
+    pub kind: MergeOutputKind,
 }
 
-impl fmt::Display for OutputClause {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let OutputClause {
-            select_items,
-            into_table,
-        } = self;
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum MergeOutputKind {
+    Output,
+    Returning,
+}
 
-        write!(
-            f,
-            "OUTPUT {} {}",
-            display_comma_separated(select_items),
-            into_table
-        )
+impl fmt::Display for MergeOutputClause {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self.kind {
+            MergeOutputKind::Output => {
+                write!(f, "OUTPUT {}", display_comma_separated(&self.select_items))?;
+                if let Some(into) = &self.into_table {
+                    write!(f, " {into}")?;
+                }
+                Ok(())
+            }
+            MergeOutputKind::Returning => {
+                write!(f, "RETURNING {}", display_comma_separated(&self.select_items))
+            }
+        }
     }
 }
 
diff --git a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/query.rs b/app/src/ast/query.rs
index 2ef456b..967af85 100644
--- a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -161,6 +161,7 @@ pub enum SetExpr {
     Insert(Statement),
     Update(Statement),
     Delete(Statement),
+    Merge(Statement),
     Table(Box<Table>),
 }
 
@@ -188,6 +189,7 @@ impl fmt::Display for SetExpr {
             SetExpr::Insert(v) => v.fmt(f),
             SetExpr::Update(v) => v.fmt(f),
             SetExpr::Delete(v) => v.fmt(f),
+            SetExpr::Merge(v) => v.fmt(f),
             SetExpr::Table(t) => t.fmt(f),
             SetExpr::SetOperation {
                 left,
diff --git a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/spans.rs b/app/src/ast/spans.rs
index add6c39..3976175 100644
--- a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -214,6 +214,7 @@ impl Spanned for SetExpr {
             SetExpr::Table(_) => Span::empty(),
             SetExpr::Update(statement) => statement.span(),
             SetExpr::Delete(statement) => statement.span(),
+            SetExpr::Merge(statement) => statement.span(),
         }
     }
 }
diff --git a/tmp/agent-patch-flux-pr-2011.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 c4c72e9..f34efb5 100644
--- a/tmp/agent-patch-flux-pr-2011.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
@@ -11508,6 +11508,11 @@ impl<'a> Parser<'a> {
         Ok(Box::new(SetExpr::Delete(self.parse_delete()?)))
     }
 
+    /// Parse a MERGE statement returning a `Box`ed SetExpr.
+    fn parse_merge_setexpr_boxed(&mut self) -> Result<Box<SetExpr>, ParserError> {
+        Ok(Box::new(SetExpr::Merge(self.parse_merge()?)))
+    }
+
     pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
         let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
             // `FROM` keyword is optional in BigQuery SQL.
@@ -12202,6 +12207,8 @@ impl<'a> Parser<'a> {
             SetExpr::Values(self.parse_values(is_mysql)?)
         } else if self.parse_keyword(Keyword::TABLE) {
             SetExpr::Table(Box::new(self.parse_as_table()?))
+        } else if self.parse_keyword(Keyword::MERGE) {
+            SetExpr::Merge(self.parse_merge()?)
         } else {
             return self.expected(
                 "SELECT, VALUES, or a subquery in the query body",
@@ -16571,16 +16578,29 @@ impl<'a> Parser<'a> {
         Ok(clauses)
     }
 
-    fn parse_output(&mut self) -> Result<OutputClause, ParserError> {
-        self.expect_keyword_is(Keyword::OUTPUT)?;
-        let select_items = self.parse_projection()?;
-        self.expect_keyword_is(Keyword::INTO)?;
-        let into_table = self.parse_select_into()?;
+    fn parse_merge_output(&mut self) -> Result<MergeOutputClause, ParserError> {
+        if self.parse_keyword(Keyword::RETURNING) {
+            let select_items = self.parse_projection()?;
+            Ok(MergeOutputClause {
+                select_items,
+                into_table: None,
+                kind: MergeOutputKind::Returning,
+            })
+        } else {
+            self.expect_keyword_is(Keyword::OUTPUT)?;
+            let select_items = self.parse_projection()?;
+            let into_table = if self.parse_keyword(Keyword::INTO) {
+                Some(self.parse_select_into()?)
+            } else {
+                None
+            };
 
-        Ok(OutputClause {
-            select_items,
-            into_table,
-        })
+            Ok(MergeOutputClause {
+                select_items,
+                into_table,
+                kind: MergeOutputKind::Output,
+            })
+        }
     }
 
     fn parse_select_into(&mut self) -> Result<SelectInto, ParserError> {
@@ -16609,8 +16629,8 @@ impl<'a> Parser<'a> {
         self.expect_keyword_is(Keyword::ON)?;
         let on = self.parse_expr()?;
         let clauses = self.parse_merge_clauses()?;
-        let output = if self.peek_keyword(Keyword::OUTPUT) {
-            Some(self.parse_output()?)
+        let output = if self.peek_keyword(Keyword::OUTPUT) || self.peek_keyword(Keyword::RETURNING) {
+            Some(self.parse_merge_output()?)
         } else {
             None
         };
diff --git a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index 54ad173..011112d 100644
--- a/tmp/agent-patch-flux-pr-2011.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -9912,7 +9912,70 @@ fn test_merge_with_output() {
             INSERT (ID, description) VALUES (source_table.id, source_table.description) \
         OUTPUT inserted.* INTO log_target";
 
-    verified_stmt(sql);
+    match verified_stmt(sql) {
+        Statement::Merge { output: Some(output), .. } => {
+            assert_eq!(output.kind, MergeOutputKind::Output);
+            assert!(output.into_table.is_some());
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn test_merge_output_without_into() {
+    let sql = "MERGE INTO target_table USING source_table \
+        ON target_table.id = source_table.oooid \
+        WHEN MATCHED THEN \
+            UPDATE SET target_table.description = source_table.description \
+        WHEN NOT MATCHED THEN \
+            INSERT (ID, description) VALUES (source_table.id, source_table.description) \
+        OUTPUT inserted.*";
+
+    match verified_stmt(sql) {
+        Statement::Merge { output: Some(output), .. } => {
+            assert_eq!(output.kind, MergeOutputKind::Output);
+            assert!(output.into_table.is_none());
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn test_merge_returning_clause() {
+    let sql = "MERGE INTO target_table USING source_table \
+        ON target_table.id = source_table.oooid \
+        WHEN MATCHED THEN \
+            UPDATE SET target_table.description = source_table.description \
+        WHEN NOT MATCHED THEN \
+            INSERT (ID, description) VALUES (source_table.id, source_table.description) \
+        RETURNING target_table.id";
+
+    match verified_stmt(sql) {
+        Statement::Merge { output: Some(output), .. } => {
+            assert_eq!(output.kind, MergeOutputKind::Returning);
+            assert!(output.into_table.is_none());
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn test_merge_as_query_body() {
+    let sql = "WITH cte AS (SELECT 1) \
+        MERGE INTO target_table t USING source_table s \
+        ON t.id = s.id \
+        WHEN MATCHED THEN UPDATE SET t.description = s.description \
+        WHEN NOT MATCHED THEN INSERT (ID, description) VALUES (s.id, s.description) \
+        RETURNING t.id";
+    let query = verified_query(sql);
+    assert!(query.with.is_some());
+    match query.body.as_ref() {
+        SetExpr::Merge(Statement::Merge { output, .. }) => {
+            let output = output.as_ref().expect("expected output clause");
+            assert_eq!(output.kind, MergeOutputKind::Returning);
+        }
+        _ => unreachable!(),
+    }
 }
 
 #[test]