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
|
from migrate.tests import fixture
|
||||||
|
|
||||||
class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
class SchemaDiffBase(fixture.DB):
|
||||||
|
|
||||||
level = fixture.DB.CONNECT
|
level = fixture.DB.CONNECT
|
||||||
|
|
||||||
@ -22,12 +22,35 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
if kw.get('create',True):
|
if kw.get('create',True):
|
||||||
self.table.create()
|
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):
|
def _run_diff(self,**kw):
|
||||||
return schemadiff.getDiffOfModelAgainstDatabase(
|
return schemadiff.getDiffOfModelAgainstDatabase(
|
||||||
self.meta, self.engine, **kw
|
self.meta, self.engine, **kw
|
||||||
)
|
)
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_table_missing_in_db(self):
|
def test_table_missing_in_db(self):
|
||||||
self._make_table(create=False)
|
self._make_table(create=False)
|
||||||
diff = self._run_diff()
|
diff = self._run_diff()
|
||||||
self.assertTrue(diff)
|
self.assertTrue(diff)
|
||||||
@ -35,7 +58,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
str(diff))
|
str(diff))
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_table_missing_in_model(self):
|
def test_table_missing_in_model(self):
|
||||||
self._make_table()
|
self._make_table()
|
||||||
self.meta.clear()
|
self.meta.clear()
|
||||||
diff = self._run_diff()
|
diff = self._run_diff()
|
||||||
@ -44,7 +67,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
str(diff))
|
str(diff))
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_column_missing_in_db(self):
|
def test_column_missing_in_db(self):
|
||||||
# db
|
# db
|
||||||
Table('xtable', self.meta,
|
Table('xtable', self.meta,
|
||||||
Column('id',Integer(), primary_key=True),
|
Column('id',Integer(), primary_key=True),
|
||||||
@ -64,7 +87,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
str(diff))
|
str(diff))
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_column_missing_in_model(self):
|
def test_column_missing_in_model(self):
|
||||||
# db
|
# db
|
||||||
self._make_table(
|
self._make_table(
|
||||||
Column('xcol',Integer()),
|
Column('xcol',Integer()),
|
||||||
@ -83,7 +106,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
str(diff))
|
str(diff))
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_exclude_tables(self):
|
def test_exclude_tables(self):
|
||||||
# db
|
# db
|
||||||
Table('ytable', self.meta,
|
Table('ytable', self.meta,
|
||||||
Column('id',Integer(), primary_key=True),
|
Column('id',Integer(), primary_key=True),
|
||||||
@ -109,14 +132,57 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
eq_('No schema diffs',str(diff))
|
eq_('No schema diffs',str(diff))
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_identical_just_pk(self):
|
def test_identical_just_pk(self):
|
||||||
self._make_table()
|
self._make_table()
|
||||||
diff = self._run_diff()
|
diff = self._run_diff()
|
||||||
self.assertFalse(diff)
|
self.assertFalse(diff)
|
||||||
eq_('No schema diffs',str(diff))
|
eq_('No schema diffs',str(diff))
|
||||||
|
|
||||||
|
|
||||||
@fixture.usedb()
|
@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(
|
self._make_table(
|
||||||
Column('data', Integer()),
|
Column('data', Integer()),
|
||||||
)
|
)
|
||||||
@ -125,7 +191,7 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
self.assertFalse(diff)
|
self.assertFalse(diff)
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_string_identical(self):
|
def test_string_identical(self):
|
||||||
self._make_table(
|
self._make_table(
|
||||||
Column('data', String(10)),
|
Column('data', String(10)),
|
||||||
)
|
)
|
||||||
@ -134,10 +200,11 @@ class Test_getDiffOfModelAgainstDatabase(fixture.DB):
|
|||||||
self.assertFalse(diff)
|
self.assertFalse(diff)
|
||||||
|
|
||||||
@fixture.usedb()
|
@fixture.usedb()
|
||||||
def test_getDiffOfModelAgainstDatabase_text_identical(self):
|
def test_text_identical(self):
|
||||||
self._make_table(
|
self._make_table(
|
||||||
Column('data', Text(255)),
|
Column('data', Text(255)),
|
||||||
)
|
)
|
||||||
diff = self._run_diff()
|
diff = self._run_diff()
|
||||||
eq_('No schema diffs',str(diff))
|
eq_('No schema diffs',str(diff))
|
||||||
self.assertFalse(diff)
|
self.assertFalse(diff)
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
Schema differencing support.
|
Schema differencing support.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from migrate.changeset import SQLA_06
|
|
||||||
|
|
||||||
|
from migrate.changeset import SQLA_06
|
||||||
|
from sqlalchemy.types import Float
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -33,9 +34,66 @@ def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None):
|
|||||||
return SchemaDiff(metadataA, metadataB, excludeTables)
|
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):
|
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``
|
between two :class:`~sqlalchemy.schema.MetaData` instances, ``A``
|
||||||
and ``B``.
|
and ``B``.
|
||||||
|
|
||||||
@ -51,7 +109,10 @@ class TableDiff(object):
|
|||||||
|
|
||||||
.. attribute:: columns_different
|
.. 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__ = (
|
__slots__ = (
|
||||||
'columns_missing_from_A',
|
'columns_missing_from_A',
|
||||||
@ -59,11 +120,11 @@ class TableDiff(object):
|
|||||||
'columns_different',
|
'columns_different',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __len__(self):
|
def __nonzero__(self):
|
||||||
return (
|
return bool(
|
||||||
len(self.columns_missing_from_A)+
|
self.columns_missing_from_A or
|
||||||
len(self.columns_missing_from_B)+
|
self.columns_missing_from_B or
|
||||||
len(self.columns_different)
|
self.columns_different
|
||||||
)
|
)
|
||||||
|
|
||||||
class SchemaDiff(object):
|
class SchemaDiff(object):
|
||||||
@ -95,6 +156,23 @@ class SchemaDiff(object):
|
|||||||
|
|
||||||
:param excludeTables:
|
:param excludeTables:
|
||||||
A sequence of table names to exclude.
|
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,
|
def __init__(self,
|
||||||
@ -105,6 +183,7 @@ class SchemaDiff(object):
|
|||||||
|
|
||||||
self.metadataA, self.metadataB = metadataA, metadataB
|
self.metadataA, self.metadataB = metadataA, metadataB
|
||||||
self.labelA, self.labelB = labelA, labelB
|
self.labelA, self.labelB = labelA, labelB
|
||||||
|
self.label_width = max(len(labelA),len(labelB))
|
||||||
excludeTables = set(excludeTables or [])
|
excludeTables = set(excludeTables or [])
|
||||||
|
|
||||||
A_table_names = set(metadataA.tables.keys())
|
A_table_names = set(metadataA.tables.keys())
|
||||||
@ -138,11 +217,15 @@ class SchemaDiff(object):
|
|||||||
|
|
||||||
td.columns_different = {}
|
td.columns_different = {}
|
||||||
|
|
||||||
# XXX - should check columns differences
|
for col_name in A_column_names.intersection(B_column_names):
|
||||||
#for col_name in A_column_names.intersection(B_column_names):
|
|
||||||
#
|
cd = ColDiff(
|
||||||
# A_col = A_table.columns.get(col_name)
|
A_table.columns.get(col_name),
|
||||||
# B_col = B_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
|
# XXX - index and constraint differences should
|
||||||
# be checked for here
|
# be checked for here
|
||||||
@ -153,6 +236,7 @@ class SchemaDiff(object):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
''' Summarize differences. '''
|
''' Summarize differences. '''
|
||||||
out = []
|
out = []
|
||||||
|
column_template =' %%%is: %%r' % self.label_width
|
||||||
|
|
||||||
for names,label in (
|
for names,label in (
|
||||||
(self.tables_missing_from_A,self.labelA),
|
(self.tables_missing_from_A,self.labelA),
|
||||||
@ -179,6 +263,10 @@ class SchemaDiff(object):
|
|||||||
label,', '.join(sorted(names))
|
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:
|
if out:
|
||||||
out.insert(0, 'Schema diffs:')
|
out.insert(0, 'Schema diffs:')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user