rewrite of schemadiff internals
This commit is contained in:
@@ -1,13 +1,172 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from migrate.exceptions import *
|
import sqlalchemy
|
||||||
from migrate.versioning.genmodel import *
|
from sqlalchemy import *
|
||||||
|
from nose.tools import eq_
|
||||||
|
|
||||||
|
from migrate.versioning import genmodel, schemadiff
|
||||||
|
from migrate.changeset import schema, SQLA_06
|
||||||
|
|
||||||
from migrate.tests import fixture
|
from migrate.tests import fixture
|
||||||
|
|
||||||
|
|
||||||
class TestModelGenerator(fixture.Pathed, fixture.DB):
|
class TestSchemaDiff(fixture.DB):
|
||||||
level = fixture.DB.TXN
|
table_name = 'tmp_schemadiff'
|
||||||
|
level = fixture.DB.CONNECT
|
||||||
|
|
||||||
|
def _setup(self, url):
|
||||||
|
super(TestSchemaDiff, self)._setup(url)
|
||||||
|
self.meta = MetaData(self.engine, reflect=True)
|
||||||
|
self.meta.drop_all() # in case junk tables are lying around in the test database
|
||||||
|
self.meta = MetaData(self.engine, reflect=True) # needed if we just deleted some tables
|
||||||
|
self.table = Table(self.table_name, self.meta,
|
||||||
|
Column('id',Integer(), primary_key=True),
|
||||||
|
Column('name', UnicodeText()),
|
||||||
|
Column('data', UnicodeText()),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _teardown(self):
|
||||||
|
if self.table.exists():
|
||||||
|
self.meta = MetaData(self.engine, reflect=True)
|
||||||
|
self.meta.drop_all()
|
||||||
|
super(TestSchemaDiff, self)._teardown()
|
||||||
|
|
||||||
|
def _applyLatestModel(self):
|
||||||
|
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
||||||
|
genmodel.ModelGenerator(diff,self.engine).applyModel()
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_functional(self):
|
||||||
|
|
||||||
|
def assertDiff(isDiff, tablesMissingInDatabase, tablesMissingInModel, tablesWithDiff):
|
||||||
|
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
||||||
|
eq_(bool(diff), isDiff)
|
||||||
|
eq_(
|
||||||
|
(diff.tables_missing_from_B,
|
||||||
|
diff.tables_missing_from_A,
|
||||||
|
diff.tables_different.keys()),
|
||||||
|
(tablesMissingInDatabase,
|
||||||
|
tablesMissingInModel,
|
||||||
|
tablesWithDiff)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Model is defined but database is empty.
|
||||||
|
assertDiff(True, [self.table_name], [], [])
|
||||||
|
|
||||||
|
# Check Python upgrade and downgrade of database from updated model.
|
||||||
|
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
||||||
|
decls, upgradeCommands, downgradeCommands = genmodel.ModelGenerator(diff,self.engine).toUpgradeDowngradePython()
|
||||||
|
self.assertEqualsIgnoreWhitespace(decls, '''
|
||||||
|
from migrate.changeset import schema
|
||||||
|
meta = MetaData()
|
||||||
|
tmp_schemadiff = Table('tmp_schemadiff', meta,
|
||||||
|
Column('id', Integer(), primary_key=True, nullable=False),
|
||||||
|
Column('name', UnicodeText(length=None)),
|
||||||
|
Column('data', UnicodeText(length=None)),
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
self.assertEqualsIgnoreWhitespace(upgradeCommands,
|
||||||
|
'''meta.bind = migrate_engine
|
||||||
|
tmp_schemadiff.create()''')
|
||||||
|
self.assertEqualsIgnoreWhitespace(downgradeCommands,
|
||||||
|
'''meta.bind = migrate_engine
|
||||||
|
tmp_schemadiff.drop()''')
|
||||||
|
|
||||||
|
# Create table in database, now model should match database.
|
||||||
|
self._applyLatestModel()
|
||||||
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
|
# Check Python code gen from database.
|
||||||
|
diff = schemadiff.getDiffOfModelAgainstDatabase(MetaData(), self.engine, excludeTables=['migrate_version'])
|
||||||
|
src = genmodel.ModelGenerator(diff,self.engine).toPython()
|
||||||
|
|
||||||
|
exec src in locals()
|
||||||
|
|
||||||
|
c1 = Table('tmp_schemadiff', self.meta, autoload=True).c
|
||||||
|
c2 = tmp_schemadiff.c
|
||||||
|
self.compare_columns_equal(c1, c2, ['type'])
|
||||||
|
# TODO: get rid of ignoring type
|
||||||
|
|
||||||
|
if not self.engine.name == 'oracle':
|
||||||
|
# Add data, later we'll make sure it's still present.
|
||||||
|
result = self.engine.execute(self.table.insert(), id=1, name=u'mydata')
|
||||||
|
if SQLA_06:
|
||||||
|
dataId = result.inserted_primary_key[0]
|
||||||
|
else:
|
||||||
|
dataId = result.last_inserted_ids()[0]
|
||||||
|
|
||||||
|
# Modify table in model (by removing it and adding it back to model) -- drop column data and add column data2.
|
||||||
|
self.meta.remove(self.table)
|
||||||
|
self.table = Table(self.table_name,self.meta,
|
||||||
|
Column('id',Integer(),primary_key=True),
|
||||||
|
Column('name',UnicodeText(length=None)),
|
||||||
|
Column('data2',Integer(),nullable=True),
|
||||||
|
)
|
||||||
|
assertDiff(True, [], [], [self.table_name])
|
||||||
|
|
||||||
|
# Apply latest model changes and find no more diffs.
|
||||||
|
self._applyLatestModel()
|
||||||
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
|
if not self.engine.name == 'oracle':
|
||||||
|
# Make sure data is still present.
|
||||||
|
result = self.engine.execute(self.table.select(self.table.c.id==dataId))
|
||||||
|
rows = result.fetchall()
|
||||||
|
eq_(len(rows), 1)
|
||||||
|
eq_(rows[0].name, 'mydata')
|
||||||
|
|
||||||
|
# Add data, later we'll make sure it's still present.
|
||||||
|
result = self.engine.execute(self.table.insert(), id=2, name=u'mydata2', data2=123)
|
||||||
|
if SQLA_06:
|
||||||
|
dataId2 = result.inserted_primary_key[0]
|
||||||
|
else:
|
||||||
|
dataId2 = result.last_inserted_ids()[0]
|
||||||
|
|
||||||
|
# Change column type in model.
|
||||||
|
self.meta.remove(self.table)
|
||||||
|
self.table = Table(self.table_name,self.meta,
|
||||||
|
Column('id',Integer(),primary_key=True),
|
||||||
|
Column('name',UnicodeText(length=None)),
|
||||||
|
Column('data2',String(255),nullable=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
# XXX test type diff
|
||||||
|
return
|
||||||
|
|
||||||
|
assertDiff(True, [], [], [self.table_name])
|
||||||
|
|
||||||
|
# Apply latest model changes and find no more diffs.
|
||||||
|
self._applyLatestModel()
|
||||||
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
|
if not self.engine.name == 'oracle':
|
||||||
|
# Make sure data is still present.
|
||||||
|
result = self.engine.execute(self.table.select(self.table.c.id==dataId2))
|
||||||
|
rows = result.fetchall()
|
||||||
|
self.assertEquals(len(rows), 1)
|
||||||
|
self.assertEquals(rows[0].name, 'mydata2')
|
||||||
|
self.assertEquals(rows[0].data2, '123')
|
||||||
|
|
||||||
|
# Delete data, since we're about to make a required column.
|
||||||
|
# Not even using sqlalchemy.PassiveDefault helps because we're doing explicit column select.
|
||||||
|
self.engine.execute(self.table.delete(), id=dataId)
|
||||||
|
|
||||||
|
if not self.engine.name == 'firebird':
|
||||||
|
# Change column nullable in model.
|
||||||
|
self.meta.remove(self.table)
|
||||||
|
self.table = Table(self.table_name,self.meta,
|
||||||
|
Column('id',Integer(),primary_key=True),
|
||||||
|
Column('name',UnicodeText(length=None)),
|
||||||
|
Column('data2',String(255),nullable=False),
|
||||||
|
)
|
||||||
|
assertDiff(True, [], [], [self.table_name]) # TODO test nullable diff
|
||||||
|
|
||||||
|
# Apply latest model changes and find no more diffs.
|
||||||
|
self._applyLatestModel()
|
||||||
|
assertDiff(False, [], [], [])
|
||||||
|
|
||||||
|
# Remove table from model.
|
||||||
|
self.meta.remove(self.table)
|
||||||
|
assertDiff(True, [], [self.table_name], [])
|
||||||
|
|||||||
@@ -2,175 +2,138 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import sqlalchemy
|
|
||||||
from sqlalchemy import *
|
from sqlalchemy import *
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
from migrate.versioning import genmodel, schemadiff
|
from migrate.versioning import schemadiff
|
||||||
from migrate.changeset import schema, SQLA_06
|
from migrate.changeset import SQLA_06
|
||||||
|
|
||||||
from migrate.tests import fixture
|
from migrate.tests import fixture
|
||||||
|
|
||||||
|
class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||||
|
|
||||||
class TestSchemaDiff(fixture.DB):
|
|
||||||
table_name = 'tmp_schemadiff'
|
|
||||||
level = fixture.DB.CONNECT
|
level = fixture.DB.CONNECT
|
||||||
|
|
||||||
def _setup(self, url):
|
def _make_table(self,*cols,**kw):
|
||||||
super(TestSchemaDiff, self)._setup(url)
|
self.table = Table('xtable', self.meta,
|
||||||
self.meta = MetaData(self.engine, reflect=True)
|
|
||||||
self.meta.drop_all() # in case junk tables are lying around in the test database
|
|
||||||
self.meta = MetaData(self.engine, reflect=True) # needed if we just deleted some tables
|
|
||||||
self.table = Table(self.table_name, self.meta,
|
|
||||||
Column('id',Integer(), primary_key=True),
|
Column('id',Integer(), primary_key=True),
|
||||||
Column('name', UnicodeText()),
|
*cols
|
||||||
Column('data', UnicodeText()),
|
|
||||||
)
|
)
|
||||||
|
if kw.get('create',True):
|
||||||
def _teardown(self):
|
self.table.create()
|
||||||
if self.table.exists():
|
|
||||||
self.meta = MetaData(self.engine, reflect=True)
|
def _run_diff(self,**kw):
|
||||||
self.meta.drop_all()
|
return schemadiff.getDiffOfModelAgainstDatabase(
|
||||||
super(TestSchemaDiff, self)._teardown()
|
self.meta, self.engine, **kw
|
||||||
|
)
|
||||||
def _applyLatestModel(self):
|
@fixture.usedb()
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
def test_getDiffOfModelAgainstDatabase_table_missing_in_db(self):
|
||||||
genmodel.ModelGenerator(diff).applyModel()
|
self._make_table(create=False)
|
||||||
|
diff = self._run_diff()
|
||||||
# TODO: support for diff/generator to extract differences between columns
|
self.assertTrue(diff)
|
||||||
#@fixture.usedb()
|
eq_('Schema diffs:\n tables missing from database: xtable',
|
||||||
#def test_type_diff(self):
|
str(diff))
|
||||||
#"""Basic test for correct type diff"""
|
|
||||||
#self.table.create()
|
|
||||||
#self.meta = MetaData(self.engine)
|
|
||||||
#self.table = Table(self.table_name, self.meta,
|
|
||||||
#Column('id', Integer(), primary_key=True),
|
|
||||||
#Column('name', Unicode(45)),
|
|
||||||
#Column('data', Integer),
|
|
||||||
#)
|
|
||||||
#diff = schemadiff.getDiffOfModelAgainstDatabase\
|
|
||||||
#(self.meta, self.engine, excludeTables=['migrate_version'])
|
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_rundiffs(self):
|
def test_getDiffOfModelAgainstDatabase_table_missing_in_model(self):
|
||||||
|
self._make_table()
|
||||||
|
self.meta.clear()
|
||||||
|
diff = self._run_diff()
|
||||||
|
self.assertTrue(diff)
|
||||||
|
eq_('Schema diffs:\n tables missing from model: xtable',
|
||||||
|
str(diff))
|
||||||
|
|
||||||
def assertDiff(isDiff, tablesMissingInDatabase, tablesMissingInModel, tablesWithDiff):
|
@fixture.usedb()
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
def test_getDiffOfModelAgainstDatabase_column_missing_in_db(self):
|
||||||
eq_(bool(diff), isDiff)
|
# db
|
||||||
eq_( ([t.name for t in diff.tablesMissingInDatabase], [t.name for t in diff.tablesMissingInModel], [t.name for t in diff.tablesWithDiff]),
|
Table('xtable', self.meta,
|
||||||
(tablesMissingInDatabase, tablesMissingInModel, tablesWithDiff) )
|
Column('id',Integer(), primary_key=True),
|
||||||
|
).create()
|
||||||
# Model is defined but database is empty.
|
self.meta.clear()
|
||||||
assertDiff(True, [self.table_name], [], [])
|
# model
|
||||||
|
self._make_table(
|
||||||
# Check Python upgrade and downgrade of database from updated model.
|
Column('xcol',Integer()),
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(self.meta, self.engine, excludeTables=['migrate_version'])
|
create=False
|
||||||
decls, upgradeCommands, downgradeCommands = genmodel.ModelGenerator(diff).toUpgradeDowngradePython()
|
|
||||||
self.assertEqualsIgnoreWhitespace(decls, '''
|
|
||||||
from migrate.changeset import schema
|
|
||||||
meta = MetaData()
|
|
||||||
tmp_schemadiff = Table('tmp_schemadiff', meta,
|
|
||||||
Column('id', Integer(), primary_key=True, nullable=False),
|
|
||||||
Column('name', UnicodeText(length=None)),
|
|
||||||
Column('data', UnicodeText(length=None)),
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
self.assertEqualsIgnoreWhitespace(upgradeCommands,
|
|
||||||
'''meta.bind = migrate_engine
|
|
||||||
tmp_schemadiff.create()''')
|
|
||||||
self.assertEqualsIgnoreWhitespace(downgradeCommands,
|
|
||||||
'''meta.bind = migrate_engine
|
|
||||||
tmp_schemadiff.drop()''')
|
|
||||||
|
|
||||||
# Create table in database, now model should match database.
|
|
||||||
self._applyLatestModel()
|
|
||||||
assertDiff(False, [], [], [])
|
|
||||||
|
|
||||||
# Check Python code gen from database.
|
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(MetaData(), self.engine, excludeTables=['migrate_version'])
|
|
||||||
src = genmodel.ModelGenerator(diff).toPython()
|
|
||||||
|
|
||||||
exec src in locals()
|
|
||||||
|
|
||||||
c1 = Table('tmp_schemadiff', self.meta, autoload=True).c
|
|
||||||
c2 = tmp_schemadiff.c
|
|
||||||
self.compare_columns_equal(c1, c2, ['type'])
|
|
||||||
# TODO: get rid of ignoring type
|
|
||||||
|
|
||||||
if not self.engine.name == 'oracle':
|
|
||||||
# Add data, later we'll make sure it's still present.
|
|
||||||
result = self.engine.execute(self.table.insert(), id=1, name=u'mydata')
|
|
||||||
if SQLA_06:
|
|
||||||
dataId = result.inserted_primary_key[0]
|
|
||||||
else:
|
|
||||||
dataId = result.last_inserted_ids()[0]
|
|
||||||
|
|
||||||
# Modify table in model (by removing it and adding it back to model) -- drop column data and add column data2.
|
|
||||||
self.meta.remove(self.table)
|
|
||||||
self.table = Table(self.table_name,self.meta,
|
|
||||||
Column('id',Integer(),primary_key=True),
|
|
||||||
Column('name',UnicodeText(length=None)),
|
|
||||||
Column('data2',Integer(),nullable=True),
|
|
||||||
)
|
|
||||||
assertDiff(True, [], [], [self.table_name])
|
|
||||||
|
|
||||||
# Apply latest model changes and find no more diffs.
|
|
||||||
self._applyLatestModel()
|
|
||||||
assertDiff(False, [], [], [])
|
|
||||||
|
|
||||||
if not self.engine.name == 'oracle':
|
|
||||||
# Make sure data is still present.
|
|
||||||
result = self.engine.execute(self.table.select(self.table.c.id==dataId))
|
|
||||||
rows = result.fetchall()
|
|
||||||
eq_(len(rows), 1)
|
|
||||||
eq_(rows[0].name, 'mydata')
|
|
||||||
|
|
||||||
# Add data, later we'll make sure it's still present.
|
|
||||||
result = self.engine.execute(self.table.insert(), id=2, name=u'mydata2', data2=123)
|
|
||||||
if SQLA_06:
|
|
||||||
dataId2 = result.inserted_primary_key[0]
|
|
||||||
else:
|
|
||||||
dataId2 = result.last_inserted_ids()[0]
|
|
||||||
|
|
||||||
# Change column type in model.
|
|
||||||
self.meta.remove(self.table)
|
|
||||||
self.table = Table(self.table_name,self.meta,
|
|
||||||
Column('id',Integer(),primary_key=True),
|
|
||||||
Column('name',UnicodeText(length=None)),
|
|
||||||
Column('data2',String(255),nullable=True),
|
|
||||||
)
|
|
||||||
assertDiff(True, [], [], [self.table_name]) # TODO test type diff
|
|
||||||
|
|
||||||
# Apply latest model changes and find no more diffs.
|
|
||||||
self._applyLatestModel()
|
|
||||||
assertDiff(False, [], [], [])
|
|
||||||
|
|
||||||
if not self.engine.name == 'oracle':
|
|
||||||
# Make sure data is still present.
|
|
||||||
result = self.engine.execute(self.table.select(self.table.c.id==dataId2))
|
|
||||||
rows = result.fetchall()
|
|
||||||
self.assertEquals(len(rows), 1)
|
|
||||||
self.assertEquals(rows[0].name, 'mydata2')
|
|
||||||
self.assertEquals(rows[0].data2, '123')
|
|
||||||
|
|
||||||
# Delete data, since we're about to make a required column.
|
|
||||||
# Not even using sqlalchemy.PassiveDefault helps because we're doing explicit column select.
|
|
||||||
self.engine.execute(self.table.delete(), id=dataId)
|
|
||||||
|
|
||||||
if not self.engine.name == 'firebird':
|
|
||||||
# Change column nullable in model.
|
|
||||||
self.meta.remove(self.table)
|
|
||||||
self.table = Table(self.table_name,self.meta,
|
|
||||||
Column('id',Integer(),primary_key=True),
|
|
||||||
Column('name',UnicodeText(length=None)),
|
|
||||||
Column('data2',String(255),nullable=False),
|
|
||||||
)
|
)
|
||||||
assertDiff(True, [], [], [self.table_name]) # TODO test nullable diff
|
# run diff
|
||||||
|
diff = self._run_diff()
|
||||||
# Apply latest model changes and find no more diffs.
|
self.assertTrue(diff)
|
||||||
self._applyLatestModel()
|
eq_('Schema diffs:\n xtable missing columns from database: xcol',
|
||||||
assertDiff(False, [], [], [])
|
str(diff))
|
||||||
|
|
||||||
# Remove table from model.
|
@fixture.usedb()
|
||||||
self.meta.remove(self.table)
|
def test_getDiffOfModelAgainstDatabase_column_missing_in_model(self):
|
||||||
assertDiff(True, [], [self.table_name], [])
|
# db
|
||||||
|
self._make_table(
|
||||||
|
Column('xcol',Integer()),
|
||||||
|
)
|
||||||
|
self.meta.clear()
|
||||||
|
# model
|
||||||
|
self._make_table(
|
||||||
|
create=False
|
||||||
|
)
|
||||||
|
# run diff
|
||||||
|
diff = self._run_diff()
|
||||||
|
self.assertTrue(diff)
|
||||||
|
eq_('Schema diffs:\n xtable missing columns from model: xcol',
|
||||||
|
str(diff))
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_getDiffOfModelAgainstDatabase_exclude_tables(self):
|
||||||
|
# db
|
||||||
|
Table('ytable', self.meta,
|
||||||
|
Column('id',Integer(), primary_key=True),
|
||||||
|
).create()
|
||||||
|
Table('ztable', self.meta,
|
||||||
|
Column('id',Integer(), primary_key=True),
|
||||||
|
).create()
|
||||||
|
self.meta.clear()
|
||||||
|
# model
|
||||||
|
self._make_table(
|
||||||
|
create=False
|
||||||
|
)
|
||||||
|
Table('ztable', self.meta,
|
||||||
|
Column('id',Integer(), primary_key=True),
|
||||||
|
)
|
||||||
|
# run diff
|
||||||
|
diff = self._run_diff(excludeTables=('xtable','ytable'))
|
||||||
|
# ytable only in database
|
||||||
|
# xtable only in model
|
||||||
|
# ztable identical on both
|
||||||
|
# ...so we expect no diff!
|
||||||
|
self.assertFalse(diff)
|
||||||
|
eq_('No schema diffs',str(diff))
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_getDiffOfModelAgainstDatabase_identical_just_pk(self):
|
||||||
|
self._make_table()
|
||||||
|
diff = self._run_diff()
|
||||||
|
self.assertFalse(diff)
|
||||||
|
eq_('No schema diffs',str(diff))
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_getDiffOfModelAgainstDatabase_integer_identical(self):
|
||||||
|
self._make_table(
|
||||||
|
Column('data', Integer()),
|
||||||
|
)
|
||||||
|
diff = self._run_diff()
|
||||||
|
eq_('No schema diffs',str(diff))
|
||||||
|
self.assertFalse(diff)
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_getDiffOfModelAgainstDatabase_string_identical(self):
|
||||||
|
self._make_table(
|
||||||
|
Column('data', String(10)),
|
||||||
|
)
|
||||||
|
diff = self._run_diff()
|
||||||
|
eq_('No schema diffs',str(diff))
|
||||||
|
self.assertFalse(diff)
|
||||||
|
|
||||||
|
@fixture.usedb()
|
||||||
|
def test_getDiffOfModelAgainstDatabase_text_identical(self):
|
||||||
|
self._make_table(
|
||||||
|
Column('data', Text(255)),
|
||||||
|
)
|
||||||
|
diff = self._run_diff()
|
||||||
|
eq_('No schema diffs',str(diff))
|
||||||
|
self.assertFalse(diff)
|
||||||
|
|||||||
@@ -479,14 +479,14 @@ class TestShellDatabase(Shell, DB):
|
|||||||
# Model is defined but database is empty.
|
# Model is defined but database is empty.
|
||||||
result = self.env.run('migrate compare_model_to_db %s %s --model=%s' \
|
result = self.env.run('migrate compare_model_to_db %s %s --model=%s' \
|
||||||
% (self.url, repos_path, model_module))
|
% (self.url, repos_path, model_module))
|
||||||
self.assert_("tables missing in database: tmp_account_rundiffs" in result.stdout)
|
self.assert_("tables missing from database: tmp_account_rundiffs" in result.stdout)
|
||||||
|
|
||||||
# Test Deprecation
|
# Test Deprecation
|
||||||
result = self.env.run('migrate compare_model_to_db %s %s --model=%s' \
|
result = self.env.run('migrate compare_model_to_db %s %s --model=%s' \
|
||||||
% (self.url, repos_path, model_module.replace(":", ".")), expect_error=True)
|
% (self.url, repos_path, model_module.replace(":", ".")), expect_error=True)
|
||||||
self.assertEqual(result.returncode, 0)
|
self.assertEqual(result.returncode, 0)
|
||||||
self.assertTrue("DeprecationWarning" in result.stderr)
|
self.assertTrue("DeprecationWarning" in result.stderr)
|
||||||
self.assert_("tables missing in database: tmp_account_rundiffs" in result.stdout)
|
self.assert_("tables missing from database: tmp_account_rundiffs" in result.stdout)
|
||||||
|
|
||||||
# Update db to latest model.
|
# Update db to latest model.
|
||||||
result = self.env.run('migrate update_db_from_model %s %s %s'\
|
result = self.env.run('migrate update_db_from_model %s %s %s'\
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ Base = declarative.declarative_base()
|
|||||||
|
|
||||||
class ModelGenerator(object):
|
class ModelGenerator(object):
|
||||||
|
|
||||||
def __init__(self, diff, declarative=False):
|
def __init__(self, diff, engine, declarative=False):
|
||||||
self.diff = diff
|
self.diff = diff
|
||||||
|
self.engine = engine
|
||||||
self.declarative = declarative
|
self.declarative = declarative
|
||||||
|
|
||||||
def column_repr(self, col):
|
def column_repr(self, col):
|
||||||
@@ -111,6 +112,17 @@ class ModelGenerator(object):
|
|||||||
out.append(")")
|
out.append(")")
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def _get_tables(self,missingA=False,missingB=False,modified=False):
|
||||||
|
to_process = []
|
||||||
|
for bool_,names,metadata in (
|
||||||
|
(missingA,self.diff.tables_missing_from_A,self.diff.metadataB),
|
||||||
|
(missingB,self.diff.tables_missing_from_B,self.diff.metadataA),
|
||||||
|
(modified,self.diff.tables_different,self.diff.metadataA),
|
||||||
|
):
|
||||||
|
if bool_:
|
||||||
|
for name in names:
|
||||||
|
yield metadata.tables.get(name)
|
||||||
|
|
||||||
def toPython(self):
|
def toPython(self):
|
||||||
"""Assume database is current and model is empty."""
|
"""Assume database is current and model is empty."""
|
||||||
out = []
|
out = []
|
||||||
@@ -119,7 +131,7 @@ class ModelGenerator(object):
|
|||||||
else:
|
else:
|
||||||
out.append(HEADER)
|
out.append(HEADER)
|
||||||
out.append("")
|
out.append("")
|
||||||
for table in self.diff.tablesMissingInModel:
|
for table in self._get_tables(missingA=True):
|
||||||
out.extend(self.getTableDefn(table))
|
out.extend(self.getTableDefn(table))
|
||||||
out.append("")
|
out.append("")
|
||||||
return '\n'.join(out)
|
return '\n'.join(out)
|
||||||
@@ -128,25 +140,22 @@ class ModelGenerator(object):
|
|||||||
''' Assume model is most current and database is out-of-date. '''
|
''' Assume model is most current and database is out-of-date. '''
|
||||||
decls = ['from migrate.changeset import schema',
|
decls = ['from migrate.changeset import schema',
|
||||||
'meta = MetaData()']
|
'meta = MetaData()']
|
||||||
for table in self.diff.tablesMissingInModel + \
|
for table in self._get_tables(
|
||||||
self.diff.tablesMissingInDatabase + \
|
missingA=True,missingB=True,modified=True
|
||||||
self.diff.tablesWithDiff:
|
):
|
||||||
decls.extend(self.getTableDefn(table))
|
decls.extend(self.getTableDefn(table))
|
||||||
|
|
||||||
upgradeCommands, downgradeCommands = [], []
|
upgradeCommands, downgradeCommands = [], []
|
||||||
for table in self.diff.tablesMissingInModel:
|
for tableName in self.diff.tables_missing_from_A:
|
||||||
tableName = table.name
|
|
||||||
upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
||||||
downgradeCommands.append("%(table)s.create()" % \
|
downgradeCommands.append("%(table)s.create()" % \
|
||||||
{'table': tableName})
|
{'table': tableName})
|
||||||
for table in self.diff.tablesMissingInDatabase:
|
for tableName in self.diff.tables_missing_from_B:
|
||||||
tableName = table.name
|
|
||||||
upgradeCommands.append("%(table)s.create()" % {'table': tableName})
|
upgradeCommands.append("%(table)s.create()" % {'table': tableName})
|
||||||
downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
|
||||||
|
|
||||||
for modelTable in self.diff.tablesWithDiff:
|
for tableName in self.diff.tables_different:
|
||||||
dbTable = self.diff.reflected_model.tables[modelTable.name]
|
dbTable = self.diff.metadataB.tables[tableName]
|
||||||
tableName = modelTable.name
|
|
||||||
missingInDatabase, missingInModel, diffDecl = \
|
missingInDatabase, missingInModel, diffDecl = \
|
||||||
self.diff.colDiffs[tableName]
|
self.diff.colDiffs[tableName]
|
||||||
for col in missingInDatabase:
|
for col in missingInDatabase:
|
||||||
@@ -173,38 +182,40 @@ class ModelGenerator(object):
|
|||||||
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]),
|
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]),
|
||||||
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
|
'\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
|
||||||
|
|
||||||
|
def _db_can_handle_this_change(self,td):
|
||||||
|
if (td.columns_missing_from_B
|
||||||
|
and not td.columns_missing_from_A
|
||||||
|
and not td.columns_different):
|
||||||
|
# Even sqlite can handle this.
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return not self.engine.url.drivername.startswith('sqlite')
|
||||||
|
|
||||||
def applyModel(self):
|
def applyModel(self):
|
||||||
"""Apply model to current database."""
|
"""Apply model to current database."""
|
||||||
|
|
||||||
def dbCanHandleThisChange(missingInDatabase, missingInModel, diffDecl):
|
meta = sqlalchemy.MetaData(self.engine)
|
||||||
if missingInDatabase and not missingInModel and not diffDecl:
|
|
||||||
# Even sqlite can handle this.
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return not self.diff.conn.url.drivername.startswith('sqlite')
|
|
||||||
|
|
||||||
meta = sqlalchemy.MetaData(self.diff.conn.engine)
|
for table in self._get_tables(missingA=True):
|
||||||
|
|
||||||
for table in self.diff.tablesMissingInModel:
|
|
||||||
table = table.tometadata(meta)
|
table = table.tometadata(meta)
|
||||||
table.drop()
|
table.drop()
|
||||||
for table in self.diff.tablesMissingInDatabase:
|
for table in self._get_tables(missingB=True):
|
||||||
table = table.tometadata(meta)
|
table = table.tometadata(meta)
|
||||||
table.create()
|
table.create()
|
||||||
for modelTable in self.diff.tablesWithDiff:
|
for modelTable in self._get_tables(modified=True):
|
||||||
modelTable = modelTable.tometadata(meta)
|
|
||||||
dbTable = self.diff.reflected_model.tables[modelTable.name]
|
|
||||||
tableName = modelTable.name
|
tableName = modelTable.name
|
||||||
missingInDatabase, missingInModel, diffDecl = \
|
modelTable = modelTable.tometadata(meta)
|
||||||
self.diff.colDiffs[tableName]
|
dbTable = self.diff.metadataB.tables[tableName]
|
||||||
if dbCanHandleThisChange(missingInDatabase, missingInModel,
|
|
||||||
diffDecl):
|
td = self.diff.tables_different[tableName]
|
||||||
for col in missingInDatabase:
|
|
||||||
modelTable.columns[col.name].create()
|
if self._db_can_handle_this_change(td):
|
||||||
for col in missingInModel:
|
|
||||||
dbTable.columns[col.name].drop()
|
for col in td.columns_missing_from_B:
|
||||||
for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
|
modelTable.columns[col].create()
|
||||||
databaseCol.alter(modelCol)
|
for col in td.columns_missing_from_A:
|
||||||
|
dbTable.columns[col].drop()
|
||||||
|
# XXX handle column changes here.
|
||||||
else:
|
else:
|
||||||
# Sqlite doesn't support drop column, so you have to
|
# Sqlite doesn't support drop column, so you have to
|
||||||
# do more: create temp table, copy data to it, drop
|
# do more: create temp table, copy data to it, drop
|
||||||
@@ -214,7 +225,7 @@ class ModelGenerator(object):
|
|||||||
tempName = '_temp_%s' % modelTable.name
|
tempName = '_temp_%s' % modelTable.name
|
||||||
|
|
||||||
def getCopyStatement():
|
def getCopyStatement():
|
||||||
preparer = self.diff.conn.engine.dialect.preparer
|
preparer = self.engine.dialect.preparer
|
||||||
commonCols = []
|
commonCols = []
|
||||||
for modelCol in modelTable.columns:
|
for modelCol in modelTable.columns:
|
||||||
if modelCol.name in dbTable.columns:
|
if modelCol.name in dbTable.columns:
|
||||||
@@ -225,7 +236,7 @@ class ModelGenerator(object):
|
|||||||
|
|
||||||
# Move the data in one transaction, so that we don't
|
# Move the data in one transaction, so that we don't
|
||||||
# leave the database in a nasty state.
|
# leave the database in a nasty state.
|
||||||
connection = self.diff.conn.connect()
|
connection = self.engine.connect()
|
||||||
trans = connection.begin()
|
trans = connection.begin()
|
||||||
try:
|
try:
|
||||||
connection.execute(
|
connection.execute(
|
||||||
|
|||||||
@@ -108,8 +108,9 @@ class ControlledSchema(object):
|
|||||||
model = load_model(model)
|
model = load_model(model)
|
||||||
|
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
||||||
model, self.engine, excludeTables=[self.repository.version_table])
|
model, self.engine, excludeTables=[self.repository.version_table]
|
||||||
genmodel.ModelGenerator(diff).applyModel()
|
)
|
||||||
|
genmodel.ModelGenerator(diff,self.engine).applyModel()
|
||||||
|
|
||||||
self.update_repository_table(self.version, int(self.repository.latest))
|
self.update_repository_table(self.version, int(self.repository.latest))
|
||||||
|
|
||||||
@@ -207,5 +208,6 @@ class ControlledSchema(object):
|
|||||||
repository = Repository(repository)
|
repository = Repository(repository)
|
||||||
|
|
||||||
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
diff = schemadiff.getDiffOfModelAgainstDatabase(
|
||||||
MetaData(), engine, excludeTables=[repository.version_table])
|
MetaData(), engine, excludeTables=[repository.version_table]
|
||||||
return genmodel.ModelGenerator(diff, declarative).toPython()
|
)
|
||||||
|
return genmodel.ModelGenerator(diff, engine, declarative).toPython()
|
||||||
|
|||||||
@@ -9,182 +9,176 @@ from migrate.changeset import SQLA_06
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
def getDiffOfModelAgainstDatabase(model, conn, excludeTables=None):
|
def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None):
|
||||||
"""
|
"""
|
||||||
Return differences of model against database.
|
Return differences of model against database.
|
||||||
|
|
||||||
:return: object which will evaluate to :keyword:`True` if there \
|
:return: object which will evaluate to :keyword:`True` if there \
|
||||||
are differences else :keyword:`False`.
|
are differences else :keyword:`False`.
|
||||||
"""
|
"""
|
||||||
return SchemaDiff(model, conn, excludeTables)
|
return SchemaDiff(metadata,
|
||||||
|
sqlalchemy.MetaData(engine, reflect=True),
|
||||||
|
labelA='model',
|
||||||
|
labelB='database',
|
||||||
|
excludeTables=excludeTables)
|
||||||
|
|
||||||
|
|
||||||
def getDiffOfModelAgainstModel(oldmodel, model, conn, excludeTables=None):
|
def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None):
|
||||||
"""
|
"""
|
||||||
Return differences of model against another model.
|
Return differences of model against another model.
|
||||||
|
|
||||||
:return: object which will evaluate to :keyword:`True` if there \
|
:return: object which will evaluate to :keyword:`True` if there \
|
||||||
are differences else :keyword:`False`.
|
are differences else :keyword:`False`.
|
||||||
"""
|
"""
|
||||||
return SchemaDiff(model, conn, excludeTables, oldmodel=oldmodel)
|
return SchemaDiff(metadataA, metadataB, excludeTables)
|
||||||
|
|
||||||
|
|
||||||
|
class TableDiff(object):
|
||||||
|
"""
|
||||||
|
Container for differences in one :class:`~sqlalchemy.schema.Table
|
||||||
|
between two :class:`~sqlalchemy.schema.MetaData` instances, ``A``
|
||||||
|
and ``B``.
|
||||||
|
|
||||||
|
.. attribute:: columns_missing_from_A
|
||||||
|
|
||||||
|
A sequence of column names that were found in B but weren't in
|
||||||
|
A.
|
||||||
|
|
||||||
|
.. attribute:: columns_missing_from_B
|
||||||
|
|
||||||
|
A sequence of column names that were found in A but weren't in
|
||||||
|
B.
|
||||||
|
|
||||||
|
.. attribute:: columns_different
|
||||||
|
|
||||||
|
An empty dictionary, for future use...
|
||||||
|
"""
|
||||||
|
__slots__ = (
|
||||||
|
'columns_missing_from_A',
|
||||||
|
'columns_missing_from_B',
|
||||||
|
'columns_different',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return (
|
||||||
|
len(self.columns_missing_from_A)+
|
||||||
|
len(self.columns_missing_from_B)+
|
||||||
|
len(self.columns_different)
|
||||||
|
)
|
||||||
|
|
||||||
class SchemaDiff(object):
|
class SchemaDiff(object):
|
||||||
"""
|
"""
|
||||||
Differences of model against database.
|
Compute the difference between two :class:`~sqlalchemy.schema.MetaData`
|
||||||
|
objects.
|
||||||
|
|
||||||
|
The string representation of a :class:`SchemaDiff` will summarise
|
||||||
|
the changes found between the two
|
||||||
|
:class:`~sqlalchemy.schema.MetaData` objects.
|
||||||
|
|
||||||
|
The length of a :class:`SchemaDiff` will give the number of
|
||||||
|
changes found, enabling it to be used much like a boolean in
|
||||||
|
expressions.
|
||||||
|
|
||||||
|
:param metadataA:
|
||||||
|
First :class:`~sqlalchemy.schema.MetaData` to compare.
|
||||||
|
|
||||||
|
:param metadataB:
|
||||||
|
Second :class:`~sqlalchemy.schema.MetaData` to compare.
|
||||||
|
|
||||||
|
:param labelA:
|
||||||
|
The label to use in messages about the first
|
||||||
|
:class:`~sqlalchemy.schema.MetaData`.
|
||||||
|
|
||||||
|
:param labelB:
|
||||||
|
The label to use in messages about the second
|
||||||
|
:class:`~sqlalchemy.schema.MetaData`.
|
||||||
|
|
||||||
|
:param excludeTables:
|
||||||
|
A sequence of table names to exclude.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model, conn, excludeTables=None, oldmodel=None):
|
def __init__(self,
|
||||||
"""
|
metadataA, metadataB,
|
||||||
:param model: Python model's metadata
|
labelA='metadataA',
|
||||||
:param conn: active database connection.
|
labelB='metadataB',
|
||||||
"""
|
excludeTables=None):
|
||||||
self.model = model
|
|
||||||
self.conn = conn
|
|
||||||
if not excludeTables:
|
|
||||||
# [] can't be default value in Python parameter
|
|
||||||
excludeTables = []
|
|
||||||
self.excludeTables = excludeTables
|
|
||||||
if oldmodel:
|
|
||||||
self.reflected_model = oldmodel
|
|
||||||
else:
|
|
||||||
self.reflected_model = sqlalchemy.MetaData(conn, reflect=True)
|
|
||||||
self.tablesMissingInDatabase, self.tablesMissingInModel, \
|
|
||||||
self.tablesWithDiff = [], [], []
|
|
||||||
self.colDiffs = {}
|
|
||||||
self.compareModelToDatabase()
|
|
||||||
|
|
||||||
def compareModelToDatabase(self):
|
self.metadataA, self.metadataB = metadataA, metadataB
|
||||||
"""
|
self.labelA, self.labelB = labelA, labelB
|
||||||
Do actual comparison.
|
excludeTables = set(excludeTables or [])
|
||||||
"""
|
|
||||||
# Setup common variables.
|
A_table_names = set(metadataA.tables.keys())
|
||||||
cc = self.conn.contextual_connect()
|
B_table_names = set(metadataB.tables.keys())
|
||||||
if SQLA_06:
|
|
||||||
from sqlalchemy.ext import compiler
|
self.tables_missing_from_A = sorted(
|
||||||
from sqlalchemy.schema import DDLElement
|
B_table_names - A_table_names - excludeTables
|
||||||
class DefineColumn(DDLElement):
|
)
|
||||||
def __init__(self, col):
|
self.tables_missing_from_B = sorted(
|
||||||
self.col = col
|
A_table_names - B_table_names - excludeTables
|
||||||
|
)
|
||||||
|
|
||||||
|
self.tables_different = {}
|
||||||
|
for table_name in A_table_names.intersection(B_table_names):
|
||||||
|
|
||||||
|
td = TableDiff()
|
||||||
|
|
||||||
@compiler.compiles(DefineColumn)
|
A_table = metadataA.tables[table_name]
|
||||||
def compile(elem, compiler, **kw):
|
B_table = metadataB.tables[table_name]
|
||||||
return compiler.get_column_specification(elem.col)
|
|
||||||
|
|
||||||
def get_column_specification(col):
|
A_column_names = set(A_table.columns.keys())
|
||||||
return str(DefineColumn(col).compile(dialect=self.conn.dialect))
|
B_column_names = set(B_table.columns.keys())
|
||||||
else:
|
|
||||||
schemagenerator = self.conn.dialect.schemagenerator(
|
|
||||||
self.conn.dialect, cc)
|
|
||||||
def get_column_specification(col):
|
|
||||||
return schemagenerator.get_column_specification(col)
|
|
||||||
|
|
||||||
# For each in model, find missing in database.
|
|
||||||
for modelName, modelTable in self.model.tables.items():
|
|
||||||
if modelName in self.excludeTables:
|
|
||||||
continue
|
|
||||||
reflectedTable = self.reflected_model.tables.get(modelName, None)
|
|
||||||
if reflectedTable is not None:
|
|
||||||
# Table exists.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.tablesMissingInDatabase.append(modelTable)
|
|
||||||
|
|
||||||
# For each in database, find missing in model.
|
td.columns_missing_from_A = sorted(
|
||||||
for reflectedName, reflectedTable in \
|
B_column_names - A_column_names
|
||||||
self.reflected_model.tables.items():
|
)
|
||||||
if reflectedName in self.excludeTables:
|
|
||||||
continue
|
td.columns_missing_from_B = sorted(
|
||||||
modelTable = self.model.tables.get(reflectedName, None)
|
A_column_names - B_column_names
|
||||||
if modelTable is not None:
|
)
|
||||||
# Table exists.
|
|
||||||
|
td.columns_different = {}
|
||||||
|
|
||||||
# Find missing columns in database.
|
# XXX - should check columns differences
|
||||||
for modelCol in modelTable.columns:
|
#for col_name in A_column_names.intersection(B_column_names):
|
||||||
databaseCol = reflectedTable.columns.get(modelCol.name,
|
#
|
||||||
None)
|
# A_col = A_table.columns.get(col_name)
|
||||||
if databaseCol is not None:
|
# B_col = B_table.columns.get(col_name)
|
||||||
pass
|
|
||||||
else:
|
# XXX - index and constraint differences should
|
||||||
self.storeColumnMissingInDatabase(modelTable, modelCol)
|
# be checked for here
|
||||||
|
|
||||||
# Find missing columns in model.
|
if td:
|
||||||
for databaseCol in reflectedTable.columns:
|
self.tables_different[table_name]=td
|
||||||
|
|
||||||
# TODO: no test coverage here? (mrb)
|
|
||||||
|
|
||||||
modelCol = modelTable.columns.get(databaseCol.name, None)
|
|
||||||
if modelCol is not None:
|
|
||||||
# Compare attributes of column.
|
|
||||||
modelDecl = \
|
|
||||||
get_column_specification(modelCol)
|
|
||||||
databaseDecl = \
|
|
||||||
get_column_specification(databaseCol)
|
|
||||||
if modelDecl != databaseDecl:
|
|
||||||
# Unfortunately, sometimes the database
|
|
||||||
# decl won't quite match the model, even
|
|
||||||
# though they're the same.
|
|
||||||
mc, dc = modelCol.type.__class__, \
|
|
||||||
databaseCol.type.__class__
|
|
||||||
if (issubclass(mc, dc) \
|
|
||||||
or issubclass(dc, mc)) \
|
|
||||||
and modelCol.nullable == \
|
|
||||||
databaseCol.nullable:
|
|
||||||
# Types and nullable are the same.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.storeColumnDiff(
|
|
||||||
modelTable, modelCol, databaseCol,
|
|
||||||
modelDecl, databaseDecl)
|
|
||||||
else:
|
|
||||||
self.storeColumnMissingInModel(modelTable, databaseCol)
|
|
||||||
else:
|
|
||||||
self.tablesMissingInModel.append(reflectedTable)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
''' Summarize differences. '''
|
''' Summarize differences. '''
|
||||||
|
|
||||||
def colDiffDetails():
|
|
||||||
colout = []
|
|
||||||
for table in self.tablesWithDiff:
|
|
||||||
tableName = table.name
|
|
||||||
missingInDatabase, missingInModel, diffDecl = \
|
|
||||||
self.colDiffs[tableName]
|
|
||||||
if missingInDatabase:
|
|
||||||
colout.append(
|
|
||||||
' %s missing columns in database: %s' % \
|
|
||||||
(tableName, ', '.join(
|
|
||||||
[col.name for col in missingInDatabase])))
|
|
||||||
if missingInModel:
|
|
||||||
colout.append(
|
|
||||||
' %s missing columns in model: %s' % \
|
|
||||||
(tableName, ', '.join(
|
|
||||||
[col.name for col in missingInModel])))
|
|
||||||
if diffDecl:
|
|
||||||
colout.append(
|
|
||||||
' %s with different declaration of columns\
|
|
||||||
in database: %s' % (tableName, str(diffDecl)))
|
|
||||||
return colout
|
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
if self.tablesMissingInDatabase:
|
|
||||||
out.append(
|
for names,label in (
|
||||||
' tables missing in database: %s' % \
|
(self.tables_missing_from_A,self.labelA),
|
||||||
', '.join(
|
(self.tables_missing_from_B,self.labelB),
|
||||||
[table.name for table in self.tablesMissingInDatabase]))
|
):
|
||||||
if self.tablesMissingInModel:
|
if names:
|
||||||
out.append(
|
out.append(
|
||||||
' tables missing in model: %s' % \
|
' tables missing from %s: %s' % (
|
||||||
', '.join(
|
label,', '.join(sorted(names))
|
||||||
[table.name for table in self.tablesMissingInModel]))
|
)
|
||||||
if self.tablesWithDiff:
|
)
|
||||||
out.append(
|
|
||||||
' tables with differences: %s' % \
|
for name,td in sorted(self.tables_different.items()):
|
||||||
', '.join([table.name for table in self.tablesWithDiff]))
|
for names,label in (
|
||||||
|
(td.columns_missing_from_A,self.labelA),
|
||||||
|
(td.columns_missing_from_B,self.labelB),
|
||||||
|
):
|
||||||
|
if names:
|
||||||
|
out.append(
|
||||||
|
' %s missing columns from %s: %s' % (
|
||||||
|
name, label,', '.join(sorted(names))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if out:
|
if out:
|
||||||
out.insert(0, 'Schema diffs:')
|
out.insert(0, 'Schema diffs:')
|
||||||
out.extend(colDiffDetails())
|
|
||||||
return '\n'.join(out)
|
return '\n'.join(out)
|
||||||
else:
|
else:
|
||||||
return 'No schema diffs'
|
return 'No schema diffs'
|
||||||
@@ -193,27 +187,8 @@ class SchemaDiff(object):
|
|||||||
"""
|
"""
|
||||||
Used in bool evaluation, return of 0 means no diffs.
|
Used in bool evaluation, return of 0 means no diffs.
|
||||||
"""
|
"""
|
||||||
return len(self.tablesMissingInDatabase) + \
|
return (
|
||||||
len(self.tablesMissingInModel) + len(self.tablesWithDiff)
|
len(self.tables_missing_from_A) +
|
||||||
|
len(self.tables_missing_from_B) +
|
||||||
def storeColumnMissingInDatabase(self, table, col):
|
len(self.tables_different)
|
||||||
if table not in self.tablesWithDiff:
|
)
|
||||||
self.tablesWithDiff.append(table)
|
|
||||||
missingInDatabase, missingInModel, diffDecl = \
|
|
||||||
self.colDiffs.setdefault(table.name, ([], [], []))
|
|
||||||
missingInDatabase.append(col)
|
|
||||||
|
|
||||||
def storeColumnMissingInModel(self, table, col):
|
|
||||||
if table not in self.tablesWithDiff:
|
|
||||||
self.tablesWithDiff.append(table)
|
|
||||||
missingInDatabase, missingInModel, diffDecl = \
|
|
||||||
self.colDiffs.setdefault(table.name, ([], [], []))
|
|
||||||
missingInModel.append(col)
|
|
||||||
|
|
||||||
def storeColumnDiff(self, table, modelCol, databaseCol, modelDecl,
|
|
||||||
databaseDecl):
|
|
||||||
if table not in self.tablesWithDiff:
|
|
||||||
self.tablesWithDiff.append(table)
|
|
||||||
missingInDatabase, missingInModel, diffDecl = \
|
|
||||||
self.colDiffs.setdefault(table.name, ([], [], []))
|
|
||||||
diffDecl.append((modelCol, databaseCol, modelDecl, databaseDecl))
|
|
||||||
|
|||||||
@@ -62,11 +62,10 @@ class PythonScript(base.BaseScript):
|
|||||||
diff = schemadiff.getDiffOfModelAgainstModel(
|
diff = schemadiff.getDiffOfModelAgainstModel(
|
||||||
oldmodel,
|
oldmodel,
|
||||||
model,
|
model,
|
||||||
engine,
|
|
||||||
excludeTables=[repository.version_table])
|
excludeTables=[repository.version_table])
|
||||||
# TODO: diff can be False (there is no difference?)
|
# TODO: diff can be False (there is no difference?)
|
||||||
decls, upgradeCommands, downgradeCommands = \
|
decls, upgradeCommands, downgradeCommands = \
|
||||||
genmodel.ModelGenerator(diff).toUpgradeDowngradePython()
|
genmodel.ModelGenerator(diff,engine).toUpgradeDowngradePython()
|
||||||
|
|
||||||
# Store differences into file.
|
# Store differences into file.
|
||||||
src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
|
src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
|
||||||
|
|||||||
Reference in New Issue
Block a user