STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-3-codex/flux-pr-2148/flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/agent-logs/agent.patch

16353 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/dml.rs b/app/src/ast/dml.rs
index 4c36f70..5c75fd0 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/dml.rs
+++ b/app/src/ast/dml.rs
@@ -193,6 +193,136 @@ impl Display for Insert {
     }
 }
 
+/// The matching mode for Snowflake multi-table INSERT statements.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum MultiInsertMode {
+    /// Insert into every matching target.
+    All,
+    /// Insert into the first matching target only.
+    First,
+}
+
+impl Display for MultiInsertMode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            MultiInsertMode::All => f.write_str("ALL"),
+            MultiInsertMode::First => f.write_str("FIRST"),
+        }
+    }
+}
+
+/// A single `INTO` target in a Snowflake multi-table INSERT statement.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MultiInsertTarget {
+    /// Target table.
+    pub table: TableObject,
+    /// Optional list of target columns.
+    pub columns: Vec<Ident>,
+    /// Optional values expression attached to the target.
+    pub values: Option<Values>,
+}
+
+impl Display for MultiInsertTarget {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "INTO {}", self.table)?;
+        if !self.columns.is_empty() {
+            write!(f, " ({})", display_comma_separated(&self.columns))?;
+        }
+        if let Some(values) = &self.values {
+            write!(f, " {values}")?;
+        }
+        Ok(())
+    }
+}
+
+/// Clause used by Snowflake 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 enum MultiInsertClause {
+    /// Unconditional target (`INTO ...`) used with `INSERT ALL`.
+    Into(MultiInsertTarget),
+    /// Conditional target list (`WHEN ... THEN INTO ...`).
+    When {
+        /// Condition expression after `WHEN`.
+        condition: Expr,
+        /// One or more insertion targets after `THEN`.
+        targets: Vec<MultiInsertTarget>,
+    },
+}
+
+impl Display for MultiInsertClause {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            MultiInsertClause::Into(target) => target.fmt(f),
+            MultiInsertClause::When { condition, targets } => {
+                write!(f, "WHEN {condition} THEN")?;
+                for target in targets {
+                    SpaceOrNewline.fmt(f)?;
+                    Indent(target).fmt(f)?;
+                }
+                Ok(())
+            }
+        }
+    }
+}
+
+/// Snowflake multi-table INSERT statement.
+#[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,
+    /// Optional optimizer hint.
+    pub optimizer_hint: Option<OptimizerHint>,
+    /// `OVERWRITE` flag.
+    pub overwrite: bool,
+    /// Matching mode (`ALL` / `FIRST`).
+    pub mode: MultiInsertMode,
+    /// Unconditional `INTO` clauses or conditional `WHEN` clauses.
+    pub clauses: Vec<MultiInsertClause>,
+    /// Optional `ELSE` branch targets.
+    pub else_targets: Vec<MultiInsertTarget>,
+    /// Source query consumed once for all target tables.
+    pub source: Box<Query>,
+}
+
+impl Display for MultiInsert {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "INSERT{overwrite}",
+            overwrite = if self.overwrite { " OVERWRITE" } else { "" }
+        )?;
+        if let Some(hint) = self.optimizer_hint.as_ref() {
+            write!(f, " {hint}")?;
+        }
+        write!(f, " {}", self.mode)?;
+
+        for clause in &self.clauses {
+            SpaceOrNewline.fmt(f)?;
+            Indent(clause).fmt(f)?;
+        }
+
+        if !self.else_targets.is_empty() {
+            SpaceOrNewline.fmt(f)?;
+            f.write_str("ELSE")?;
+            for target in &self.else_targets {
+                SpaceOrNewline.fmt(f)?;
+                Indent(target).fmt(f)?;
+            }
+        }
+
+        SpaceOrNewline.fmt(f)?;
+        self.source.fmt(f)
+    }
+}
+
 /// 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-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs b/app/src/ast/mod.rs
