finally, tests pass for all supported dialects
This commit is contained in:
parent
4a2cd3797f
commit
17cc5f36e6
@ -47,35 +47,36 @@ Dialect support
|
||||
| Operation / Dialect | :ref:`sqlite <sqlite-d>` | :ref:`postgres <postgres-d>` | :ref:`mysql <mysql-d>` | :ref:`oracle <oracle-d>` | firebird | mssql |
|
||||
| | | | | | | |
|
||||
+=========================================================+==========================+==============================+========================+===========================+==========+=======+
|
||||
| :ref:`ALTER TABLE RENAME TABLE <table-rename>` | yes | yes | yes | | | |
|
||||
| :ref:`ALTER TABLE RENAME TABLE <table-rename>` | yes | yes | yes | yes | | |
|
||||
| | | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE RENAME COLUMN <column-alter>` | yes | yes | yes | | | |
|
||||
| :ref:`ALTER TABLE RENAME COLUMN <column-alter>` | yes | yes | yes | yes | | |
|
||||
| | (workaround) [#1]_ | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE ADD COLUMN <column-create>` | yes | yes | yes | | | |
|
||||
| :ref:`ALTER TABLE ADD COLUMN <column-create>` | yes | yes | yes | yes | | |
|
||||
| | (with limitations) [#2]_ | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE DROP COLUMN <column-drop>` | yes | yes | yes | | | |
|
||||
| :ref:`ALTER TABLE DROP COLUMN <column-drop>` | yes | yes | yes | yes | | |
|
||||
| | (workaround) [#1]_ | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE ALTER COLUMN <column-alter>` | no | yes | yes | | | |
|
||||
| :ref:`ALTER TABLE ALTER COLUMN <column-alter>` | no | yes | yes | yes | | |
|
||||
| | | | | (with limitations) [#3]_ | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE ADD CONSTRAINT <constraint-tutorial>` | no | yes | yes | yes | | |
|
||||
| | | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE ADD CONSTRAINT <constraint-tutorial>` | no | yes | yes | | | |
|
||||
| :ref:`ALTER TABLE DROP CONSTRAINT <constraint-tutorial>`| no | yes | yes | yes | | |
|
||||
| | | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`ALTER TABLE DROP CONSTRAINT <constraint-tutorial>`| no | yes | yes | | | |
|
||||
| | | | | | | |
|
||||
+---------------------------------------------------------+--------------------------+------------------------------+------------------------+---------------------------+----------+-------+
|
||||
| :ref:`RENAME INDEX <index-rename>` | no | yes | no | | | |
|
||||
| :ref:`RENAME INDEX <index-rename>` | no | yes | no | 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.
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
@ -220,10 +220,7 @@ class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
|
||||
def _visit_column_name(self, table, col_name, delta):
|
||||
new_name = delta['name']
|
||||
self.start_alter_table(table)
|
||||
# TODO: use preparer.format_column
|
||||
self.append('RENAME COLUMN %s TO %s' % \
|
||||
(self.preparer.quote_identifier(col_name),
|
||||
self.preparer.quote_identifier(new_name)))
|
||||
self.append('RENAME COLUMN %s TO %s' % (col_name, new_name))
|
||||
|
||||
|
||||
class ANSIConstraintCommon(AlterTableVisitor):
|
||||
|
@ -32,19 +32,25 @@ class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
|
||||
column.nullable = orig
|
||||
return ret
|
||||
|
||||
def visit_column(self, delta):
|
||||
def visit_column(self, column):
|
||||
delta = column.delta
|
||||
keys = delta.keys()
|
||||
if 'type' in keys or 'nullable' in keys or 'default' in keys \
|
||||
or 'server_default' in keys:
|
||||
self._run_subvisit(delta, self._visit_column_change)
|
||||
|
||||
if len(set(('type', 'nullable', 'server_default')).intersection(keys)):
|
||||
self._run_subvisit(delta,
|
||||
self._visit_column_change,
|
||||
start_alter=False)
|
||||
# change name as the last action to avoid conflicts
|
||||
if 'name' in keys:
|
||||
self._run_subvisit(delta, self._visit_column_name)
|
||||
self._run_subvisit(delta,
|
||||
self._visit_column_name,
|
||||
start_alter=False)
|
||||
|
||||
def _visit_column_change(self, table, col_name, delta):
|
||||
if not hasattr(delta, 'result_column'):
|
||||
# Oracle needs the whole column definition, not just a lone name/type
|
||||
raise exceptions.NotSupportedError(
|
||||
"A column object is required to do this")
|
||||
"A column object must be present in table to alter it")
|
||||
|
||||
column = delta.result_column
|
||||
# Oracle cannot drop a default once created, but it can set it
|
||||
@ -74,9 +80,10 @@ class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
|
||||
if dropdefault_hack:
|
||||
column.server_default = None
|
||||
|
||||
self.start_alter_table(self.preparer.format_table(table))
|
||||
self.append("MODIFY ")
|
||||
self.start_alter_table(table)
|
||||
self.append("MODIFY (")
|
||||
self.append(colspec)
|
||||
self.append(")")
|
||||
|
||||
|
||||
class OracleConstraintCommon(object):
|
||||
|
@ -35,7 +35,9 @@ class ControlledSchema(object):
|
||||
if not hasattr(self, 'table') or self.table is None:
|
||||
try:
|
||||
self.table = Table(tname, self.meta, autoload=True)
|
||||
except sa_exceptions.NoSuchTableError:
|
||||
except (sa_exceptions.NoSuchTableError,
|
||||
AssertionError):
|
||||
# assertionerror is raised if no table is found in oracle db
|
||||
raise exceptions.DatabaseNotControlledError(tname)
|
||||
|
||||
# TODO?: verify that the table is correct (# cols, etc.)
|
||||
|
@ -9,9 +9,6 @@ from migrate.changeset.schema import _ColumnDelta
|
||||
from test import fixture
|
||||
|
||||
|
||||
# TODO: test quoting
|
||||
# TODO: test all other constraints on create column, test defaults
|
||||
|
||||
class TestAddDropColumn(fixture.DB):
|
||||
"""Test add/drop column through all possible interfaces
|
||||
also test for constraints"""
|
||||
@ -265,7 +262,7 @@ class TestAddDropColumn(fixture.DB):
|
||||
col = Column('data', String(244), server_default='foobar')
|
||||
col.create(self.table)
|
||||
|
||||
self.table.insert().execute()
|
||||
self.table.insert(values={'id': 10}).execute()
|
||||
row = self.table.select(autocommit=True).execute().fetchone()
|
||||
self.assertEqual(u'foobar', row['data'])
|
||||
|
||||
@ -273,6 +270,8 @@ class TestAddDropColumn(fixture.DB):
|
||||
|
||||
# TODO: test sequence
|
||||
# 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
|
||||
|
||||
|
||||
class TestRename(fixture.DB):
|
||||
@ -415,8 +414,6 @@ class TestColumnChange(fixture.DB):
|
||||
self.refresh_table(self.table.name)
|
||||
self.assert_('data' not in self.table.c.keys())
|
||||
self.assert_('atad' in self.table.c.keys())
|
||||
#self.assertRaises(AttributeError,getattr,self.table.c,'data')
|
||||
self.table.c.atad # Should not raise exception
|
||||
self.assertEquals(num_rows(self.table.c.atad, content), 1)
|
||||
|
||||
# ...as a method, given a new name
|
||||
@ -482,13 +479,12 @@ class TestColumnChange(fixture.DB):
|
||||
self.refresh_table(self.table.name)
|
||||
self.assert_(isinstance(self.table.c.id.type, String))
|
||||
|
||||
@fixture.usedb(not_supported='mysql')
|
||||
@fixture.usedb()
|
||||
def test_default(self):
|
||||
"""Can change a column's server_default value (DefaultClauses only)
|
||||
Only DefaultClauses are changed here: others are managed by the
|
||||
application / by SA
|
||||
"""
|
||||
#self.engine.echo=True
|
||||
self.assertEquals(self.table.c.data.server_default.arg, 'tluafed')
|
||||
|
||||
# Just the new default
|
||||
|
@ -31,7 +31,7 @@ class CommonTestConstraint(fixture.DB):
|
||||
self.meta = MetaData(self.engine)
|
||||
self.tablename = 'mytable'
|
||||
self.table = Table(self.tablename, self.meta,
|
||||
Column('id', Integer, unique=True),
|
||||
Column('id', Integer),
|
||||
Column('fkey', Integer),
|
||||
mysql_engine='InnoDB')
|
||||
if self.engine.has_table(self.table.name):
|
||||
@ -81,7 +81,6 @@ class TestConstraint(CommonTestConstraint):
|
||||
fk = ForeignKeyConstraint([self.table.c.fkey],
|
||||
[self.table.c.id],
|
||||
name="fk_id_fkey",
|
||||
onupdate="CASCADE",
|
||||
ondelete="CASCADE")
|
||||
self.assert_(self.table.c.fkey.foreign_keys._list is not [])
|
||||
self.assertEquals(list(fk.columns), [self.table.c.fkey])
|
||||
@ -96,7 +95,6 @@ class TestConstraint(CommonTestConstraint):
|
||||
|
||||
# test for ondelete/onupdate
|
||||
fkey = self.table.c.fkey.foreign_keys._list[0]
|
||||
self.assertEquals(fkey.onupdate, "CASCADE")
|
||||
self.assertEquals(fkey.ondelete, "CASCADE")
|
||||
# TODO: test on real db if it was set
|
||||
|
||||
@ -110,7 +108,7 @@ class TestConstraint(CommonTestConstraint):
|
||||
@fixture.usedb()
|
||||
def test_define_pk(self):
|
||||
"""PK constraints can be defined, created, and dropped"""
|
||||
self._define_pk(self.table.c.id)
|
||||
self._define_pk(self.table.c.fkey)
|
||||
|
||||
@fixture.usedb()
|
||||
def test_define_pk_multi(self):
|
||||
@ -121,7 +119,7 @@ class TestConstraint(CommonTestConstraint):
|
||||
@fixture.usedb()
|
||||
def test_drop_cascade(self):
|
||||
"""Drop constraint cascaded"""
|
||||
pk = PrimaryKeyConstraint('id', table=self.table, name="id_pkey")
|
||||
pk = PrimaryKeyConstraint('fkey', table=self.table, name="id_pkey")
|
||||
pk.create()
|
||||
self.refresh_table()
|
||||
|
||||
@ -202,6 +200,9 @@ class TestAutoname(CommonTestConstraint):
|
||||
@fixture.usedb(not_supported=['oracle', 'sqlite'])
|
||||
def test_autoname_fk(self):
|
||||
"""ForeignKeyConstraints can guess their name if None is given"""
|
||||
cons = PrimaryKeyConstraint(self.table.c.id)
|
||||
cons.create()
|
||||
|
||||
cons = ForeignKeyConstraint([self.table.c.fkey], [self.table.c.id])
|
||||
cons.create()
|
||||
self.refresh_table()
|
||||
|
@ -85,9 +85,10 @@ class TestSchemaDiff(fixture.DB):
|
||||
)
|
||||
''')
|
||||
|
||||
# Add data, later we'll make sure it's still present.
|
||||
result = self.engine.execute(self.table.insert(), id=1, name=u'mydata')
|
||||
dataId = result.last_inserted_ids()[0]
|
||||
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')
|
||||
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)
|
||||
@ -102,22 +103,23 @@ class TestSchemaDiff(fixture.DB):
|
||||
self._applyLatestModel()
|
||||
assertDiff(False, [], [], [])
|
||||
|
||||
# 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')
|
||||
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)
|
||||
dataId2 = result.last_inserted_ids()[0]
|
||||
# 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)
|
||||
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',UnicodeText(),nullable=True),
|
||||
Column('data2',String(255),nullable=True),
|
||||
)
|
||||
assertDiff(True, [], [], [self.table_name]) # TODO test type diff
|
||||
|
||||
@ -125,23 +127,24 @@ class TestSchemaDiff(fixture.DB):
|
||||
self._applyLatestModel()
|
||||
assertDiff(False, [], [], [])
|
||||
|
||||
# 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 == '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)
|
||||
|
||||
# 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',UnicodeText(),nullable=False),
|
||||
Column('data2',String(255),nullable=False),
|
||||
)
|
||||
assertDiff(True, [], [], [self.table_name]) # TODO test nullable diff
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user