Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions sqlglot/dialects/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,3 +1334,31 @@ def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
@unsupported_args("this")
def currentschema_sql(self, expression: exp.CurrentSchema) -> str:
return self.func("SCHEMA")

def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
from_expr = expression.args.get("from_")
if not from_expr:
return ("", "")

# Qualify unqualified columns in SET clause with the target table
# MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
target_table = expression.this
if isinstance(target_table, exp.Table):
target_name = target_table.alias_or_name
for eq in expression.expressions:
col = eq.this
if isinstance(col, exp.Column) and not col.table:
col.set("table", exp.to_identifier(target_name))

table = from_expr.this
nested_joins = table.args.get("joins") or []
if nested_joins:
table.set("joins", None)

join_sql = self.sql(exp.Join(this=table, on=exp.true()))
for nested in nested_joins:
if not nested.args.get("on") and not nested.args.get("using"):
nested.set("on", exp.true())
join_sql += self.sql(nested)

return (join_sql, "")
13 changes: 11 additions & 2 deletions sqlglot/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2223,10 +2223,19 @@ def version_sql(self, expression: exp.Version) -> str:
def tuple_sql(self, expression: exp.Tuple) -> str:
return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"

def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
"""
Returns (join_sql, from_sql) for UPDATE statements.
- join_sql: placed after UPDATE table, before SET
- from_sql: placed after SET clause (standard position)
Dialects like MySQL override to convert FROM to JOIN syntax.
"""
return ("", self.sql(expression, "from_"))

def update_sql(self, expression: exp.Update) -> str:
this = self.sql(expression, "this")
join_sql, from_sql = self._update_from_joins_sql(expression)
set_sql = self.expressions(expression, flat=True)
from_sql = self.sql(expression, "from_")
where_sql = self.sql(expression, "where")
returning = self.sql(expression, "returning")
order = self.sql(expression, "order")
Expand All @@ -2237,7 +2246,7 @@ def update_sql(self, expression: exp.Update) -> str:
expression_sql = f"{returning}{from_sql}{where_sql}"
options = self.expressions(expression, key="options")
options = f" OPTION({options})" if options else ""
sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}"
sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
return self.prepend_ctes(expression, sql)

def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
Expand Down
26 changes: 26 additions & 0 deletions tests/dialects/test_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,32 @@ def test_ddl(self):
self.validate_identity("ALTER TABLE t ALTER COLUMN c SET INVISIBLE")
self.validate_identity("ALTER TABLE t ALTER COLUMN c SET VISIBLE")

def test_update_from_to_join(self):
# MySQL multi-table UPDATE requires qualified columns in SET to avoid ambiguity
self.validate_all(
"UPDATE foo JOIN bar ON TRUE SET foo.a = bar.a WHERE foo.id = bar.id",
read={
"postgres": "UPDATE foo SET a = bar.a FROM bar WHERE foo.id = bar.id",
"mysql": "UPDATE foo JOIN bar ON TRUE SET foo.a = bar.a WHERE foo.id = bar.id",
},
)

# Multiple columns in SET clause
self.validate_all(
"UPDATE t1 JOIN t2 ON TRUE SET t1.id = t2.id, t1.name = t2.name WHERE t1.x = t2.x",
read={
"postgres": "UPDATE t1 SET id = t2.id, name = t2.name FROM t2 WHERE t1.x = t2.x",
},
)

# Already qualified columns in Postgres should remain qualified
self.validate_all(
"UPDATE t1 JOIN t2 ON TRUE SET t1.id = t2.id WHERE t1.x = t2.x",
read={
"postgres": "UPDATE t1 SET t1.id = t2.id FROM t2 WHERE t1.x = t2.x",
},
Comment on lines +218 to +220
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this isn't correct Postgres it'd be better to remove the test; SQLGlot lives by the "garbage in, garbage out" rule so it's not our responsibility to cover such cases.

)

def test_identity(self):
self.validate_identity("SELECT HIGH_PRIORITY STRAIGHT_JOIN SQL_CALC_FOUND_ROWS * FROM t")
self.validate_identity("SELECT CAST(COALESCE(`id`, 'NULL') AS CHAR CHARACTER SET binary)")
Expand Down
2 changes: 1 addition & 1 deletion tests/dialects/test_teradata.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_update(self):
"UPDATE A FROM schema.tableA AS A, (SELECT col1 FROM schema.tableA GROUP BY col1) AS B SET col2 = '' WHERE A.col1 = B.col1",
write={
"teradata": "UPDATE A FROM schema.tableA AS A, (SELECT col1 FROM schema.tableA GROUP BY col1) AS B SET col2 = '' WHERE A.col1 = B.col1",
"mysql": "UPDATE A SET col2 = '' FROM `schema`.tableA AS A, (SELECT col1 FROM `schema`.tableA GROUP BY col1) AS B WHERE A.col1 = B.col1",
"mysql": "UPDATE A JOIN `schema`.tableA AS A ON TRUE JOIN (SELECT col1 FROM `schema`.tableA GROUP BY col1) AS B ON TRUE SET A.col2 = '' WHERE A.col1 = B.col1",
},
)

Expand Down