You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/04/09 12:43:52 UTC
(datafusion-sqlparser-rs) branch main updated: MySQL: Add support for `ORDER BY` on single-table `UPDATE` (#2296)
This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 827978dc MySQL: Add support for `ORDER BY` on single-table `UPDATE` (#2296)
827978dc is described below
commit 827978dc5b581acae9711b48f9c03cd2ed47425c
Author: Donovan Schönknecht <do...@tpyo.net>
AuthorDate: Thu Apr 9 14:31:02 2026 +0200
MySQL: Add support for `ORDER BY` on single-table `UPDATE` (#2296)
---
src/ast/dml.rs | 8 +++++++
src/ast/spans.rs | 2 ++
src/dialect/generic.rs | 4 ++++
src/dialect/mod.rs | 10 +++++++++
src/dialect/mysql.rs | 5 +++++
src/parser/mod.rs | 8 +++++++
tests/sqlparser_common.rs | 2 ++
tests/sqlparser_mysql.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++
tests/sqlparser_sqlite.rs | 1 +
9 files changed, 94 insertions(+)
diff --git a/src/ast/dml.rs b/src/ast/dml.rs
index 446d44b2..3e2f7ee0 100644
--- a/src/ast/dml.rs
+++ b/src/ast/dml.rs
@@ -387,6 +387,9 @@ pub struct Update {
pub output: Option<OutputClause>,
/// SQLite-specific conflict resolution clause
pub or: Option<SqliteOnConflict>,
+ /// ORDER BY (MySQL extension for single-table UPDATE)
+ /// See <https://dev.mysql.com/doc/refman/8.4/en/update.html>
+ pub order_by: Vec<OrderByExpr>,
/// LIMIT
pub limit: Option<Expr>,
}
@@ -434,6 +437,11 @@ impl Display for Update {
f.write_str("RETURNING")?;
indented_list(f, returning)?;
}
+ if !self.order_by.is_empty() {
+ SpaceOrNewline.fmt(f)?;
+ f.write_str("ORDER BY")?;
+ indented_list(f, &self.order_by)?;
+ }
if let Some(limit) = &self.limit {
SpaceOrNewline.fmt(f)?;
write!(f, "LIMIT {limit}")?;
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index d80a3f4d..70c12de1 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -952,6 +952,7 @@ impl Spanned for Update {
returning,
output,
or: _,
+ order_by,
limit,
} = self;
@@ -963,6 +964,7 @@ impl Spanned for Update {
.chain(selection.iter().map(|i| i.span()))
.chain(returning.iter().flat_map(|i| i.iter().map(|k| k.span())))
.chain(output.iter().map(|i| i.span()))
+ .chain(order_by.iter().map(|i| i.span()))
.chain(limit.iter().map(|i| i.span())),
)
}
diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs
index c7f17351..8809bb97 100644
--- a/src/dialect/generic.rs
+++ b/src/dialect/generic.rs
@@ -133,6 +133,10 @@ impl Dialect for GenericDialect {
true
}
+ fn supports_update_order_by(&self) -> bool {
+ true
+ }
+
fn supports_from_first_select(&self) -> bool {
true
}
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index ef9cafed..bb11f3eb 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -520,6 +520,16 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the dialect supports `ORDER BY` in `UPDATE` statements.
+ ///
+ /// ```sql
+ /// UPDATE foo SET bar = false WHERE foo = true ORDER BY foo ASC;
+ /// ```
+ /// See <https://dev.mysql.com/doc/refman/8.4/en/update.html>
+ fn supports_update_order_by(&self) -> bool {
+ false
+ }
+
/// Returns true if the dialect supports an `EXCEPT` clause following a
/// wildcard in a select list.
///
diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs
index 6b057539..7c793d0d 100644
--- a/src/dialect/mysql.rs
+++ b/src/dialect/mysql.rs
@@ -179,6 +179,11 @@ impl Dialect for MySqlDialect {
true
}
+ /// See: <https://dev.mysql.com/doc/refman/8.4/en/update.html>
+ fn supports_update_order_by(&self) -> bool {
+ true
+ }
+
fn supports_data_type_signed_suffix(&self) -> bool {
true
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 9056f85d..a63352e1 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -17755,6 +17755,13 @@ impl<'a> Parser<'a> {
} else {
None
};
+ let order_by = if self.dialect.supports_update_order_by()
+ && self.parse_keywords(&[Keyword::ORDER, Keyword::BY])
+ {
+ self.parse_comma_separated(Parser::parse_order_by_expr)?
+ } else {
+ vec![]
+ };
let limit = if self.parse_keyword(Keyword::LIMIT) {
Some(self.parse_expr()?)
} else {
@@ -17770,6 +17777,7 @@ impl<'a> Parser<'a> {
returning,
output,
or,
+ order_by,
limit,
}
.into())
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index ad1e521f..c21202f0 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -535,6 +535,7 @@ fn parse_update_set_from() {
returning: None,
output: None,
or: None,
+ order_by: vec![],
limit: None
})
);
@@ -554,6 +555,7 @@ fn parse_update_with_table_alias() {
selection,
returning,
or: None,
+ order_by: _,
limit: None,
optimizer_hints,
update_token: _,
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 269787c2..6680929c 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -2707,6 +2707,7 @@ fn parse_update_with_joins() {
selection,
returning,
or: None,
+ order_by: _,
limit: None,
optimizer_hints,
update_token: _,
@@ -2784,6 +2785,59 @@ fn parse_update_with_joins() {
}
}
+#[test]
+fn parse_update_with_order_by() {
+ let sql = "UPDATE foo SET bar = false WHERE foo = true ORDER BY foo ASC";
+ match mysql_and_generic().verified_stmt(sql) {
+ Statement::Update(Update { order_by, .. }) => {
+ assert_eq!(
+ vec![OrderByExpr {
+ expr: Expr::Identifier(Ident {
+ value: "foo".to_owned(),
+ quote_style: None,
+ span: Span::empty(),
+ }),
+ options: OrderByOptions {
+ asc: Some(true),
+ nulls_first: None,
+ },
+ with_fill: None,
+ }],
+ order_by
+ );
+ }
+ _ => unreachable!(),
+ }
+}
+
+#[test]
+fn parse_update_with_order_by_and_limit() {
+ let sql = "UPDATE foo SET bar = false WHERE foo = true ORDER BY foo ASC LIMIT 10";
+ match mysql_and_generic().verified_stmt(sql) {
+ Statement::Update(Update {
+ order_by, limit, ..
+ }) => {
+ assert_eq!(
+ vec![OrderByExpr {
+ expr: Expr::Identifier(Ident {
+ value: "foo".to_owned(),
+ quote_style: None,
+ span: Span::empty(),
+ }),
+ options: OrderByOptions {
+ asc: Some(true),
+ nulls_first: None,
+ },
+ with_fill: None,
+ }],
+ order_by
+ );
+ assert_eq!(Some(Expr::value(number("10"))), limit);
+ }
+ _ => unreachable!(),
+ }
+}
+
#[test]
fn parse_delete_with_order_by() {
let sql = "DELETE FROM customers ORDER BY id DESC";
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index 33c38fb0..f9536bc2 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -497,6 +497,7 @@ fn parse_update_tuple_row_values() {
from: None,
returning: None,
output: None,
+ order_by: vec![],
limit: None,
update_token: AttachedToken::empty()
})
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org