From a8c31eb25f89542c9be72d0a8f90b4d984b6aead Mon Sep 17 00:00:00 2001 From: iElectric Date: Mon, 22 Jun 2009 10:22:06 +0000 Subject: [PATCH] adding basic support for firebird, fixes #55 --- docs/api.rst | 10 ++++ docs/changelog.rst | 1 + docs/index.rst | 58 +++++++++++----------- migrate/changeset/ansisql.py | 7 ++- migrate/changeset/constraint.py | 1 + migrate/changeset/databases/firebird.py | 62 +++++++++++++++++++++++ migrate/changeset/databases/visitor.py | 7 ++- migrate/versioning/base/const.py | 2 +- migrate/versioning/schema.py | 2 +- test/changeset/test_changeset.py | 44 +++++++++++------ test/changeset/test_constraint.py | 66 ++++++++++++++----------- test/versioning/test_schemadiff.py | 34 ++++++------- 12 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 migrate/changeset/databases/firebird.py diff --git a/docs/api.rst b/docs/api.rst index 1edd4eb..045c809 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -39,6 +39,16 @@ Module :mod:`mysql ` :members: :synopsis: MySQL database specific changeset implementations +.. _firebird-d: + +Module :mod:`firebird ` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +.. automodule:: migrate.changeset.databases.firebird + :members: + :synopsis: Firebird database specific changeset implementations + .. _oracle-d: Module :mod:`oracle ` diff --git a/docs/changelog.rst b/docs/changelog.rst index 11efbda..25853b8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ 0.5.5 ----- +- added support for :ref:`firebird ` - server_defaults passed to column.create are now issued correctly - constraints passed to column.create are correctly interpreted (ALTER TABLE ADD CONSTRAINT is issued after ADD COLUMN) - column.create accepts `primary_key_name`, `unique_name` and `index_name` as string value which is used as contraint name when adding a column diff --git a/docs/index.rst b/docs/index.rst index 41aaf51..1cbf67d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,40 +43,40 @@ Download and Development Dialect support --------------- -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| Operation / Dialect | :ref:`sqlite ` | :ref:`postgres ` | :ref:`mysql ` | :ref:`oracle ` | firebird | mssql | -| | | | | | | | -+=========================================================+==========================+==============================+========================+===========================+==========+=======+ -| :ref:`ALTER TABLE RENAME TABLE ` | yes | yes | yes | yes | | | -| | | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`ALTER TABLE RENAME COLUMN ` | yes | yes | yes | yes | | | -| | (workaround) [#1]_ | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`ALTER TABLE ADD COLUMN ` | yes | yes | yes | yes | | | -| | (with limitations) [#2]_ | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`ALTER TABLE DROP COLUMN ` | yes | yes | yes | yes | | | -| | (workaround) [#1]_ | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`ALTER TABLE ALTER COLUMN ` | no | yes | yes | yes | | | -| | | | | (with limitations) [#3]_ | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`ALTER TABLE ADD CONSTRAINT ` | no | yes | yes | yes | | | -| | | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`ALTER TABLE DROP CONSTRAINT `| no | yes | yes | yes | | | -| | | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ -| :ref:`RENAME INDEX ` | no | yes | no | yes | | | -| | | | | | | | -+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+ ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| Operation / Dialect | :ref:`sqlite ` | :ref:`postgres ` | :ref:`mysql ` | :ref:`oracle ` | :ref:`firebird ` | mssql | +| | | | | | | | ++=========================================================+==========================+==============================+========================+===========================+===============================+=======+ +| :ref:`ALTER TABLE RENAME TABLE ` | yes | yes | yes | yes | no | | +| | | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`ALTER TABLE RENAME COLUMN ` | yes | yes | yes | yes | yes | | +| | (workaround) [#1]_ | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`ALTER TABLE ADD COLUMN ` | yes | yes | yes | yes | yes | | +| | (with limitations) [#2]_ | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`ALTER TABLE DROP COLUMN ` | yes | yes | yes | yes | yes | | +| | (workaround) [#1]_ | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`ALTER TABLE ALTER COLUMN ` | no | yes | yes | yes | yes [#4]_ | | +| | | | | (with limitations) [#3]_ | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`ALTER TABLE ADD CONSTRAINT ` | no | yes | yes | yes | yes | | +| | | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`ALTER TABLE DROP CONSTRAINT `| no | yes | yes | yes | yes | | +| | | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ +| :ref:`RENAME INDEX ` | no | yes | no | yes | yes | | +| | | | | | | | ++---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+-------------------------------+-------+ .. [#1] Table is renamed to temporary table, new table is created followed by INSERT statements. .. [#2] Visit http://www.sqlite.org/lang_altertable.html for more information. .. [#3] You can not change datatype or rename column if table has NOT NULL data, see http://blogs.x2line.com/al/archive/2005/08/30/1231.aspx for more information. - +.. [#4] Changing nullable is not supported Documentation ------------- diff --git a/migrate/changeset/ansisql.py b/migrate/changeset/ansisql.py index 580448e..7fb5e13 100644 --- a/migrate/changeset/ansisql.py +++ b/migrate/changeset/ansisql.py @@ -189,7 +189,7 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator): """Starts ALTER COLUMN""" self.start_alter_table(table) # TODO: use preparer.format_column - self.append("ALTER COLUMN %s " % self.preparer.quote_identifier(col_name)) + self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, table.quote)) def _visit_column_nullable(self, table, col_name, delta): nullable = delta['nullable'] @@ -306,9 +306,12 @@ class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper): constraint.name = self.get_constraint_name(constraint) self.append(self.preparer.format_constraint(constraint)) if constraint.cascade: - self.append(" CASCADE") + self.cascade_constraint(constraint) self.execute() + def cascade_constraint(self, constraint): + self.append(" CASCADE") + class ANSIDialect(DefaultDialect): columngenerator = ANSIColumnGenerator diff --git a/migrate/changeset/constraint.py b/migrate/changeset/constraint.py index ae103ab..b09b30a 100644 --- a/migrate/changeset/constraint.py +++ b/migrate/changeset/constraint.py @@ -39,6 +39,7 @@ class ConstraintChangeset(object): :keyword:`None` the instance's engine will be used :type engine: :class:`sqlalchemy.engine.base.Engine` """ + # TODO: set the parent here instead of in __init__ self.__do_imports('constraintgenerator', *a, **kw) def drop(self, *a, **kw): diff --git a/migrate/changeset/databases/firebird.py b/migrate/changeset/databases/firebird.py new file mode 100644 index 0000000..215762c --- /dev/null +++ b/migrate/changeset/databases/firebird.py @@ -0,0 +1,62 @@ +""" + Firebird database specific implementations of changeset classes. +""" +from sqlalchemy.databases import firebird as sa_base + +from migrate.changeset import ansisql, exceptions + + +FBSchemaGenerator = sa_base.FBSchemaGenerator + + +class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator): + """Firebird column generator implementation.""" + + +class FBColumnDropper(ansisql.ANSIColumnDropper): + """Firebird column dropper implementation.""" + + def visit_column(self, column): + table = self.start_alter_table(column) + self.append('DROP %s' % self.preparer.format_column(column)) + self.execute() + + +class FBSchemaChanger(ansisql.ANSISchemaChanger): + """Firebird schema changer implementation.""" + + def visit_table(self, table): + """Rename table not supported""" + raise exceptions.NotSupportedError( + "Firebird does not support renaming tables.") + + def _visit_column_name(self, table, col_name, delta): + new_name = delta['name'] + self.start_alter_table(table) + self.append('ALTER COLUMN %s TO %s' % ((col_name), (new_name))) + + def _visit_column_nullable(self, table, col_name, delta): + """Changing NULL is not supported""" + # TODO: http://www.firebirdfaq.org/faq103/ + raise exceptions.NotSupportedError( + "Firebird does not support altering NULL bevahior.") + + +class FBConstraintGenerator(ansisql.ANSIConstraintGenerator): + """Firebird constraint generator implementation.""" + + +class FBConstraintDropper(ansisql.ANSIConstraintDropper): + """Firebird constaint dropper implementation.""" + + def cascade_constraint(self, constraint): + raise exceptions.NotSupportedError( + "Firebird does not support cascading constraints") + + +class FBDialect(ansisql.ANSIDialect): + columngenerator = FBColumnGenerator + columndropper = FBColumnDropper + schemachanger = FBSchemaChanger + constraintgenerator = FBConstraintGenerator + constraintdropper = FBConstraintDropper diff --git a/migrate/changeset/databases/visitor.py b/migrate/changeset/databases/visitor.py index 8a084fb..18f1ac0 100644 --- a/migrate/changeset/databases/visitor.py +++ b/migrate/changeset/databases/visitor.py @@ -4,7 +4,11 @@ import sqlalchemy as sa from migrate.changeset import ansisql -from migrate.changeset.databases import sqlite, postgres, mysql, oracle +from migrate.changeset.databases import (sqlite, + postgres, + mysql, + oracle, + firebird) # Map SA dialects to the corresponding Migrate extensions @@ -14,6 +18,7 @@ DIALECTS = { sa.databases.postgres.PGDialect: postgres.PGDialect, sa.databases.mysql.MySQLDialect: mysql.MySQLDialect, sa.databases.oracle.OracleDialect: oracle.OracleDialect, + sa.databases.firebird.FBDialect: firebird.FBDialect, } diff --git a/migrate/versioning/base/const.py b/migrate/versioning/base/const.py index 590f6c0..7c24245 100644 --- a/migrate/versioning/base/const.py +++ b/migrate/versioning/base/const.py @@ -3,7 +3,7 @@ from sqlalchemy.util import OrderedDict __all__ = ['databases', 'operations'] -databases = ('sqlite', 'postgres', 'mysql', 'oracle', 'mssql') +databases = ('sqlite', 'postgres', 'mysql', 'oracle', 'mssql', 'firebird') # Map operation names to function names operations = OrderedDict() diff --git a/migrate/versioning/schema.py b/migrate/versioning/schema.py index 8bb658d..754288f 100644 --- a/migrate/versioning/schema.py +++ b/migrate/versioning/schema.py @@ -163,7 +163,7 @@ class ControlledSchema(object): table = Table( tname, meta, - Column('repository_id', String(255), primary_key=True), + Column('repository_id', String(250), primary_key=True), Column('repository_path', Text), Column('version', Integer), ) diff --git a/test/changeset/test_changeset.py b/test/changeset/test_changeset.py index 03c0d9b..d141e12 100644 --- a/test/changeset/test_changeset.py +++ b/test/changeset/test_changeset.py @@ -183,7 +183,7 @@ class TestAddDropColumn(fixture.DB): @fixture.usedb(not_supported='sqlite') def test_pk(self): """Can create columns with primary key""" - col = Column('data', Integer) + col = Column('data', Integer, nullable=False) self.assertRaises(changeset.exceptions.InvalidConstraintError, col.create, self.table, primary_key_name=True) col.create(self.table, primary_key_name='data_pkey') @@ -192,7 +192,8 @@ class TestAddDropColumn(fixture.DB): self.table.insert(values={'data': 4}).execute() try: self.table.insert(values={'data': 4}).execute() - except sqlalchemy.exc.IntegrityError: + except (sqlalchemy.exc.IntegrityError, + sqlalchemy.exc.ProgrammingError): pass else: self.fail() @@ -211,7 +212,8 @@ class TestAddDropColumn(fixture.DB): self.table.insert(values={'data': 5}).execute() try: self.table.insert(values={'data': 3}).execute() - except sqlalchemy.exc.IntegrityError: + except (sqlalchemy.exc.IntegrityError, + sqlalchemy.exc.ProgrammingError): pass else: self.fail() @@ -230,7 +232,8 @@ class TestAddDropColumn(fixture.DB): self.table.insert(values={'data': 5}).execute() try: self.table.insert(values={'data': 5}).execute() - except sqlalchemy.exc.IntegrityError: + except (sqlalchemy.exc.IntegrityError, + sqlalchemy.exc.ProgrammingError): pass else: self.fail() @@ -249,11 +252,13 @@ class TestAddDropColumn(fixture.DB): self.table.insert(values={'data': 5}).execute() try: self.table.insert(values={'data': 5}).execute() - except sqlalchemy.exc.IntegrityError: + except (sqlalchemy.exc.IntegrityError, + sqlalchemy.exc.ProgrammingError): pass else: self.fail() + Index('ix_data', col).drop(bind=self.engine) col.drop() @fixture.usedb() @@ -272,6 +277,7 @@ class TestAddDropColumn(fixture.DB): # TODO: test that if column is appended on creation and removed on deletion # TODO: test column.alter with all changes at one time # TODO: test quoting + # TODO: test drop default class TestRename(fixture.DB): @@ -283,7 +289,7 @@ class TestRename(fixture.DB): super(TestRename, self)._setup(url) self.meta.bind = self.engine - @fixture.usedb() + @fixture.usedb(not_supported='firebird') def test_rename_table(self): """Tables can be renamed""" c_name = 'col_1' @@ -463,21 +469,29 @@ class TestColumnChange(fixture.DB): self.table.c.data.alter(Column('data', String(42))) self.refresh_table(self.table.name) self.assert_(isinstance(self.table.c.data.type, String)) - self.assertEquals(self.table.c.data.type.length, 42) + if self.engine.name == 'firebird': + self.assertEquals(self.table.c.data.type.length, 42 * 4) + else: + self.assertEquals(self.table.c.data.type.length, 42) # Just the new type - self.table.c.data.alter(type=String(21)) + self.table.c.data.alter(type=String(43)) self.refresh_table(self.table.name) self.assert_(isinstance(self.table.c.data.type, String)) - self.assertEquals(self.table.c.data.type.length, 21) + if self.engine.name == 'firebird': + self.assertEquals(self.table.c.data.type.length, 43 * 4) + else: + self.assertEquals(self.table.c.data.type.length, 43) # Different type self.assert_(isinstance(self.table.c.id.type, Integer)) self.assertEquals(self.table.c.id.nullable, False) - self.table.c.id.alter(type=String(20)) - self.assertEquals(self.table.c.id.nullable, False) - self.refresh_table(self.table.name) - self.assert_(isinstance(self.table.c.id.type, String)) + + if not self.engine.name == 'firebird': + self.table.c.id.alter(type=String(20)) + self.assertEquals(self.table.c.id.nullable, False) + self.refresh_table(self.table.name) + self.assert_(isinstance(self.table.c.id.type, String)) @fixture.usedb() def test_default(self): @@ -511,14 +525,14 @@ class TestColumnChange(fixture.DB): self.assert_(row['data'] is None, row['data']) - @fixture.usedb() + @fixture.usedb(not_supported='firebird') def test_null(self): """Can change a column's null constraint""" self.assertEquals(self.table.c.data.nullable, True) # Column object self.table.c.data.alter(Column('data', String(40), nullable=False)) - self.table.nullable=None + self.table.nullable = None self.refresh_table(self.table.name) self.assertEquals(self.table.c.data.nullable, False) diff --git a/test/changeset/test_constraint.py b/test/changeset/test_constraint.py index 12760be..82b796b 100644 --- a/test/changeset/test_constraint.py +++ b/test/changeset/test_constraint.py @@ -6,6 +6,7 @@ from sqlalchemy.util import * from sqlalchemy.exc import * from migrate.changeset import * +from migrate.changeset.exceptions import * from test import fixture @@ -31,8 +32,8 @@ class CommonTestConstraint(fixture.DB): self.meta = MetaData(self.engine) self.tablename = 'mytable' self.table = Table(self.tablename, self.meta, - Column('id', Integer), - Column('fkey', Integer), + Column('id', Integer, nullable=False), + Column('fkey', Integer, nullable=False), mysql_engine='InnoDB') if self.engine.has_table(self.table.name): self.table.drop() @@ -49,20 +50,21 @@ class TestConstraint(CommonTestConstraint): def _define_pk(self, *cols): # Add a pk by creating a PK constraint - pk = PrimaryKeyConstraint(table=self.table, *cols) - self.assertEquals(list(pk.columns), list(cols)) - if self.url.startswith('oracle'): + if (self.engine.name in ('oracle', 'firebird')): # Can't drop Oracle PKs without an explicit name - pk.name = 'fgsfds' + pk = PrimaryKeyConstraint(table=self.table, name='temp_pk_key', *cols) + else: + pk = PrimaryKeyConstraint(table=self.table, *cols) + self.assertEquals(list(pk.columns), list(cols)) pk.create() self.refresh_table() if not self.url.startswith('sqlite'): self.assertEquals(list(self.table.primary_key), list(cols)) # Drop the PK constraint - if not self.url.startswith('oracle'): - # Apparently Oracle PK names aren't introspected - pk.name = self.table.primary_key.name + #if (self.engine.name in ('oracle', 'firebird')): + # # Apparently Oracle PK names aren't introspected + # pk.name = self.table.primary_key.name pk.drop() self.refresh_table() self.assertEquals(len(self.table.primary_key), 0) @@ -113,18 +115,23 @@ class TestConstraint(CommonTestConstraint): @fixture.usedb() def test_define_pk_multi(self): """Multicolumn PK constraints can be defined, created, and dropped""" - #self.engine.echo=True self._define_pk(self.table.c.id, self.table.c.fkey) @fixture.usedb() def test_drop_cascade(self): """Drop constraint cascaded""" + pk = PrimaryKeyConstraint('fkey', table=self.table, name="id_pkey") pk.create() self.refresh_table() # Drop the PK constraint forcing cascade - pk.drop(cascade=True) + try: + pk.drop(cascade=True) + except NotSupportedError: + if self.engine.name == 'firebird': + pass + # TODO: add real assertion if it was added @fixture.usedb(supported=['mysql']) @@ -149,10 +156,10 @@ class TestConstraint(CommonTestConstraint): cons.create() self.refresh_table() - self.table.insert(values={'id': 4}).execute() + self.table.insert(values={'id': 4, 'fkey': 1}).execute() try: - self.table.insert(values={'id': 1}).execute() - except IntegrityError: + self.table.insert(values={'id': 1, 'fkey': 1}).execute() + except (IntegrityError, ProgrammingError): pass else: self.fail() @@ -160,8 +167,8 @@ class TestConstraint(CommonTestConstraint): # Remove the name, drop the constraint; it should succeed cons.drop() self.refresh_table() - self.table.insert(values={'id': 2}).execute() - self.table.insert(values={'id': 1}).execute() + self.table.insert(values={'id': 2, 'fkey': 2}).execute() + self.table.insert(values={'id': 1, 'fkey': 2}).execute() class TestAutoname(CommonTestConstraint): @@ -170,7 +177,7 @@ class TestAutoname(CommonTestConstraint): """ level = fixture.DB.CONNECT - @fixture.usedb(not_supported='oracle') + @fixture.usedb(not_supported=['oracle', 'firebird']) def test_autoname_pk(self): """PrimaryKeyConstraints can guess their name if None is given""" # Don't supply a name; it should create one @@ -197,7 +204,7 @@ class TestAutoname(CommonTestConstraint): cons.name = None cons.drop() - @fixture.usedb(not_supported=['oracle', 'sqlite']) + @fixture.usedb(not_supported=['oracle', 'sqlite', 'firebird']) def test_autoname_fk(self): """ForeignKeyConstraints can guess their name if None is given""" cons = PrimaryKeyConstraint(self.table.c.id) @@ -230,13 +237,12 @@ class TestAutoname(CommonTestConstraint): cons = CheckConstraint('id > 3', columns=[self.table.c.id]) cons.create() self.refresh_table() - if not self.engine.name == 'mysql': - self.table.insert(values={'id': 4}).execute() + self.table.insert(values={'id': 4, 'fkey': 1}).execute() try: - self.table.insert(values={'id': 1}).execute() - except IntegrityError: + self.table.insert(values={'id': 1, 'fkey': 2}).execute() + except (IntegrityError, ProgrammingError): pass else: self.fail() @@ -245,8 +251,8 @@ class TestAutoname(CommonTestConstraint): cons.name = None cons.drop() self.refresh_table() - self.table.insert(values={'id': 2}).execute() - self.table.insert(values={'id': 1}).execute() + self.table.insert(values={'id': 2, 'fkey': 2}).execute() + self.table.insert(values={'id': 1, 'fkey': 3}).execute() @fixture.usedb(not_supported=['oracle', 'sqlite']) def test_autoname_unique(self): @@ -254,12 +260,12 @@ class TestAutoname(CommonTestConstraint): cons = UniqueConstraint(self.table.c.fkey) cons.create() self.refresh_table() - - self.table.insert(values={'fkey': 4}).execute() + self.table.insert(values={'fkey': 4, 'id': 1}).execute() try: - self.table.insert(values={'fkey': 4}).execute() - except IntegrityError: + self.table.insert(values={'fkey': 4, 'id': 2}).execute() + except (sqlalchemy.exc.IntegrityError, + sqlalchemy.exc.ProgrammingError): pass else: self.fail() @@ -268,5 +274,5 @@ class TestAutoname(CommonTestConstraint): cons.name = None cons.drop() self.refresh_table() - self.table.insert(values={'fkey': 4}).execute() - self.table.insert(values={'fkey': 4}).execute() + self.table.insert(values={'fkey': 4, 'id': 2}).execute() + self.table.insert(values={'fkey': 4, 'id': 1}).execute() diff --git a/test/versioning/test_schemadiff.py b/test/versioning/test_schemadiff.py index ea383eb..c82e5b1 100644 --- a/test/versioning/test_schemadiff.py +++ b/test/versioning/test_schemadiff.py @@ -139,20 +139,20 @@ class TestSchemaDiff(fixture.DB): # Not even using sqlalchemy.PassiveDefault helps because we're doing explicit column select. self.engine.execute(self.table.delete(), id=dataId) - # 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], []) - + 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], [])