]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
narrow scope of _correct_for_mysql_bugs_88718_96365
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 20 Apr 2026 13:30:35 +0000 (09:30 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 21 Apr 2026 14:57:46 +0000 (10:57 -0400)
Narrowed the scope of the internal workaround for MySQL bugs `#88718
<https://bugs.mysql.com/bug.php?id=88718>`_ and `#96365
<https://bugs.mysql.com/bug.php?id=96365>`_ so that it is only applied
where needed: MySQL 8.0.1 through 8.0.13 (where bug 88718 is present), and
on systems with ``lower_case_table_names=2`` (where bug 96365 applies,
typically macOS).  Previously the workaround was applied unconditionally
for all MySQL 8.0+ versions, which caused a ``KeyError`` during foreign key
reflection when the database user lacked SELECT privileges on referred
tables.

Fixes: #13243
Change-Id: I7c29f67d1653c5cd32f29e098f038fea1d56117b

doc/build/changelog/unreleased_20/13243.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/base.py
test/dialect/mysql/test_dialect.py

diff --git a/doc/build/changelog/unreleased_20/13243.rst b/doc/build/changelog/unreleased_20/13243.rst
new file mode 100644 (file)
index 0000000..2a19b9e
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+    :tags: bug, mysql, reflection
+    :tickets: 13243
+
+    Narrowed the scope of the internal workaround for MySQL bugs `#88718
+    <https://bugs.mysql.com/bug.php?id=88718>`_ and `#96365
+    <https://bugs.mysql.com/bug.php?id=96365>`_ so that it is only applied
+    where needed: MySQL 8.0.1 through 8.0.13 (where bug 88718 is present), and
+    on systems with ``lower_case_table_names=2`` (where bug 96365 applies,
+    typically macOS).  Previously the workaround was applied unconditionally
+    for all MySQL 8.0+ versions, which caused a ``KeyError`` during foreign key
+    reflection when the database user lacked SELECT privileges on referred
+    tables.
index d2e7068d100a8935e44f17ca97fb95cc440348ae..908d1b5cb0e8804d82fee818ac45aa9f9de1f05e 100644 (file)
@@ -2784,6 +2784,7 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
     # i.e. first connect
     _backslash_escapes = True
     _server_ansiquotes = False
+    _casing = 0
     _support_default_function = True
     _support_float_cast = False
 
@@ -3177,7 +3178,9 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
 
         self.use_mysql_for_share = self.server_version_info >= (8, 0, 1)
 
-        self._needs_correct_for_88718_96365 = self.server_version_info >= (8,)
+        self._needs_correct_for_88718_96365 = self.server_version_info >= (
+            8,
+        ) and (self.server_version_info < (8, 0, 14) or self._casing == 2)
 
         self._requires_alias_for_on_duplicate_key = (
             self.server_version_info >= (8, 0, 20)
index 41bf091c5a1fcf4e26af28c4408411331b5b73c5..eaf86c15d25ab7e35f9e1e06e1839b4addebe499 100644 (file)
@@ -64,6 +64,42 @@ class BackendDialectTest(
 
             yield go
 
+    @testing.fixture
+    def mysql_version_casing_dialect(self, mysql_version_dialect):
+        """yield a MySQL engine that will simulate a specific version
+        and casing setting.
+
+        Builds on mysql_version_dialect, additionally patching out
+        _detect_casing and _detect_sql_mode so that
+        dialect.initialize() can be called with controlled values.
+
+        """
+        _patchers = []
+
+        def go(server_version, casing):
+            engine = mysql_version_dialect(server_version)
+
+            def _mock_detect_casing(conn):
+                engine.dialect._casing = casing
+
+            def _mock_detect_sql_mode(conn):
+                engine.dialect._sql_mode = ""
+
+            for method, fn in [
+                ("_detect_casing", _mock_detect_casing),
+                ("_detect_sql_mode", _mock_detect_sql_mode),
+            ]:
+                p = mock.patch.object(engine.dialect, method, fn)
+                p.start()
+                _patchers.append(p)
+
+            return engine
+
+        yield go
+
+        for p in _patchers:
+            p.stop()
+
     def test_reserved_words_mysql_vs_mariadb(
         self, mysql_mariadb_reserved_words
     ):
@@ -185,6 +221,29 @@ class BackendDialectTest(
             c = testing.db.connect().execution_options(isolation_level=value)
             eq_(testing.db.dialect.get_isolation_level(c.connection), value)
 
+    @testing.only_on("mysql")
+    @testing.combinations(
+        ("8.0.12", 0, True),
+        ("8.0.13", 0, True),
+        ("8.0.13", 2, True),
+        ("8.0.14", 0, False),
+        ("8.0.14", 1, False),
+        ("8.0.14", 2, True),
+        ("8.0.32", 0, False),
+        ("8.0.32", 2, True),
+        ("8.4.0", 0, False),
+        ("8.4.0", 2, True),
+        ("5.7.0", 0, False),
+        ("5.7.0", 2, False),
+        argnames="server_version,casing,expected",
+    )
+    def test_needs_correct_for_88718_96365(
+        self, mysql_version_casing_dialect, server_version, casing, expected
+    ):
+        engine = mysql_version_casing_dialect(server_version, casing)
+        engine.connect()
+        is_(engine.dialect._needs_correct_for_88718_96365, expected)
+
 
 class DialectTest(fixtures.TestBase):
     __backend__ = True