implement column type diff'ing

This commit is contained in:
chrisw 2010-09-15 23:36:11 +01:00
parent 8e5eb8f634
commit 82c5fcd58c
2 changed files with 180 additions and 25 deletions

View File

@ -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_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_getDiffOfModelAgainstDatabase_integer_identical(self):
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)

View File

@ -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,12 +217,16 @@ 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:')