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