STET

agent_patch

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

19224 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/dml.rs b/app/src/ast/dml.rs
index 4c36f70..cc24399 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/dml.rs
+++ b/app/src/ast/dml.rs
@@ -36,6 +36,93 @@ use super::{
     TableFactor, TableObject, TableWithJoins, UpdateTableFromKind, Values,
 };
 
+#[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 {
+    All,
+    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"),
+        }
+    }
+}
+
+#[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 {
+    pub table: TableObject,
+    pub columns: Vec<Ident>,
+    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(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MultiInsertConditionalBranch {
+    pub condition: Expr,
+    pub targets: Vec<MultiInsertTarget>,
+}
+
+impl Display for MultiInsertConditionalBranch {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "WHEN {} THEN", self.condition)?;
+        for target in &self.targets {
+            write!(f, " {target}")?;
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MultiTableInsert {
+    pub mode: MultiInsertMode,
+    pub unconditional_targets: Vec<MultiInsertTarget>,
+    pub conditional_branches: Vec<MultiInsertConditionalBranch>,
+    pub else_targets: Vec<MultiInsertTarget>,
+    pub source: Box<Query>,
+}
+
+impl Display for MultiTableInsert {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.mode)?;
+        for target in &self.unconditional_targets {
+            write!(f, " {target}")?;
+        }
+        for branch in &self.conditional_branches {
+            write!(f, " {branch}")?;
+        }
+        if !self.else_targets.is_empty() {
+            f.write_str(" ELSE")?;
+            for target in &self.else_targets {
+                write!(f, " {target}")?;
+            }
+        }
+        write!(f, " {}", self.source)
+    }
+}
+
 /// INSERT statement.
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -64,6 +151,8 @@ pub struct Insert {
     pub overwrite: bool,
     /// A SQL query that specifies what to insert
     pub source: Option<Box<Query>>,
+    /// Snowflake multi-table INSERT statement.
+    pub multi_table: Option<MultiTableInsert>,
     /// MySQL `INSERT INTO ... SET`
     /// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
     pub assignments: Vec<Assignment>,
@@ -100,6 +189,15 @@ pub struct Insert {
 
 impl Display for Insert {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(multi_table) = &self.multi_table {
+            f.write_str("INSERT")?;
+            if let Some(hint) = self.optimizer_hint.as_ref() {
+                write!(f, " {hint}")?;
+            }
+            write!(f, " {multi_table}")?;
+            return Ok(());
+        }
+
         let table_name = if let Some(alias) = &self.table_alias {
             format!("{0} AS {alias}", self.table)
         } else {
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index cc34ec7..4ce7436 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/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, MultiInsertConditionalBranch, MultiInsertMode,
+    MultiInsertTarget, MultiTableInsert, OutputClause, Update,
 };
 pub use self::operator::{BinaryOperator, UnaryOperator};
 pub use self::query::{
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs b/app/src/ast/spans.rs
index bdd430e..ad7f5da 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -39,14 +39,15 @@ 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,
-    ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
-    OrderByExpr, OrderByKind, OutputClause, Partition, PartitionBoundValue, PivotValueSource,
-    ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction,
-    RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
-    SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
-    TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins, Update,
-    UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement,
+    MergeInsertKind, MergeUpdateExpr, MultiInsertConditionalBranch, MultiInsertTarget,
+    MultiTableInsert, NamedParenthesizedList, NamedWindowDefinition, ObjectName, ObjectNamePart,
+    Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy, OrderByExpr,
+    OrderByKind, OutputClause, Partition, PartitionBoundValue, PivotValueSource, ProjectionSelect,
+    Query, RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem,
+    ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption,
+    Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint,
+    TableFactor, TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind,
+    Use, Value, Values, ViewColumnDef, WhileStatement,
     WildcardAdditionalOptions, With, WithFill,
 };
 
@@ -1303,6 +1304,7 @@ impl Spanned for Insert {
             columns,
             overwrite: _, // bool
             source,
+            multi_table,
             partitioned,
             after_columns,
             has_table_keyword: _, // bool
@@ -1322,6 +1324,7 @@ impl Spanned for Insert {
                 .chain(table_alias.as_ref().map(|i| i.span))
                 .chain(columns.iter().map(|i| i.span))
                 .chain(source.as_ref().map(|q| q.span()))
+                .chain(multi_table.as_ref().map(|q| q.span()))
                 .chain(assignments.iter().map(|i| i.span()))
                 .chain(partitioned.iter().flat_map(|i| i.iter().map(|k| k.span())))
                 .chain(after_columns.iter().map(|i| i.span))
@@ -1331,6 +1334,51 @@ impl Spanned for Insert {
     }
 }
 
+impl Spanned for MultiTableInsert {
+    fn span(&self) -> Span {
+        let MultiTableInsert {
+            mode: _,
+            unconditional_targets,
+            conditional_branches,
+            else_targets,
+            source,
+        } = self;
+
+        union_spans(
+            unconditional_targets
+                .iter()
+                .map(|target| target.span())
+                .chain(conditional_branches.iter().map(|branch| branch.span()))
+                .chain(else_targets.iter().map(|target| target.span()))
+                .chain(core::iter::once(source.span())),
+        )
+    }
+}
+
+impl Spanned for MultiInsertConditionalBranch {
+    fn span(&self) -> Span {
+        let MultiInsertConditionalBranch { condition, targets } = self;
+        union_spans(
+            core::iter::once(condition.span()).chain(targets.iter().map(|target| target.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(|column| column.span))
+                .chain(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-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 8f3ae38..80f8006 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -17005,6 +17005,40 @@ impl<'a> Parser<'a> {
 
         let local = self.parse_keyword(Keyword::LOCAL);
 
+        if dialect_of!(self is SnowflakeDialect)
+            && !overwrite
+            && !into
+            && !local
+            && self.peek_one_of_keywords(&[Keyword::ALL, Keyword::FIRST]).is_some()
+        {
+            let multi_table = self.parse_snowflake_multi_table_insert()?;
+            return Ok(Insert {
+                insert_token: insert_token.into(),
+                optimizer_hint,
+                or,
+                ignore,
+                into,
+                table: TableObject::TableName(ObjectName(vec![])),
+                table_alias: None,
+                columns: vec![],
+                overwrite,
+                source: None,
+                multi_table: Some(multi_table),
+                assignments: vec![],
+                partitioned: None,
+                after_columns: vec![],
+                has_table_keyword: false,
+                on: None,
+                returning: None,
+                replace_into,
+                priority,
+                insert_alias: None,
+                settings: None,
+                format_clause: None,
+            }
+            .into());
+        }
+
         if self.parse_keyword(Keyword::DIRECTORY) {
             let path = self.parse_literal_string()?;
             let file_format = if self.parse_keywords(&[Keyword::STORED, Keyword::AS]) {
@@ -17162,6 +17196,7 @@ impl<'a> Parser<'a> {
                 columns,
                 after_columns,
                 source,
+                multi_table: None,
                 assignments,
                 has_table_keyword: table,
                 on,
@@ -17176,6 +17211,82 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_snowflake_multi_table_insert(&mut self) -> Result<MultiTableInsert, ParserError> {
+        let mode = match self.expect_one_of_keywords(&[Keyword::ALL, Keyword::FIRST])? {
+            Keyword::ALL => MultiInsertMode::All,
+            Keyword::FIRST => MultiInsertMode::First,
+            unexpected_keyword => {
+                return Err(ParserError::ParserError(format!(
+                    "Internal parser error: expected ALL or FIRST, got {unexpected_keyword:?}"
+                )))
+            }
+        };
+
+        let mut unconditional_targets = vec![];
+        let mut conditional_branches = vec![];
+        loop {
+            if self.parse_keyword(Keyword::INTO) {
+                unconditional_targets.push(self.parse_multi_insert_target()?);
+            } else if self.parse_keyword(Keyword::WHEN) {
+                let condition = self.parse_expr()?;
+                self.expect_keyword_is(Keyword::THEN)?;
+                let mut targets = vec![];
+                while self.parse_keyword(Keyword::INTO) {
+                    targets.push(self.parse_multi_insert_target()?);
+                }
+                if targets.is_empty() {
+                    return self.expected("INTO after THEN", self.peek_token());
+                }
+                conditional_branches.push(MultiInsertConditionalBranch { condition, targets });
+            } else {
+                break;
+            }
+        }
+
+        let else_targets = if self.parse_keyword(Keyword::ELSE) {
+            let mut targets = vec![];
+            while self.parse_keyword(Keyword::INTO) {
+                targets.push(self.parse_multi_insert_target()?);
+            }
+            if targets.is_empty() {
+                return self.expected("INTO after ELSE", self.peek_token());
+            }
+            targets
+        } else {
+            vec![]
+        };
+
+        if unconditional_targets.is_empty() && conditional_branches.is_empty() && else_targets.is_empty()
+        {
+            return self.expected("INTO or WHEN", self.peek_token());
+        }
+
+        let source = Box::new(self.parse_query()?);
+        Ok(MultiTableInsert {
+            mode,
+            unconditional_targets,
+            conditional_branches,
+            else_targets,
+            source,
+        })
+    }
+
+    fn parse_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, false)?)
+        } 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-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index cfb0373..a1096a2 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -5443,6 +5443,7 @@ fn test_simple_postgres_insert_with_alias() {
                 format_clause: None,
                 pipe_operators: vec![],
             })),
+            multi_table: None,
             assignments: vec![],
             partitioned: None,
             after_columns: vec![],
@@ -5518,6 +5519,7 @@ fn test_simple_postgres_insert_with_alias() {
                 format_clause: None,
                 pipe_operators: vec![],
             })),
+            multi_table: None,
             assignments: vec![],
             partitioned: None,
             after_columns: vec![],
@@ -5591,6 +5593,7 @@ fn test_simple_insert_with_quoted_alias() {
                 format_clause: None,
                 pipe_operators: vec![],
             })),
+            multi_table: None,
             assignments: vec![],
             partitioned: None,
             after_columns: vec![],
diff --git a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_snowflake.rs b/app/tests/sqlparser_snowflake.rs
index 014a241..ac78477 100644
--- a/tmp/agent-patch-flux-pr-2148.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_snowflake.rs
+++ b/app/tests/sqlparser_snowflake.rs
@@ -3388,6 +3388,79 @@ fn parse_insert_overwrite() {
     snowflake().verified_stmt(insert_overwrite_into);
 }
 
+#[test]
+fn parse_multi_table_insert_all() {
+    let sql = "INSERT ALL INTO t1 INTO t2 (a, b) VALUES (c1, c2) SELECT c1, c2 FROM src";
+
+    match snowflake().verified_stmt(sql) {
+        Statement::Insert(Insert {
+            source: None,
+            multi_table: Some(multi_table),
+            ..
+        }) => {
+            assert_eq!(MultiInsertMode::All, multi_table.mode);
+            assert!(multi_table.conditional_branches.is_empty());
+            assert!(multi_table.else_targets.is_empty());
+            assert_eq!(2, multi_table.unconditional_targets.len());
+            assert_eq!("t1", multi_table.unconditional_targets[0].table.to_string());
+            assert_eq!("t2", multi_table.unconditional_targets[1].table.to_string());
+            assert_eq!(
+                &vec![Ident::new("a"), Ident::new("b")],
+                &multi_table.unconditional_targets[1].columns
+            );
+            assert_eq!(
+                Some(&Values {
+                    explicit_row: false,
+                    value_keyword: false,
+                    rows: vec![vec![
+                        Expr::Identifier(Ident::new("c1")),
+                        Expr::Identifier(Ident::new("c2")),
+                    ]],
+                }),
+                multi_table.unconditional_targets[1].values.as_ref()
+            );
+            assert_eq!("SELECT c1, c2 FROM src", multi_table.source.to_string());
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_multi_table_insert_first_with_conditions() {
+    let sql = "INSERT FIRST WHEN score >= 90 THEN INTO top_students (name) VALUES (name) WHEN score >= 70 THEN INTO passing_students VALUES (name) ELSE INTO other_students VALUES (name) SELECT name, score FROM students";
+
+    match snowflake().verified_stmt(sql) {
+        Statement::Insert(Insert {
+            source: None,
+            multi_table: Some(multi_table),
+            ..
+        }) => {
+            assert_eq!(MultiInsertMode::First, multi_table.mode);
+            assert!(multi_table.unconditional_targets.is_empty());
+            assert_eq!(2, multi_table.conditional_branches.len());
+            assert_eq!(1, multi_table.else_targets.len());
+            assert_eq!(
+                "score >= 90",
+                multi_table.conditional_branches[0].condition.to_string()
+            );
+            assert_eq!(
+                &vec![Ident::new("name")],
+                &multi_table.conditional_branches[0].targets[0].columns
+            );
+            assert_eq!(
+                Some(&Values {
+                    explicit_row: false,
+                    value_keyword: false,
+                    rows: vec![vec![Expr::Identifier(Ident::new("name"))]],
+                }),
+                multi_table.else_targets[0].values.as_ref()
+            );
+            assert_eq!("SELECT name, score FROM students", multi_table.source.to_string());
+        }
+        _ => unreachable!(),
+    }
+}
+
 #[test]
 fn test_table_sample() {
     snowflake_and_generic().verified_stmt("SELECT * FROM testtable SAMPLE (10)");