]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Perform additional retrieval of correct column names
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 2 Oct 2018 21:49:44 +0000 (17:49 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 3 Oct 2018 13:53:22 +0000 (09:53 -0400)
Added a workaround for a MySQL bug #88718 introduced in the 8.0 series,
where the reflection of a foreign key constraint is not reporting the
correct case sensitivity for the referred column, leading to errors during
use of the reflected constraint such as when using the automap extension.
The workaround emits an additional query to the information_schema tables in
order to retrieve the correct case sensitive name.

Fixes: #4344
Change-Id: I08020d6eec43cbe8a56316660380d3739a0b45f7
(cherry picked from commit 56fb68ca8620a211ca29b3d47d649dfa332d354a)

doc/build/changelog/unreleased_12/4344.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/base.py
test/dialect/mysql/test_reflection.py

diff --git a/doc/build/changelog/unreleased_12/4344.rst b/doc/build/changelog/unreleased_12/4344.rst
new file mode 100644 (file)
index 0000000..05be223
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+   :tags: bug, mysql
+   :tickets: 4344
+
+   Added a workaround for a MySQL bug #88718 introduced in the 8.0 series,
+   where the reflection of a foreign key constraint is not reporting the
+   correct case sensitivity for the referred column, leading to errors during
+   use of the reflected constraint such as when using the automap extension.
+   The workaround emits an additional query to the information_schema tables in
+   order to retrieve the correct case sensitive name.
index caf11cfe62aad572068b23be9f3b106cf5666bba..2a52de06cfd2fc8af4ec0a0729651d873a21ac2b 100644 (file)
@@ -695,6 +695,7 @@ output::
 
 """
 
+from collections import defaultdict
 import re
 import sys
 import json
@@ -1920,6 +1921,10 @@ class MySQLDialect(default.DefaultDialect):
 
         default.DefaultDialect.initialize(self, connection)
 
+        self._needs_correct_for_88718 = (
+            not self._is_mariadb and self.server_version_info >= (8, )
+        )
+
         self._warn_for_known_db_issues()
 
     def _warn_for_known_db_issues(self):
@@ -2071,8 +2076,56 @@ class MySQLDialect(default.DefaultDialect):
                 'options': con_kw
             }
             fkeys.append(fkey_d)
+
+        if self._needs_correct_for_88718:
+            self._correct_for_mysql_bug_88718(fkeys, connection)
+
         return fkeys
 
+    def _correct_for_mysql_bug_88718(self, fkeys, connection):
+        # Foreign key is always in lower case (MySQL 8.0)
+        # https://bugs.mysql.com/bug.php?id=88718
+        # issue #4344 for SQLAlchemy
+
+        default_schema_name = connection.dialect.default_schema_name
+        col_tuples = [
+            (
+                rec['referred_schema'] or default_schema_name,
+                rec['referred_table'],
+                col_name
+            )
+            for rec in fkeys
+            for col_name in rec['referred_columns']
+        ]
+
+        if col_tuples:
+
+            correct_for_wrong_fk_case = connection.execute(
+                sql.text("""
+                    select table_schema, table_name, column_name
+                    from information_schema.columns
+                    where (table_schema, table_name, lower(column_name)) in
+                    :table_data;
+                """).bindparams(
+                    sql.bindparam("table_data", expanding=True)
+                ), table_data=col_tuples
+            )
+
+            d = defaultdict(dict)
+            for schema, tname, cname in correct_for_wrong_fk_case:
+                d[(schema, tname)][cname.lower()] = cname
+
+            for fkey in fkeys:
+                fkey['referred_columns'] = [
+                    d[
+                        (
+                            fkey['referred_schema'] or default_schema_name,
+                            fkey['referred_table']
+                        )
+                    ][col.lower()]
+                    for col in fkey['referred_columns']
+                ]
+
     @reflection.cache
     def get_check_constraints(
             self, connection, table_name, schema=None, **kw):
index 6b126ca0a1702c370a070c8419f4658261f8c8b9..1063af588e5539e6f01fa781eb8d60599f8708a4 100644 (file)
@@ -4,7 +4,7 @@ from sqlalchemy.testing import eq_, is_
 from sqlalchemy import Column, Table, DDL, MetaData, TIMESTAMP, \
     DefaultClause, String, Integer, Text, UnicodeText, SmallInteger,\
     NCHAR, LargeBinary, DateTime, select, UniqueConstraint, Unicode,\
-    BigInteger, Index
+    BigInteger, Index, ForeignKey
 from sqlalchemy import event
 from sqlalchemy import sql
 from sqlalchemy import exc
@@ -596,6 +596,50 @@ class ReflectionTest(fixtures.TestBase, AssertsExecutionResults):
             [{'name': 'foo_idx', 'column_names': ['x'], 'unique': False}]
         )
 
+    @testing.provide_metadata
+    def test_case_sensitive_column_constraint_reflection(self):
+        # test for issue #4344 which works around
+        # MySQL 8.0 bug https://bugs.mysql.com/bug.php?id=88718
+
+        m1 = self.metadata
+
+        Table(
+            'Track', m1, Column('TrackID', Integer, primary_key=True)
+        )
+        Table(
+            'Track', m1, Column('TrackID', Integer, primary_key=True),
+            schema=testing.config.test_schema
+        )
+        Table(
+            'PlaylistTrack', m1, Column('id', Integer, primary_key=True),
+            Column('TrackID',
+                   ForeignKey('Track.TrackID', name='FK_PlaylistTrackId')),
+            Column(
+                'TTrackID',
+                ForeignKey(
+                    '%s.Track.TrackID' % (testing.config.test_schema,),
+                    name='FK_PlaylistTTrackId'
+                )
+            )
+        )
+        m1.create_all()
+
+        eq_(
+            inspect(testing.db).get_foreign_keys('PlaylistTrack'),
+            [
+                {'name': 'FK_PlaylistTTrackId',
+                 'constrained_columns': ['TTrackID'],
+                 'referred_schema': testing.config.test_schema,
+                 'referred_table': 'Track',
+                 'referred_columns': ['TrackID'], 'options': {}},
+                {'name': 'FK_PlaylistTrackId',
+                 'constrained_columns': ['TrackID'],
+                 'referred_schema': None,
+                 'referred_table': 'Track',
+                 'referred_columns': ['TrackID'], 'options': {}}
+            ]
+        )
+
 
 class RawReflectionTest(fixtures.TestBase):
     __backend__ = True