self._connection_charset = self._detect_charset(connection)
self._detect_sql_mode(connection)
self._detect_ansiquotes(connection)
+ self._detect_casing(connection)
if self._server_ansiquotes:
# if ansiquotes == True, build a new IdentifierPreparer
# with the new setting
# https://bugs.mysql.com/bug.php?id=88718
# issue #4344 for SQLAlchemy
+ # for lower_case_table_names=2, information_schema.columns
+ # preserves the original table/schema casing, but SHOW CREATE
+ # TABLE does not. this problem is not in lower_case_table_names=1,
+ # but use case-insensitive matching for these two modes in any case.
+ if self._casing in (1, 2):
+ lower = str.lower
+ else:
+ # if on case sensitive, there can be two tables referenced
+ # with the same name different casing, so we need to use
+ # case-sensitive matching.
+ def lower(s):
+ return s
+
default_schema_name = connection.dialect.default_schema_name
col_tuples = [
(
- rec['referred_schema'] or default_schema_name,
- rec['referred_table'],
+ lower(rec['referred_schema'] or default_schema_name),
+ lower(rec['referred_table']),
col_name
)
for rec in fkeys
), table_data=col_tuples
)
+ # in casing=0, table name and schema name come back in their
+ # exact case.
+ # in casing=1, table name and schema name come back in lower
+ # case.
+ # in casing=2, table name and schema name come back from the
+ # information_schema.columns view in the case
+ # that was used in CREATE DATABASE and CREATE TABLE, but
+ # SHOW CREATE TABLE converts them to *lower case*, therefore
+ # not matching. So for this case, case-insensitive lookup
+ # is necessary
d = defaultdict(dict)
for schema, tname, cname in correct_for_wrong_fk_case:
- d[(schema, tname)][cname.lower()] = cname
+ d[(lower(schema), lower(tname))][cname.lower()] = cname
for fkey in fkeys:
fkey['referred_columns'] = [
d[
(
- fkey['referred_schema'] or default_schema_name,
- fkey['referred_table']
+ lower(
+ fkey['referred_schema'] or
+ default_schema_name),
+ lower(fkey['referred_table'])
)
][col.lower()]
for col in fkey['referred_columns']
cs = 1
else:
cs = int(row[1])
+ self._casing = cs
return cs
def _detect_collations(self, connection):
m1 = self.metadata
Table(
- 'Track', m1, Column('TrackID', Integer, primary_key=True)
+ 'Track', m1, Column('TrackID', Integer, primary_key=True),
+ mysql_engine='InnoDB'
)
Table(
'Track', m1, Column('TrackID', Integer, primary_key=True),
- schema=testing.config.test_schema
+ schema=testing.config.test_schema,
+ mysql_engine='InnoDB'
)
Table(
'PlaylistTrack', m1, Column('id', Integer, primary_key=True),
'%s.Track.TrackID' % (testing.config.test_schema,),
name='FK_PlaylistTTrackId'
)
- )
+ ),
+ mysql_engine='InnoDB'
)
m1.create_all()
+ if testing.db.dialect._casing in (1, 2):
+ 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': {}}
+ ]
+ )
+ else:
+ 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': {}}
+ ]
+ )
+
+ @testing.requires.mysql_fully_case_sensitive
+ @testing.provide_metadata
+ def test_case_sensitive_reflection_dual_case_references(self):
+ # this tests that within the fix we do for MySQL bug
+ # 88718, we don't do case-insensitive logic if the backend
+ # is case sensitive
+ m = self.metadata
+ Table(
+ 't1', m,
+ Column('some_id', Integer, primary_key=True),
+ mysql_engine='InnoDB'
+
+ )
+
+ Table(
+ 'T1', m,
+ Column('Some_Id', Integer, primary_key=True),
+ mysql_engine='InnoDB'
+ )
+
+ Table(
+ 't2', m,
+ Column('id', Integer, primary_key=True),
+ Column('t1id', ForeignKey('t1.some_id', name='t1id_fk')),
+ Column('cap_t1id', ForeignKey('T1.Some_Id', name='cap_t1id_fk')),
+ mysql_engine='InnoDB'
+ )
+ m.create_all(testing.db)
+
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': {}}
- ]
+ dict(
+ (rec['name'], rec)
+ for rec in inspect(testing.db).get_foreign_keys('t2')
+ ),
+ {
+ 'cap_t1id_fk': {
+ 'name': 'cap_t1id_fk',
+ 'constrained_columns': ['cap_t1id'],
+ 'referred_schema': None,
+ 'referred_table': 'T1',
+ 'referred_columns': ['Some_Id'], 'options': {}
+ },
+ 't1id_fk': {
+ 'name': 't1id_fk',
+ 'constrained_columns': ['t1id'],
+ 'referred_schema': None,
+ 'referred_table': 't1',
+ 'referred_columns': ['some_id'], 'options': {}
+ },
+ }
)