STET

agent_patch

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

9479 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1604.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/changelog/0.53.0.md b/app/changelog/0.53.0.md
index 5b9de07..8409741 100644
--- a/tmp/agent-patch-flux-pr-1604.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/changelog/0.53.0.md
+++ b/app/changelog/0.53.0.md
@@ -67,6 +67,8 @@ This release consists of 47 commits from 16 contributors. See credits at the end
 - Support INSERT OVERWRITE INTO syntax [#1584](https://github.com/apache/datafusion-sqlparser-rs/pull/1584) (yuval-illumex)
 - Parse `INSERT` with subquery when lacking column names [#1586](https://github.com/apache/datafusion-sqlparser-rs/pull/1586) (iffyio)
 - Add support for ODBC functions [#1585](https://github.com/apache/datafusion-sqlparser-rs/pull/1585) (iffyio)
+- Support Snowflake `UPDATE ... FROM` statements with the `FROM` clause before `SET`
+  and preserve the clause position in the AST [#1604](https://github.com/apache/datafusion-sqlparser-rs/pull/1604)
 
 ## Credits
 
@@ -92,4 +94,3 @@ Thank you to everyone who contributed to this release. Here is a breakdown of co
 ```
 
 Thank you also to everyone who contributed in other ways such as filing issues, reviewing PRs, and providing feedback on this release.
-
diff --git a/tmp/agent-patch-flux-pr-1604.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 9fb2bb9..ebb5a53 100644
--- a/tmp/agent-patch-flux-pr-1604.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
@@ -2472,8 +2472,8 @@ pub enum Statement {
         table: TableWithJoins,
         /// Column assignments
         assignments: Vec<Assignment>,
-        /// Table which provide value to be set
-        from: Option<TableWithJoins>,
+        /// `FROM` clause along with its relative position to `SET`
+        from: Option<UpdateFrom>,
         /// WHERE
         selection: Option<Expr>,
         /// RETURNING
@@ -3403,6 +3403,25 @@ pub enum Statement {
     },
 }
 
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+pub enum UpdateFrom {
+    /// The `FROM` clause appears before `SET` (Snowflake-style syntax).
+    BeforeSet(TableWithJoins),
+    /// The `FROM` clause appears after `SET` (standard syntax).
+    AfterSet(TableWithJoins),
+}
+
+impl UpdateFrom {
+    /// Returns the `TableWithJoins` referenced by this `FROM` clause.
+    pub fn table(&self) -> &TableWithJoins {
+        match self {
+            UpdateFrom::BeforeSet(table) | UpdateFrom::AfterSet(table) => table,
+        }
+    }
+}
+
 impl fmt::Display for Statement {
     // Clippy thinks this function is too complicated, but it is painful to
     // split up without extracting structs for each `Statement` variant.
@@ -3745,11 +3764,14 @@ impl fmt::Display for Statement {
                     write!(f, "{or} ")?;
                 }
                 write!(f, "{table}")?;
+                if let Some(UpdateFrom::BeforeSet(from_table)) = from.as_ref() {
+                    write!(f, " FROM {from_table}")?;
+                }
                 if !assignments.is_empty() {
                     write!(f, " SET {}", display_comma_separated(assignments))?;
                 }
-                if let Some(from) = from {
-                    write!(f, " FROM {from}")?;
+                if let Some(UpdateFrom::AfterSet(from_table)) = from.as_ref() {
+                    write!(f, " FROM {from_table}")?;
                 }
                 if let Some(selection) = selection {
                     write!(f, " WHERE {selection}")?;
diff --git a/tmp/agent-patch-flux-pr-1604.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 9ba3bdd..4e45da2 100644
--- a/tmp/agent-patch-flux-pr-1604.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
@@ -33,6 +33,7 @@ use super::{
     RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
     SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
     TableConstraint, TableFactor, TableOptionsClustered, TableWithJoins, Use, Value, Values,
+    UpdateFrom,
     ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
 };
 
@@ -355,7 +356,7 @@ impl Spanned for Statement {
             } => union_spans(
                 core::iter::once(table.span())
                     .chain(assignments.iter().map(|i| i.span()))
-                    .chain(from.iter().map(|i| i.span()))
+                    .chain(from.iter().map(|i| i.table().span()))
                     .chain(selection.iter().map(|i| i.span()))
                     .chain(returning.iter().flat_map(|i| i.iter().map(|k| k.span()))),
             ),
diff --git a/tmp/agent-patch-flux-pr-1604.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 cc0a57e..9fb5f54 100644
--- a/tmp/agent-patch-flux-pr-1604.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
@@ -11791,12 +11791,26 @@ impl<'a> Parser<'a> {
     pub fn parse_update(&mut self) -> Result<Statement, ParserError> {
         let or = self.parse_conflict_clause();
         let table = self.parse_table_and_joins()?;
+        let from_before_set = if self.parse_keyword(Keyword::FROM)
+            && dialect_of!(self is SnowflakeDialect)
+        {
+            Some(UpdateFrom::BeforeSet(self.parse_table_and_joins()?))
+        } else {
+            None
+        };
         self.expect_keyword(Keyword::SET)?;
         let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
-        let from = if self.parse_keyword(Keyword::FROM)
-            && dialect_of!(self is GenericDialect | PostgreSqlDialect | DuckDbDialect | BigQueryDialect | SnowflakeDialect | RedshiftSqlDialect | MsSqlDialect | SQLiteDialect )
+        let from_after_set = if self.parse_keyword(Keyword::FROM)
+            && dialect_of!(self is GenericDialect
+                | PostgreSqlDialect
+                | DuckDbDialect
+                | BigQueryDialect
+                | SnowflakeDialect
+                | RedshiftSqlDialect
+                | MsSqlDialect
+                | SQLiteDialect)
         {
-            Some(self.parse_table_and_joins()?)
+            Some(UpdateFrom::AfterSet(self.parse_table_and_joins()?))
         } else {
             None
         };
@@ -11810,6 +11824,7 @@ impl<'a> Parser<'a> {
         } else {
             None
         };
+        let from = from_before_set.or(from_after_set);
         Ok(Statement::Update {
             table,
             assignments,
diff --git a/tmp/agent-patch-flux-pr-1604.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 79f5c8d..42e322d 100644
--- a/tmp/agent-patch-flux-pr-1604.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
@@ -366,7 +366,7 @@ fn parse_update_set_from() {
                 target: AssignmentTarget::ColumnName(ObjectName(vec![Ident::new("name")])),
                 value: Expr::CompoundIdentifier(vec![Ident::new("t2"), Ident::new("name")])
             }],
-            from: Some(TableWithJoins {
+            from: Some(UpdateFrom::AfterSet(TableWithJoins {
                 relation: TableFactor::Derived {
                     lateral: false,
                     subquery: Box::new(Query {
diff --git a/tmp/agent-patch-flux-pr-1604.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_snowflake.rs b/app/tests/sqlparser_snowflake.rs
index 9fe1478..ca4deab 100644
--- a/tmp/agent-patch-flux-pr-1604.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_snowflake.rs
+++ b/app/tests/sqlparser_snowflake.rs
@@ -1278,6 +1278,47 @@ fn snowflake_and_generic() -> TestedDialects {
     ])
 }
 
+#[test]
+fn test_snowflake_update_from_before_set() {
+    let sql = "UPDATE t1 FROM t2 SET t1.col = t2.col";
+    match snowflake().verified_stmt(sql) {
+        Statement::Update {
+            table,
+            assignments,
+            from: Some(UpdateFrom::BeforeSet(from_table)),
+            selection,
+            returning,
+            or,
+        } => {
+            assert_eq!(
+                table,
+                table_from_name(ObjectName(vec![Ident::new("t1")])),
+            );
+            assert_eq!(
+                from_table,
+                table_from_name(ObjectName(vec![Ident::new("t2")])),
+            );
+            assert_eq!(
+                assignments,
+                vec![Assignment {
+                    target: AssignmentTarget::ColumnName(ObjectName(vec![
+                        Ident::new("t1"),
+                        Ident::new("col")
+                    ])),
+                    value: Expr::CompoundIdentifier(vec![
+                        Ident::new("t2"),
+                        Ident::new("col")
+                    ]),
+                }],
+            );
+            assert_eq!(selection, None);
+            assert_eq!(returning, None);
+            assert_eq!(or, None);
+        }
+        _ => unreachable!(),
+    }
+}
+
 #[test]
 fn test_select_wildcard_with_exclude() {
     let select = snowflake_and_generic().verified_only_select("SELECT * EXCLUDE (col_a) FROM data");