Restore the check_foreign_keys() method.
This method was prematurely removed from oslo.db without a deprecation phase, when Alembic added support for foreign key autodetection. oslo.db continues to use alembic for this purpose, however the ModelsMigrationsSync.check_foreign_keys() method is restored directly for those projects which were calling into it explicitly. Change-Id: Id892567bd60d6b4b88765bbfe3cd5c5e75910b25
This commit is contained in:
parent
98cdceb1ec
commit
b1fc55c7ce
@ -15,6 +15,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import collections
|
||||||
import logging
|
import logging
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
@ -510,6 +511,77 @@ class ModelsMigrationsSync(object):
|
|||||||
for table in tbs:
|
for table in tbs:
|
||||||
conn.execute(schema.DropTable(table))
|
conn.execute(schema.DropTable(table))
|
||||||
|
|
||||||
|
FKInfo = collections.namedtuple('fk_info', ['constrained_columns',
|
||||||
|
'referred_table',
|
||||||
|
'referred_columns'])
|
||||||
|
|
||||||
|
def check_foreign_keys(self, metadata, bind):
|
||||||
|
"""Compare foreign keys between model and db table.
|
||||||
|
|
||||||
|
:returns: a list that contains information about:
|
||||||
|
|
||||||
|
* should be a new key added or removed existing,
|
||||||
|
* name of that key,
|
||||||
|
* source table,
|
||||||
|
* referred table,
|
||||||
|
* constrained columns,
|
||||||
|
* referred columns
|
||||||
|
|
||||||
|
Output::
|
||||||
|
|
||||||
|
[('drop_key',
|
||||||
|
'testtbl_fk_check_fkey',
|
||||||
|
'testtbl',
|
||||||
|
fk_info(constrained_columns=(u'fk_check',),
|
||||||
|
referred_table=u'table',
|
||||||
|
referred_columns=(u'fk_check',)))]
|
||||||
|
|
||||||
|
DEPRECATED: this function is deprecated and will be removed from
|
||||||
|
oslo.db in a few releases. Alembic autogenerate.compare_metadata()
|
||||||
|
now includes foreign key comparison directly.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
diff = []
|
||||||
|
insp = sqlalchemy.engine.reflection.Inspector.from_engine(bind)
|
||||||
|
# Get all tables from db
|
||||||
|
db_tables = insp.get_table_names()
|
||||||
|
# Get all tables from models
|
||||||
|
model_tables = metadata.tables
|
||||||
|
for table in db_tables:
|
||||||
|
if table not in model_tables:
|
||||||
|
continue
|
||||||
|
# Get all necessary information about key of current table from db
|
||||||
|
fk_db = dict((self._get_fk_info_from_db(i), i['name'])
|
||||||
|
for i in insp.get_foreign_keys(table))
|
||||||
|
fk_db_set = set(fk_db.keys())
|
||||||
|
# Get all necessary information about key of current table from
|
||||||
|
# models
|
||||||
|
fk_models = dict((self._get_fk_info_from_model(fk), fk)
|
||||||
|
for fk in model_tables[table].foreign_keys)
|
||||||
|
fk_models_set = set(fk_models.keys())
|
||||||
|
for key in (fk_db_set - fk_models_set):
|
||||||
|
diff.append(('drop_key', fk_db[key], table, key))
|
||||||
|
LOG.info(("Detected removed foreign key %(fk)r on "
|
||||||
|
"table %(table)r"), {'fk': fk_db[key],
|
||||||
|
'table': table})
|
||||||
|
for key in (fk_models_set - fk_db_set):
|
||||||
|
diff.append(('add_key', fk_models[key], table, key))
|
||||||
|
LOG.info((
|
||||||
|
"Detected added foreign key for column %(fk)r on table "
|
||||||
|
"%(table)r"), {'fk': fk_models[key].column.name,
|
||||||
|
'table': table})
|
||||||
|
return diff
|
||||||
|
|
||||||
|
def _get_fk_info_from_db(self, fk):
|
||||||
|
return self.FKInfo(tuple(fk['constrained_columns']),
|
||||||
|
fk['referred_table'],
|
||||||
|
tuple(fk['referred_columns']))
|
||||||
|
|
||||||
|
def _get_fk_info_from_model(self, fk):
|
||||||
|
return self.FKInfo((fk.parent.name,), fk.column.table.name,
|
||||||
|
(fk.column.name,))
|
||||||
|
|
||||||
def test_models_sync(self):
|
def test_models_sync(self):
|
||||||
# recent versions of sqlalchemy and alembic are needed for running of
|
# recent versions of sqlalchemy and alembic are needed for running of
|
||||||
# this test, but we already have them in requirements
|
# this test, but we already have them in requirements
|
||||||
|
@ -307,3 +307,190 @@ class ModelsMigrationsSyncPsql(ModelsMigrationSyncMixin,
|
|||||||
|
|
||||||
def test_models_not_sync(self):
|
def test_models_not_sync(self):
|
||||||
self._test_models_not_sync()
|
self._test_models_not_sync()
|
||||||
|
|
||||||
|
|
||||||
|
class TestOldCheckForeignKeys(test_base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestOldCheckForeignKeys, self).setUp()
|
||||||
|
|
||||||
|
test = self
|
||||||
|
|
||||||
|
class MigrateSync(migrate.ModelsMigrationsSync):
|
||||||
|
def get_engine(self):
|
||||||
|
return test.engine
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
return test.metadata
|
||||||
|
|
||||||
|
def db_sync(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
self.migration_sync = MigrateSync()
|
||||||
|
|
||||||
|
def _fk_added_fixture(self):
|
||||||
|
self.metadata = sa.MetaData()
|
||||||
|
self.metadata_migrations = sa.MetaData()
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_one', self.metadata,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_two', self.metadata,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
sa.Column('tone_id', sa.Integer),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_one', self.metadata_migrations,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_two', self.metadata_migrations,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
sa.Column(
|
||||||
|
'tone_id', sa.Integer,
|
||||||
|
sa.ForeignKey('testtbl_one.id', name="tone_id_fk")),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _fk_removed_fixture(self):
|
||||||
|
self.metadata = sa.MetaData()
|
||||||
|
self.metadata_migrations = sa.MetaData()
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_one', self.metadata,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_two', self.metadata,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
sa.Column(
|
||||||
|
'tone_id', sa.Integer,
|
||||||
|
sa.ForeignKey('testtbl_one.id', name="tone_id_fk")),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_one', self.metadata_migrations,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_two', self.metadata_migrations,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
sa.Column('tone_id', sa.Integer),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _fk_no_change_fixture(self):
|
||||||
|
self.metadata = sa.MetaData()
|
||||||
|
self.metadata_migrations = sa.MetaData()
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_one', self.metadata,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_two', self.metadata,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
sa.Column(
|
||||||
|
'tone_id', sa.Integer,
|
||||||
|
sa.ForeignKey('testtbl_one.id', name="tone_id_fk")),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_one', self.metadata_migrations,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
sa.Table(
|
||||||
|
'testtbl_two', self.metadata_migrations,
|
||||||
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
|
sa.Column(
|
||||||
|
'tone_id', sa.Integer,
|
||||||
|
sa.ForeignKey('testtbl_one.id', name="tone_id_fk")),
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self):
|
||||||
|
self.metadata.create_all(bind=self.engine)
|
||||||
|
return self.migration_sync.check_foreign_keys(
|
||||||
|
self.metadata_migrations, self.engine)
|
||||||
|
|
||||||
|
def _compare_diffs(self, diffs, compare_to):
|
||||||
|
diffs = [
|
||||||
|
(
|
||||||
|
cmd,
|
||||||
|
fk._get_colspec() if isinstance(fk, sa.ForeignKey)
|
||||||
|
else "tone_id_fk" if fk is None # sqlite workaround
|
||||||
|
else fk,
|
||||||
|
tname, fk_info
|
||||||
|
)
|
||||||
|
for cmd, fk, tname, fk_info in diffs
|
||||||
|
]
|
||||||
|
self.assertEqual(diffs, compare_to)
|
||||||
|
|
||||||
|
def test_fk_added(self):
|
||||||
|
self._fk_added_fixture()
|
||||||
|
diffs = self._run_test()
|
||||||
|
|
||||||
|
self._compare_diffs(
|
||||||
|
diffs,
|
||||||
|
[(
|
||||||
|
'add_key',
|
||||||
|
'testtbl_one.id',
|
||||||
|
'testtbl_two',
|
||||||
|
self.migration_sync.FKInfo(
|
||||||
|
constrained_columns=('tone_id',),
|
||||||
|
referred_table='testtbl_one',
|
||||||
|
referred_columns=('id',))
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fk_removed(self):
|
||||||
|
self._fk_removed_fixture()
|
||||||
|
diffs = self._run_test()
|
||||||
|
|
||||||
|
self._compare_diffs(
|
||||||
|
diffs,
|
||||||
|
[(
|
||||||
|
'drop_key',
|
||||||
|
"tone_id_fk",
|
||||||
|
'testtbl_two',
|
||||||
|
self.migration_sync.FKInfo(
|
||||||
|
constrained_columns=('tone_id',),
|
||||||
|
referred_table='testtbl_one',
|
||||||
|
referred_columns=('id',))
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fk_no_change(self):
|
||||||
|
self._fk_no_change_fixture()
|
||||||
|
diffs = self._run_test()
|
||||||
|
|
||||||
|
self._compare_diffs(
|
||||||
|
diffs,
|
||||||
|
[])
|
||||||
|
|
||||||
|
|
||||||
|
class PGTestOldCheckForeignKeys(
|
||||||
|
TestOldCheckForeignKeys, test_base.PostgreSQLOpportunisticTestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLTestOldCheckForeignKeys(
|
||||||
|
TestOldCheckForeignKeys, test_base.MySQLOpportunisticTestCase):
|
||||||
|
pass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user