Skip to content

Commit

Permalink
Perform additional retrieval of correct column names
Browse files Browse the repository at this point in the history
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
  • Loading branch information
zzzeek committed Oct 3, 2018
1 parent ffd27ce commit 56fb68c
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 1 deletion.
10 changes: 10 additions & 0 deletions doc/build/changelog/unreleased_12/4344.rst
Original file line number Diff line number Diff line change
@@ -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.
53 changes: 53 additions & 0 deletions lib/sqlalchemy/dialects/mysql/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ def process(element, compiler, **kw):
"""

from collections import defaultdict
import re
import sys
import json
Expand Down Expand Up @@ -1979,6 +1980,10 @@ def initialize(self, connection):

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):
Expand Down Expand Up @@ -2130,8 +2135,56 @@ def get_foreign_keys(self, connection, table_name, schema=None, **kw):
'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):
Expand Down
46 changes: 45 additions & 1 deletion test/dialect/mysql/test_reflection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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
Expand Down Expand Up @@ -647,6 +647,50 @@ def test_non_column_index(self):
[{'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
Expand Down

0 comments on commit 56fb68c

Please sign in to comment.