index cc34ec7..1485145 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/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, MultiInsertClause, MultiInsertMode,
+    MultiInsertTarget, OutputClause, Update,
 };
 pub use self::operator::{BinaryOperator, UnaryOperator};
 pub use self::query::{
@@ -3427,6 +3428,10 @@ pub enum Statement {
     /// ```
     Insert(Insert),
     /// ```sql
+    /// INSERT ALL/FIRST ... SELECT
+    /// ```
+    InsertMulti(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::InsertMulti(insert) => insert.fmt(f),
             Statement::Install {
                 extension_name: name,
             } => write!(f, "INSTALL {name}"),
@@ -11619,6 +11625,12 @@ impl From<Insert> for Statement {
     }
 }
 
+impl From<MultiInsert> for Statement {
+    fn from(i: MultiInsert) -> Self {
+        Self::InsertMulti(i)
+    }
+}
+
 impl From<Update> for Statement {
     fn from(u: Update) -> Self {
         Self::Update(u)
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs b/app/src/ast/spans.rs
index bdd430e..f4f0c37 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -37,7 +37,8 @@ use super::{
     ExprWithAlias, Fetch, ForValues, FromTable, Function, FunctionArg, FunctionArgExpr,
     FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound,
     IfStatement, IlikeSelectItem, IndexColumn, Insert, Interpolate, InterpolateExpr, Join,
-    JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
+    JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause, MultiInsert,
+    MultiInsertClause, MultiInsertTarget,
     MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
     MergeInsertKind, MergeUpdateExpr, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
     ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
@@ -316,6 +317,7 @@ impl Spanned for Statement {
             Statement::Msck(msck) => msck.span(),
             Statement::Query(query) => query.span(),
             Statement::Insert(insert) => insert.span(),
+            Statement::InsertMulti(insert) => insert.span(),
             Statement::Install { extension_name } => extension_name.span,
             Statement::Load { extension_name } => extension_name.span,
             Statement::Directory {
@@ -1331,6 +1333,54 @@ impl Spanned for Insert {
     }
 }
 
+impl Spanned for MultiInsert {
+    fn span(&self) -> Span {
+        let MultiInsert {
+            insert_token,
+            optimizer_hint: _,
+            overwrite: _,
+            mode: _,
+            clauses,
+            else_targets,
+            source,
+        } = self;
+
+        union_spans(
+            core::iter::once(insert_token.0.span)
+                .chain(clauses.iter().map(|c| c.span()))
+                .chain(else_targets.iter().map(|t| t.span()))
+                .chain(core::iter::once(source.span())),
+        )
+    }
+}
+
+impl Spanned for MultiInsertClause {
+    fn span(&self) -> Span {
+        match self {
+            MultiInsertClause::Into(target) => target.span(),
+            MultiInsertClause::When { condition, targets } => {
+                union_spans(core::iter::once(condition.span()).chain(targets.iter().map(|t| t.span())))
+            }
+        }
+    }
+}
+
+impl Spanned for MultiInsertTarget {
+    fn span(&self) -> Span {
+        let MultiInsertTarget {
+            table,
+            columns,
+            values,
+        } = self;
+
+        union_spans(
+            core::iter::once(table.span())
+                .chain(columns.iter().map(|c| c.span))
+                .chain(values.as_ref().map(|v| v.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-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 8f3ae38..3d75fcf 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -17003,6 +17003,25 @@ impl<'a> Parser<'a> {
         let overwrite = self.parse_keyword(Keyword::OVERWRITE);
         let into = self.parse_keyword(Keyword::INTO);
 
+        if dialect_of!(self is SnowflakeDialect) && !into {
+            let mode = if self.parse_keyword(Keyword::ALL) {
+                Some(MultiInsertMode::All)
+            } else if self.parse_keyword(Keyword::FIRST) {
+                Some(MultiInsertMode::First)
+            } else {
+                None
+            };
+
+            if let Some(mode) = mode {
+                return self.parse_snowflake_multi_insert(
+                    insert_token,
+                    optimizer_hint,
+                    overwrite,
+                    mode,
+                );
+            }
+        }
+
         let local = self.parse_keyword(Keyword::LOCAL);
 
         if self.parse_keyword(Keyword::DIRECTORY) {
@@ -17176,6 +17195,91 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_snowflake_multi_insert(
+        &mut self,
+        insert_token: TokenWithSpan,
+        optimizer_hint: Option<OptimizerHint>,
+        overwrite: bool,
+        mode: MultiInsertMode,
+    ) -> Result<Statement, ParserError> {
+        let mut clauses = vec![];
+        let mut else_targets = vec![];
+
+        if self.parse_keyword(Keyword::WHEN) {
+            loop {
+                let condition = self.parse_expr()?;
+                self.expect_keyword_is(Keyword::THEN)?;
+                let targets = self.parse_snowflake_multi_insert_targets()?;
+                clauses.push(MultiInsertClause::When { condition, targets });
+
+                if !self.parse_keyword(Keyword::WHEN) {
+                    break;
+                }
+            }
+
+            if self.parse_keyword(Keyword::ELSE) {
+                else_targets = self.parse_snowflake_multi_insert_targets()?;
+            }
+        } else {
+            if mode != MultiInsertMode::All {
+                return self.expected("WHEN", self.peek_token());
+            }
+
+            while self.parse_keyword(Keyword::INTO) {
+                clauses.push(MultiInsertClause::Into(
+                    self.parse_snowflake_multi_insert_target()?,
+                ));
+            }
+        }
+
+        if clauses.is_empty() {
+            return self.expected("INTO", self.peek_token());
+        }
+
+        let source = self.parse_query()?;
+
+        Ok(MultiInsert {
+            insert_token: insert_token.into(),
+            optimizer_hint,
+            overwrite,
+            mode,
+            clauses,
+            else_targets,
+            source,
+        }
+        .into())
+    }
+
+    fn parse_snowflake_multi_insert_targets(
+        &mut self,
+    ) -> Result<Vec<MultiInsertTarget>, ParserError> {
+        let mut targets = vec![];
+        while self.parse_keyword(Keyword::INTO) {
+            targets.push(self.parse_snowflake_multi_insert_target()?);
+        }
+
+        if targets.is_empty() {
+            return self.expected("INTO", self.peek_token());
+        }
+        Ok(targets)
+    }
+
+    fn parse_snowflake_multi_insert_target(&mut self) -> Result<MultiInsertTarget, ParserError> {
+        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, true)?)
+        } else {
+            None
+        };
+
+        Ok(MultiInsertTarget {
+            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-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_snowflake.rs b/app/tests/sqlparser_snowflake.rs
index 014a241..ea7c842 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_snowflake.rs
+++ b/app/tests/sqlparser_snowflake.rs
@@ -3388,6 +3388,74 @@ fn parse_insert_overwrite() {
     snowflake().verified_stmt(insert_overwrite_into);
 }
 
+#[test]
+fn parse_insert_all_multi_table_unconditional() {
+    let sql = "INSERT ALL INTO t1 INTO t2 (a) VALUES (a) SELECT a FROM src";
+    match snowflake().verified_stmt(sql) {
+        Statement::InsertMulti(MultiInsert {
+            overwrite,
+            mode,
+            clauses,
+            else_targets,
+            ..
+        }) => {
+            assert!(!overwrite);
+            assert_eq!(MultiInsertMode::All, mode);
+            assert!(else_targets.is_empty());
+            assert_eq!(2, clauses.len());
+            assert!(matches!(clauses[0], MultiInsertClause::Into(_)));
+            assert!(matches!(clauses[1], MultiInsertClause::Into(_)));
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_insert_multi_table_conditional_with_else() {
+    let sql = "INSERT ALL WHEN a > 0 THEN INTO t1 (a) VALUES (a) INTO t2 WHEN a < 0 THEN INTO t3 ELSE INTO t4 SELECT a FROM src";
+    match snowflake().verified_stmt(sql) {
+        Statement::InsertMulti(MultiInsert {
+            mode,
+            clauses,
+            else_targets,
+            ..
+        }) => {
+            assert_eq!(MultiInsertMode::All, mode);
+            assert_eq!(2, clauses.len());
+            assert_eq!(1, else_targets.len());
+
+            match &clauses[0] {
+                MultiInsertClause::When { targets, .. } => assert_eq!(2, targets.len()),
+                _ => unreachable!(),
+            }
+            match &clauses[1] {
+                MultiInsertClause::When { targets, .. } => assert_eq!(1, targets.len()),
+                _ => unreachable!(),
+            }
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_insert_first_multi_table_conditional() {
+    let sql =
+        "INSERT FIRST WHEN a = 1 THEN INTO t1 VALUES (a) ELSE INTO t2 (a) VALUES (a) SELECT a FROM src";
+    match snowflake().verified_stmt(sql) {
+        Statement::InsertMulti(MultiInsert {
+            mode,
+            clauses,
+            else_targets,
+            ..
+        }) => {
+            assert_eq!(MultiInsertMode::First, mode);
+            assert_eq!(1, clauses.len());
+            assert_eq!(1, else_targets.len());
+        }
+        _ => unreachable!(),
+    }
+}
+
 #[test]
 fn test_table_sample() {
     snowflake_and_generic().verified_stmt("SELECT * FROM testtable SAMPLE (10)");