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
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)");