From: Mike Bayer Date: Tue, 2 Oct 2018 21:49:44 +0000 (-0400) Subject: Perform additional retrieval of correct column names X-Git-Tag: rel_1_3_0b1~53 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=56fb68ca8620a211ca29b3d47d649dfa332d354a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Perform additional retrieval of correct column names 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 --- diff --git a/doc/build/changelog/unreleased_12/4344.rst b/doc/build/changelog/unreleased_12/4344.rst new file mode 100644 index 0000000000..05be223d9c --- /dev/null +++ b/doc/build/changelog/unreleased_12/4344.rst @@ -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. diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 45e8c25104..cd17bcdc48 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -733,6 +733,7 @@ output:: """ +from collections import defaultdict import re import sys import json @@ -1979,6 +1980,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): @@ -2130,8 +2135,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): diff --git a/test/dialect/mysql/test_reflection.py b/test/dialect/mysql/test_reflection.py index c244ba9727..c739d3c4f1 100644 --- a/test/dialect/mysql/test_reflection.py +++ b/test/dialect/mysql/test_reflection.py @@ -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.schema import CreateIndex from sqlalchemy import event from sqlalchemy import sql @@ -647,6 +647,50 @@ class ReflectionTest(fixtures.TestBase, AssertsCompiledSQL): [{'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