STET

agent_patch

runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-2148/flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/agent-logs/agent.patch

15089 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/dml.rs b/app/src/ast/dml.rs
index 4c36f70..9d0714e 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/dml.rs
+++ b/app/src/ast/dml.rs
@@ -193,6 +193,144 @@ impl Display for Insert {
     }
 }
 
+/// Snowflake-style multi-table INSERT statements.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MultiInsert {
+    /// Token for the `INSERT` keyword.
+    pub insert_token: AttachedToken,
+    /// A query optimizer hint.
+    pub optimizer_hint: Option<OptimizerHint>,
+    /// Whether `OVERWRITE` was specified.
+    pub overwrite: bool,
+    /// Determines whether the statement uses `ALL` or `FIRST`.
+    pub mode: MultiInsertMode,
+    /// Specifies whether the insert is conditional or unconditional.
+    pub form: MultiInsertForm,
+    /// The query that supplies the rows.
+    pub source: Box<Query>,
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum MultiInsertMode {
+    /// `INSERT ALL`.
+    All,
+    /// `INSERT FIRST` (only allowed with conditional forms).
+    First,
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum MultiInsertForm {
+    /// `INSERT ALL INTO ... SELECT ...`.
+    Unconditional(Vec<MultiInsertInto>),
+    /// `INSERT {ALL | FIRST} WHEN ... THEN ... [ELSE ...] SELECT ...`.
+    Conditional {
+        when_clauses: Vec<MultiInsertWhenClause>,
+        else_targets: Option<Vec<MultiInsertInto>>,
+    },
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MultiInsertWhenClause {
+    /// Expression that guards the `INTO` clauses.
+    pub condition: Expr,
+    /// The list of `INTO` clauses to execute when the condition holds.
+    pub targets: Vec<MultiInsertInto>,
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MultiInsertInto {
+    /// Target table or table function.
+    pub table: TableObject,
+    /// Optional list of target columns.
+    pub columns: Vec<Ident>,
+    /// Optional `VALUES` clause.
+    pub values: Option<Values>,
+}
+
+impl fmt::Display for MultiInsert {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "INSERT")?;
+        if let Some(hint) = &self.optimizer_hint {
+            write!(f, " {hint}")?;
+        }
+        write!(f, " ")?;
+        if self.overwrite {
+            write!(f, "OVERWRITE ")?;
+        }
+        match &self.form {
+            MultiInsertForm::Unconditional(_) => write!(f, "ALL")?,
+            MultiInsertForm::Conditional { .. } => match self.mode {
+                MultiInsertMode::All => write!(f, "ALL")?,
+                MultiInsertMode::First => write!(f, "FIRST")?,
+            },
+        }
+
+        match &self.form {
+            MultiInsertForm::Unconditional(targets) => {
+                for target in targets {
+                    SpaceOrNewline.fmt(f)?;
+                    target.fmt(f)?;
+                }
+            }
+            MultiInsertForm::Conditional {
+                when_clauses,
+                else_targets,
+            } => {
+                for clause in when_clauses {
+                    SpaceOrNewline.fmt(f)?;
+                    clause.fmt(f)?;
+                }
+                if let Some(targets) = else_targets {
+                    SpaceOrNewline.fmt(f)?;
+                    f.write_str("ELSE")?;
+                    for target in targets {
+                        SpaceOrNewline.fmt(f)?;
+                        target.fmt(f)?;
+                    }
+                }
+            }
+        }
+
+        SpaceOrNewline.fmt(f)?;
+        self.source.fmt(f)
+    }
+}
+
+impl fmt::Display for MultiInsertWhenClause {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "WHEN {} THEN", self.condition)?;
+        for target in &self.targets {
+            SpaceOrNewline.fmt(f)?;
+            target.fmt(f)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for MultiInsertInto {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "INTO {table}", table = self.table)?;
+        if !self.columns.is_empty() {
+            write!(f, "({})", display_comma_separated(&self.columns))?;
+        }
+        if let Some(values) = &self.values {
+            SpaceOrNewline.fmt(f)?;
+            values.fmt(f)?;
+        }
+        Ok(())
+    }
+}
+
 /// DELETE statement.
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index cc34ec7..aecc454 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -83,7 +83,8 @@ pub use self::ddl::{
 };
 pub use self::dml::{
     Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
-    MergeInsertKind, MergeUpdateExpr, OutputClause, Update,
+    MergeInsertKind, MergeUpdateExpr, MultiInsert, MultiInsertForm, MultiInsertInto,
+    MultiInsertMode, MultiInsertWhenClause, OutputClause, Update,
 };
 pub use self::operator::{BinaryOperator, UnaryOperator};
 pub use self::query::{
@@ -3427,6 +3428,10 @@ pub enum Statement {
     /// ```
     Insert(Insert),
     /// ```sql
+    /// Multi-table INSERT (Snowflake)
+    /// ```
+    MultiInsert(MultiInsert),
+    /// ```sql
     /// INSTALL
     /// ```
     Install {
@@ -5029,6 +5034,7 @@ impl fmt::Display for Statement {
             }
             Statement::Analyze(analyze) => analyze.fmt(f),
             Statement::Insert(insert) => insert.fmt(f),
+            Statement::MultiInsert(multi_insert) => multi_insert.fmt(f),
             Statement::Install {
                 extension_name: name,
             } => write!(f, "INSTALL {name}"),
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/spans.rs b/app/src/ast/spans.rs
index bdd430e..a69322d 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -39,7 +39,8 @@ use super::{
     IfStatement, IlikeSelectItem, IndexColumn, Insert, Interpolate, InterpolateExpr, Join,
     JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
     MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
-    MergeInsertKind, MergeUpdateExpr, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
+    MergeInsertKind, MergeUpdateExpr, MultiInsert, MultiInsertForm, MultiInsertInto,
+    MultiInsertWhenClause, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
     ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
     OrderByExpr, OrderByKind, OutputClause, Partition, PartitionBoundValue, PivotValueSource,
     ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction,
@@ -316,6 +317,7 @@ impl Spanned for Statement {
             Statement::Msck(msck) => msck.span(),
             Statement::Query(query) => query.span(),
             Statement::Insert(insert) => insert.span(),
+            Statement::MultiInsert(multi_insert) => multi_insert.span(),
             Statement::Install { extension_name } => extension_name.span,
             Statement::Load { extension_name } => extension_name.span,
             Statement::Directory {
@@ -1331,6 +1333,65 @@ impl Spanned for Insert {
     }
 }
 
+impl Spanned for MultiInsert {
+    fn span(&self) -> Span {
+        let MultiInsert {
+            insert_token,
+            optimizer_hint: _,
+            overwrite: _,
+            mode: _,
+            form,
+            source,
+        } = self;
+
+        union_spans(
+            core::iter::once(insert_token.0.span)
+                .chain(core::iter::once(form.span()))
+                .chain(core::iter::once(source.span())),
+        )
+    }
+}
+
+impl Spanned for MultiInsertForm {
+    fn span(&self) -> Span {
+        match self {
+            MultiInsertForm::Unconditional(targets) => {
+                union_spans(targets.iter().map(|target| target.span()))
+            }
+            MultiInsertForm::Conditional {
+                when_clauses,
+                else_targets,
+            } => union_spans(
+                when_clauses
+                    .iter()
+                    .map(|clause| clause.span())
+                    .chain(else_targets.iter().flat_map(|targets| {
+                        targets.iter().map(|target| target.span())
+                    })),
+            ),
+        }
+    }
+}
+
+impl Spanned for MultiInsertWhenClause {
+    fn span(&self) -> Span {
+        union_spans(
+            core::iter::once(self.condition.span())
+                .chain(self.targets.iter().map(|target| target.span())),
+        )
+    }
+}
+
+impl Spanned for MultiInsertInto {
+    fn span(&self) -> Span {
+        union_spans(
+            core::iter::once(self.table.span())
+                .chain(self.columns.iter().map(|column| column.span))
+                .chain(self.values.as_ref().map(|values| values.span())),
+        )
+    }
+}
+
 impl Spanned for OnInsert {
     fn span(&self) -> Span {
         match self {
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 8f3ae38..541627c 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -17001,6 +17001,11 @@ impl<'a> Parser<'a> {
         let replace_into = false;
 
         let overwrite = self.parse_keyword(Keyword::OVERWRITE);
+
+        if self.peek_keyword(Keyword::ALL) || self.peek_keyword(Keyword::FIRST) {
+            return self.parse_multi_insert(insert_token, optimizer_hint, overwrite);
+        }
+
         let into = self.parse_keyword(Keyword::INTO);
 
         let local = self.parse_keyword(Keyword::LOCAL);
@@ -17176,6 +17181,100 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_multi_insert(
+        &mut self,
+        insert_token: TokenWithSpan,
+        optimizer_hint: Option<OptimizerHint>,
+        overwrite: bool,
+    ) -> Result<Statement, ParserError> {
+        let mode = if self.parse_keyword(Keyword::ALL) {
+            MultiInsertMode::All
+        } else if self.parse_keyword(Keyword::FIRST) {
+            MultiInsertMode::First
+        } else {
+            return self.expected("ALL or FIRST", self.peek_token());
+        };
+
+        let has_when = self.peek_keyword(Keyword::WHEN);
+        if !has_when && mode == MultiInsertMode::First {
+            return self.expected("WHEN", self.peek_token());
+        }
+
+        let form = if has_when {
+            let when_clauses = self.parse_multi_insert_when_clauses()?;
+            let else_targets = if self.parse_keyword(Keyword::ELSE) {
+                Some(self.parse_multi_insert_targets()?)
+            } else {
+                None
+            };
+            MultiInsertForm::Conditional {
+                when_clauses,
+                else_targets,
+            }
+        } else {
+            MultiInsertForm::Unconditional(self.parse_multi_insert_targets()?)
+        };
+
+        let source = Box::new(self.parse_query()?);
+
+        Ok(Statement::MultiInsert(MultiInsert {
+            insert_token: insert_token.into(),
+            optimizer_hint,
+            overwrite,
+            mode,
+            form,
+            source,
+        }))
+    }
+
+    fn parse_multi_insert_targets(&mut self) -> Result<Vec<MultiInsertInto>, ParserError> {
+        let mut targets = vec![];
+        while self.peek_keyword(Keyword::INTO) {
+            targets.push(self.parse_multi_insert_into()?);
+        }
+
+        if targets.is_empty() {
+            self.expected("INTO clause", self.peek_token())
+        } else {
+            Ok(targets)
+        }
+    }
+
+    fn parse_multi_insert_when_clauses(
+        &mut self,
+    ) -> Result<Vec<MultiInsertWhenClause>, ParserError> {
+        let mut clauses = vec![];
+        loop {
+            self.expect_keyword(Keyword::WHEN)?;
+            let condition = self.parse_expr()?;
+            self.expect_keyword(Keyword::THEN)?;
+            let targets = self.parse_multi_insert_targets()?;
+            clauses.push(MultiInsertWhenClause { condition, targets });
+
+            if !self.peek_keyword(Keyword::WHEN) {
+                break;
+            }
+        }
+        Ok(clauses)
+    }
+
+    fn parse_multi_insert_into(&mut self) -> Result<MultiInsertInto, ParserError> {
+        self.expect_keyword(Keyword::INTO)?;
+        let table = self.parse_table_object()?;
+        let columns = self.parse_parenthesized_column_list(Optional, false)?;
+        let values = if self.parse_keyword(Keyword::VALUES) {
+            Some(self.parse_values(false, false)?)
+        } else {
+            None
+        };
+
+        Ok(MultiInsertInto {
+            table,
+            columns,
+            values,
+        })
+    }
+
     /// Parses input format clause used for ClickHouse.
     ///
     /// <https://clickhouse.com/docs/en/interfaces/formats>
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_snowflake.rs b/app/tests/sqlparser_snowflake.rs
index 014a241..79df7fd 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_snowflake.rs
+++ b/app/tests/sqlparser_snowflake.rs
@@ -3388,6 +3388,18 @@ fn parse_insert_overwrite() {
     snowflake().verified_stmt(insert_overwrite_into);
 }
 
+#[test]
+fn parse_multi_table_insert_unconditional() {
+    let sql = "INSERT ALL INTO tbl1 (a, b) VALUES (1, 2) INTO tbl2 (c) VALUES (3) SELECT * FROM source";
+    snowflake().verified_stmt(sql);
+}
+
+#[test]
+fn parse_multi_table_insert_conditional() {
+    let sql = "INSERT FIRST WHEN a > 0 THEN INTO tbl1 (a) VALUES (1) WHEN TRUE THEN INTO tbl2 ELSE INTO tbl3 (a) VALUES (2) SELECT * FROM source";
+    snowflake().verified_stmt(sql);
+}
+
 #[test]
 fn test_table_sample() {
     snowflake_and_generic().verified_stmt("SELECT * FROM testtable SAMPLE (10)");