implement column type diff'ing
This commit is contained in:
parent
8e5eb8f634
commit
82c5fcd58c
@ -10,7 +10,7 @@ from migrate.changeset import SQLA_06
|
||||
|
||||
from migrate.tests import fixture
|
||||
|
||||
class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
class SchemaDiffBase(fixture.DB):
|
||||
|
||||
level = fixture.DB.CONNECT
|
||||
|
||||
@ -22,12 +22,35 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
if kw.get('create',True):
|
||||
self.table.create()
|
||||
|
||||
def _assert_diff(self,col_A,col_B):
|
||||
self._make_table(col_A)
|
||||
self.meta.clear()
|
||||
self._make_table(col_B,create=False)
|
||||
diff = self._run_diff()
|
||||
# print diff
|
||||
self.assertTrue(diff)
|
||||
eq_(1,len(diff.tables_different))
|
||||
td = diff.tables_different.values()[0]
|
||||
eq_(1,len(td.columns_different))
|
||||
cd = td.columns_different.values()[0]
|
||||
eq_(('Schema diffs:\n'
|
||||
' table with differences: xtable\n'
|
||||
' column with differences: data\n'
|
||||
' model: %r\n'
|
||||
' database: %r')%(
|
||||
cd.col_A,
|
||||
cd.col_B
|
||||
),str(diff))
|
||||
|
||||
class Test_getDiffOfModelAgainstDatabase(SchemaDiffBase):
|
||||
|
||||
def _run_diff(self,**kw):
|
||||
return schemadiff.getDiffOfModelAgainstDatabase(
|
||||
self.meta, self.engine, **kw
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_table_missing_in_db(self):
|
||||
def test_table_missing_in_db(self):
|
||||
self._make_table(create=False)
|
||||
diff = self._run_diff()
|
||||
self.assertTrue(diff)
|
||||
@ -35,7 +58,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
str(diff))
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_table_missing_in_model(self):
|
||||
def test_table_missing_in_model(self):
|
||||
self._make_table()
|
||||
self.meta.clear()
|
||||
diff = self._run_diff()
|
||||
@ -44,7 +67,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
str(diff))
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_column_missing_in_db(self):
|
||||
def test_column_missing_in_db(self):
|
||||
# db
|
||||
Table('xtable', self.meta,
|
||||
Column('id',Integer(), primary_key=True),
|
||||
@ -64,7 +87,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
str(diff))
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_column_missing_in_model(self):
|
||||
def test_column_missing_in_model(self):
|
||||
# db
|
||||
self._make_table(
|
||||
Column('xcol',Integer()),
|
||||
@ -83,7 +106,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
str(diff))
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_exclude_tables(self):
|
||||
def test_exclude_tables(self):
|
||||
# db
|
||||
Table('ytable', self.meta,
|
||||
Column('id',Integer(), primary_key=True),
|
||||
@ -109,14 +132,57 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
eq_('No schema diffs',str(diff))
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_identical_just_pk(self):
|
||||
def test_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):
|
||||
def test_different_type(self):
|
||||
self._assert_diff(
|
||||
Column('data', String(10)),
|
||||
Column('data', Integer()),
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_int_vs_float(self):
|
||||
self._assert_diff(
|
||||
Column('data', Integer()),
|
||||
Column('data', Float()),
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_float_vs_numeric(self):
|
||||
self._assert_diff(
|
||||
Column('data', Float()),
|
||||
Column('data', Numeric()),
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_numeric_precision(self):
|
||||
self._assert_diff(
|
||||
Column('data', Numeric(precision=5)),
|
||||
Column('data', Numeric(precision=6)),
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_numeric_scale(self):
|
||||
self._assert_diff(
|
||||
Column('data', Numeric(precision=6,scale=0)),
|
||||
Column('data', Numeric(precision=6,scale=1)),
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_string_length(self):
|
||||
self._assert_diff(
|
||||
Column('data', String(10)),
|
||||
Column('data', String(20)),
|
||||
)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_integer_identical(self):
|
||||
self._make_table(
|
||||
Column('data', Integer()),
|
||||
)
|
||||
@ -125,7 +191,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
self.assertFalse(diff)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_string_identical(self):
|
||||
def test_string_identical(self):
|
||||
self._make_table(
|
||||
Column('data', String(10)),
|
||||
)
|
||||
@ -134,10 +200,11 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
||||
self.assertFalse(diff)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_getDiffOfModelAgainstDatabase_text_identical(self):
|
||||
def test_text_identical(self):
|
||||
self._make_table(
|
||||
Column('data', Text(255)),
|
||||
)
|
||||
diff = self._run_diff()
|
||||
eq_('No schema diffs',str(diff))
|
||||
self.assertFalse(diff)
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
"""
|
||||
Schema differencing support.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sqlalchemy
|
||||
from migrate.changeset import SQLA_06
|
||||
|
||||
from migrate.changeset import SQLA_06
|
||||
from sqlalchemy.types import Float
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -33,9 +34,66 @@ def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None):
|
||||
return SchemaDiff(metadataA, metadataB, excludeTables)
|
||||
|
||||
|
||||
class ColDiff(object):
|
||||
"""
|
||||
Container for differences in one :class:`~sqlalchemy.schema.Column`
|
||||
between two :class:`~sqlalchemy.schema.Table` instances, ``A``
|
||||
and ``B``.
|
||||
|
||||
.. attribute:: col_A
|
||||
|
||||
The :class:`~sqlalchemy.schema.Column` object for A.
|
||||
|
||||
.. attribute:: col_B
|
||||
|
||||
The :class:`~sqlalchemy.schema.Column` object for B.
|
||||
|
||||
.. attribute:: type_A
|
||||
|
||||
The most generic type of the :class:`~sqlalchemy.schema.Column`
|
||||
object in A.
|
||||
|
||||
.. attribute:: type_B
|
||||
|
||||
The most generic type of the :class:`~sqlalchemy.schema.Column`
|
||||
object in A.
|
||||
|
||||
"""
|
||||
|
||||
diff = False
|
||||
|
||||
def __init__(self,col_A,col_B):
|
||||
self.col_A = col_A
|
||||
self.col_B = col_B
|
||||
|
||||
self.type_A = col_A.type
|
||||
self.type_B = col_B.type
|
||||
|
||||
self.affinity_A = self.type_A._type_affinity
|
||||
self.affinity_B = self.type_B._type_affinity
|
||||
|
||||
if self.affinity_A is not self.affinity_B:
|
||||
self.diff = True
|
||||
return
|
||||
|
||||
if isinstance(self.type_A,Float) or isinstance(self.type_B,Float):
|
||||
if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)):
|
||||
self.diff=True
|
||||
return
|
||||
|
||||
for attr in ('precision','scale','length'):
|
||||
A = getattr(self.type_A,attr,None)
|
||||
B = getattr(self.type_B,attr,None)
|
||||
if not (A is None or B is None) and A!=B:
|
||||
self.diff=True
|
||||
return
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.diff
|
||||
|
||||
class TableDiff(object):
|
||||
"""
|
||||
Container for differences in one :class:`~sqlalchemy.schema.Table
|
||||
Container for differences in one :class:`~sqlalchemy.schema.Table`
|
||||
between two :class:`~sqlalchemy.schema.MetaData` instances, ``A``
|
||||
and ``B``.
|
||||
|
||||
@ -51,7 +109,10 @@ class TableDiff(object):
|
||||
|
||||
.. attribute:: columns_different
|
||||
|
||||
An empty dictionary, for future use...
|
||||
A dictionary containing information about columns that were
|
||||
found to be different.
|
||||
It maps column names to a :class:`ColDiff` objects describing the
|
||||
differences found.
|
||||
"""
|
||||
__slots__ = (
|
||||
'columns_missing_from_A',
|
||||
@ -59,11 +120,11 @@ class TableDiff(object):
|
||||
'columns_different',
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
return (
|
||||
len(self.columns_missing_from_A)+
|
||||
len(self.columns_missing_from_B)+
|
||||
len(self.columns_different)
|
||||
def __nonzero__(self):
|
||||
return bool(
|
||||
self.columns_missing_from_A or
|
||||
self.columns_missing_from_B or
|
||||
self.columns_different
|
||||
)
|
||||
|
||||
class SchemaDiff(object):
|
||||
@ -95,6 +156,23 @@ class SchemaDiff(object):
|
||||
|
||||
:param excludeTables:
|
||||
A sequence of table names to exclude.
|
||||
|
||||
.. attribute:: tables_missing_from_A
|
||||
|
||||
A sequence of table names that were found in B but weren't in
|
||||
A.
|
||||
|
||||
.. attribute:: tables_missing_from_B
|
||||
|
||||
A sequence of table names that were found in A but weren't in
|
||||
B.
|
||||
|
||||
.. attribute:: tables_different
|
||||
|
||||
A dictionary containing information about tables that were found
|
||||
to be different.
|
||||
It maps table names to a :class:`TableDiff` objects describing the
|
||||
differences found.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@ -105,6 +183,7 @@ class SchemaDiff(object):
|
||||
|
||||
self.metadataA, self.metadataB = metadataA, metadataB
|
||||
self.labelA, self.labelB = labelA, labelB
|
||||
self.label_width = max(len(labelA),len(labelB))
|
||||
excludeTables = set(excludeTables or [])
|
||||
|
||||
A_table_names = set(metadataA.tables.keys())
|
||||
@ -138,11 +217,15 @@ class SchemaDiff(object):
|
||||
|
||||
td.columns_different = {}
|
||||
|
||||
# XXX - should check columns differences
|
||||
#for col_name in A_column_names.intersection(B_column_names):
|
||||
#
|
||||
# A_col = A_table.columns.get(col_name)
|
||||
# B_col = B_table.columns.get(col_name)
|
||||
for col_name in A_column_names.intersection(B_column_names):
|
||||
|
||||
cd = ColDiff(
|
||||
A_table.columns.get(col_name),
|
||||
B_table.columns.get(col_name)
|
||||
)
|
||||
|
||||
if cd:
|
||||
td.columns_different[col_name]=cd
|
||||
|
||||
# XXX - index and constraint differences should
|
||||
# be checked for here
|
||||
@ -153,6 +236,7 @@ class SchemaDiff(object):
|
||||
def __str__(self):
|
||||
''' Summarize differences. '''
|
||||
out = []
|
||||
column_template =' %%%is: %%r' % self.label_width
|
||||
|
||||
for names,label in (
|
||||
(self.tables_missing_from_A,self.labelA),
|
||||
@ -179,6 +263,10 @@ class SchemaDiff(object):
|
||||
label,', '.join(sorted(names))
|
||||
)
|
||||
)
|
||||
for name,cd in td.columns_different.items():
|
||||
out.append(' column with differences: %s' % name)
|
||||
out.append(column_template % (self.labelA,cd.col_A))
|
||||
out.append(column_template % (self.labelB,cd.col_B))
|
||||
|
||||
if out:
|
||||
out.insert(0, 'Schema diffs:')
|
||||
|
Loading…
x
Reference in New Issue
Block a user