summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-09-29 14:17:42 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-09-29 16:46:56 -0400
commitf483573aa640efb79e8b1ec6c1faac6f79d9d8fe (patch)
treed53426d5ca4424647c0bd711c8ce0c5e548e4fe0
parent147f0969301184b952366f39195caaabe6d63dbf (diff)
downloadsqlalchemy-f483573aa640efb79e8b1ec6c1faac6f79d9d8fe.tar.gz
Scan for tables without relying upon whereclause
Fixed bug where an UPDATE statement against a JOIN using MySQL multi-table format would fail to include the table prefix for the target table if the statement had no WHERE clause, as only the WHERE clause were scanned to detect a "multi table update" at that particular point. The target is now also scanned if it's a JOIN to get the leftmost table as the primary table and the additional entries as additional FROM entries. Fixes: #5617 Change-Id: I26d74afebe06e28af28acf960258f170a1627823
-rw-r--r--doc/build/changelog/unreleased_13/5617.rst11
-rw-r--r--lib/sqlalchemy/sql/dml.py6
-rw-r--r--lib/sqlalchemy/sql/util.py13
-rw-r--r--test/profiles.txt48
-rw-r--r--test/sql/test_update.py69
5 files changed, 121 insertions, 26 deletions
diff --git a/doc/build/changelog/unreleased_13/5617.rst b/doc/build/changelog/unreleased_13/5617.rst
new file mode 100644
index 000000000..da20787ca
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/5617.rst
@@ -0,0 +1,11 @@
+.. change::
+ :tags: bug, mysql
+ :tickets: 5617
+
+ Fixed bug where an UPDATE statement against a JOIN using MySQL multi-table
+ format would fail to include the table prefix for the target table if the
+ statement had no WHERE clause, as only the WHERE clause were scanned to
+ detect a "multi table update" at that particular point. The target
+ is now also scanned if it's a JOIN to get the leftmost table as the
+ primary table and the additional entries as additional FROM entries.
+
diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py
index 5ddc9ef82..c923bf651 100644
--- a/lib/sqlalchemy/sql/dml.py
+++ b/lib/sqlalchemy/sql/dml.py
@@ -12,6 +12,7 @@ Provide :class:`_expression.Insert`, :class:`_expression.Update` and
from sqlalchemy.types import NullType
from . import coercions
from . import roles
+from . import util as sql_util
from .base import _entity_namespace_key
from .base import _from_objects
from .base import _generative
@@ -47,7 +48,9 @@ class DMLState(CompileState):
def _make_extra_froms(self, statement):
froms = []
- seen = {statement.table}
+
+ all_tables = list(sql_util.tables_from_leftmost(statement.table))
+ seen = {all_tables[0]}
for crit in statement._where_criteria:
for item in _from_objects(crit):
@@ -55,6 +58,7 @@ class DMLState(CompileState):
froms.append(item)
seen.update(item._cloned_set)
+ froms.extend(all_tables[1:])
return froms
def _process_multi_values(self, statement):
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 96fa209fd..e4f7532be 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -366,6 +366,19 @@ def clause_is_present(clause, search):
return False
+def tables_from_leftmost(clause):
+ if isinstance(clause, Join):
+ for t in tables_from_leftmost(clause.left):
+ yield t
+ for t in tables_from_leftmost(clause.right):
+ yield t
+ elif isinstance(clause, FromGrouping):
+ for t in tables_from_leftmost(clause.element):
+ yield t
+ else:
+ yield clause
+
+
def surface_selectables(clause):
stack = [clause]
while stack:
diff --git a/test/profiles.txt b/test/profiles.txt
index 15f279256..4a21de427 100644
--- a/test/profiles.txt
+++ b/test/profiles.txt
@@ -153,38 +153,38 @@ test.aaa_profiling.test_compiler.CompileTest.test_update x86_64_linux_cpython_3.
# TEST: test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_mysqldb_dbapiunicode_cextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_mysqldb_dbapiunicode_nocextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_pymysql_dbapiunicode_cextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_pymysql_dbapiunicode_nocextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mssql_pyodbc_dbapiunicode_cextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mssql_pyodbc_dbapiunicode_nocextensions 154
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_mysqldb_dbapiunicode_cextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_mysqldb_dbapiunicode_nocextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_pymysql_dbapiunicode_cextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mariadb_pymysql_dbapiunicode_nocextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mssql_pyodbc_dbapiunicode_cextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mssql_pyodbc_dbapiunicode_nocextensions 159
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_mysqldb_dbapiunicode_cextensions 152
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_mysqldb_dbapiunicode_nocextensions 152
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_pymysql_dbapiunicode_cextensions 152
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_mysql_pymysql_dbapiunicode_nocextensions 152
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_oracle_cx_oracle_dbapiunicode_cextensions 150
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_oracle_cx_oracle_dbapiunicode_nocextensions 150
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_cextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 154
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 160
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_nocextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_pymysql_dbapiunicode_cextensions 160
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_pymysql_dbapiunicode_nocextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 160
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 158
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_oracle_cx_oracle_dbapiunicode_cextensions 157
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_oracle_cx_oracle_dbapiunicode_nocextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_cextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_postgresql_psycopg2_dbapiunicode_nocextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_cextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_2.7_sqlite_pysqlite_dbapiunicode_nocextensions 159
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_cextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_mysqldb_dbapiunicode_nocextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_pymysql_dbapiunicode_cextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mariadb_pymysql_dbapiunicode_nocextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_cextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mssql_pyodbc_dbapiunicode_nocextensions 165
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_cextensions 158
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_mysqldb_dbapiunicode_nocextensions 158
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_pymysql_dbapiunicode_cextensions 158
test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_mysql_pymysql_dbapiunicode_nocextensions 158
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_cextensions 156
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_nocextensions 156
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_cextensions 160
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_nocextensions 160
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 160
-test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_nocextensions 160
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_cextensions 163
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_oracle_cx_oracle_dbapiunicode_nocextensions 163
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_cextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_postgresql_psycopg2_dbapiunicode_nocextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_cextensions 165
+test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause x86_64_linux_cpython_3.8_sqlite_pysqlite_dbapiunicode_nocextensions 165
# TEST: test.aaa_profiling.test_misc.CacheKeyTest.test_statement_key_is_cached
diff --git a/test/sql/test_update.py b/test/sql/test_update.py
index 201e6c64f..ec96af207 100644
--- a/test/sql/test_update.py
+++ b/test/sql/test_update.py
@@ -1016,7 +1016,7 @@ class UpdateFromCompileTest(
dialect="mysql",
)
- def test_update_from_join_mysql(self):
+ def test_update_from_join_mysql_whereclause(self):
users, addresses = self.tables.users, self.tables.addresses
j = users.join(addresses)
@@ -1034,6 +1034,73 @@ class UpdateFromCompileTest(
dialect=mysql.dialect(),
)
+ def test_update_from_join_mysql_no_whereclause_one(self):
+ users, addresses = self.tables.users, self.tables.addresses
+
+ j = users.join(addresses)
+ self.assert_compile(
+ update(j).values(name="newname"),
+ ""
+ "UPDATE users "
+ "INNER JOIN addresses ON users.id = addresses.user_id "
+ "SET users.name=%s",
+ checkparams={"name": "newname"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_update_from_join_mysql_no_whereclause_two(self):
+ users, addresses = self.tables.users, self.tables.addresses
+
+ j = users.join(addresses)
+ self.assert_compile(
+ update(j).values({users.c.name: addresses.c.email_address}),
+ ""
+ "UPDATE users "
+ "INNER JOIN addresses ON users.id = addresses.user_id "
+ "SET users.name=addresses.email_address",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
+ def test_update_from_join_mysql_no_whereclause_three(self):
+ users, addresses, dingalings = (
+ self.tables.users,
+ self.tables.addresses,
+ self.tables.dingalings,
+ )
+
+ j = users.join(addresses).join(dingalings)
+ self.assert_compile(
+ update(j).values({users.c.name: dingalings.c.id}),
+ ""
+ "UPDATE users "
+ "INNER JOIN addresses ON users.id = addresses.user_id "
+ "INNER JOIN dingalings ON addresses.id = dingalings.address_id "
+ "SET users.name=dingalings.id",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
+ def test_update_from_join_mysql_no_whereclause_four(self):
+ users, addresses, dingalings = (
+ self.tables.users,
+ self.tables.addresses,
+ self.tables.dingalings,
+ )
+
+ j = users.join(addresses).join(dingalings)
+
+ self.assert_compile(
+ update(j).values(name="foo"),
+ ""
+ "UPDATE users "
+ "INNER JOIN addresses ON users.id = addresses.user_id "
+ "INNER JOIN dingalings ON addresses.id = dingalings.address_id "
+ "SET users.name=%s",
+ checkparams={"name": "foo"},
+ dialect=mysql.dialect(),
+ )
+
def test_render_table(self):
users, addresses = self.tables.users, self.tables.addresses