.. note:: The table copy operation will currently not copy
CHECK constraints, and may not copy UNIQUE constraints that are
- unnamed, as is possible on SQLite.
+ unnamed, as is possible on SQLite. See the section
+ :ref:`sqlite_batch_constraints` for workarounds.
:param table_name: name of table
:param schema: optional schema name.
:param recreate: under what circumstances the table should be
recreated. At its default of ``"auto"``, the SQLite dialect will
- recreate the table if any operations other than ``add_column()`` are
+ recreate the table if any operations other than ``add_column()``,
+ ``create_index()``, or ``drop_index()`` are
present. Other options include ``"always"`` and ``"never"``.
:param copy_from: optional :class:`~sqlalchemy.schema.Table` object
that will act as the structure of the table being copied. If omitted,
table reflection is used to retrieve the structure of the table.
+ .. versionadded:: 0.7.6 Fully implemented the
+ :paramref:`~.Operations.batch_alter_table.copy_from`
+ parameter.
+
.. seealso::
+ :ref:`batch_offline_mode`
+
:paramref:`~.Operations.batch_alter_table.reflect_args`
:paramref:`~.Operations.batch_alter_table.reflect_kwargs`
from contextlib import contextmanager
import re
+import io
+
from alembic.testing import exclusions
from alembic.testing import TestBase, eq_, config
from alembic.testing.fixtures import op_fixture
from alembic.batch import ApplyBatchImpl
from alembic.migration import MigrationContext
+
from sqlalchemy import inspect
from sqlalchemy import Integer, Table, Column, String, MetaData, ForeignKey, \
UniqueConstraint, ForeignKeyConstraint, Index, Boolean, CheckConstraint, \
)
+class CopyFromTest(TestBase):
+ __requires__ = ('sqlalchemy_08', )
+
+ def _fixture(self):
+ self.metadata = MetaData()
+ self.table = Table(
+ 'foo', self.metadata,
+ Column('id', Integer, primary_key=True),
+ Column('data', String(50)),
+ Column('x', Integer),
+ )
+
+ context = op_fixture(dialect="sqlite", as_sql=True)
+ self.op = Operations(context)
+ return context
+
+ def test_change_type(self):
+ context = self._fixture()
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table) as batch_op:
+ batch_op.alter_column('data', type_=Integer)
+
+ context.assert_(
+ 'CREATE TABLE _alembic_batch_temp (id INTEGER NOT NULL, '
+ 'data INTEGER, x INTEGER, PRIMARY KEY (id))',
+ 'INSERT INTO _alembic_batch_temp (id, data, x) SELECT foo.id, '
+ 'CAST(foo.data AS INTEGER) AS anon_1, foo.x FROM foo',
+ 'DROP TABLE foo',
+ 'ALTER TABLE _alembic_batch_temp RENAME TO foo'
+ )
+
+ def test_create_drop_index_w_always(self):
+ context = self._fixture()
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table, recreate='always') as batch_op:
+ batch_op.create_index(
+ batch_op.f('ix_data'), ['data'], unique=True)
+
+ context.assert_(
+ 'CREATE TABLE _alembic_batch_temp (id INTEGER NOT NULL, '
+ 'data VARCHAR(50), '
+ 'x INTEGER, PRIMARY KEY (id))',
+ 'CREATE UNIQUE INDEX ix_data ON _alembic_batch_temp (data)',
+ 'INSERT INTO _alembic_batch_temp (id, data, x) '
+ 'SELECT foo.id, foo.data, foo.x FROM foo',
+ 'DROP TABLE foo',
+ 'ALTER TABLE _alembic_batch_temp RENAME TO foo'
+ )
+
+ context.clear_assertions()
+
+ Index('ix_data', self.table.c.data, unique=True)
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table, recreate='always') as batch_op:
+ batch_op.drop_index('ix_data')
+
+ context.assert_(
+ 'CREATE TABLE _alembic_batch_temp (id INTEGER NOT NULL, '
+ 'data VARCHAR(50), x INTEGER, PRIMARY KEY (id))',
+ 'INSERT INTO _alembic_batch_temp (id, data, x) '
+ 'SELECT foo.id, foo.data, foo.x FROM foo',
+ 'DROP TABLE foo',
+ 'ALTER TABLE _alembic_batch_temp RENAME TO foo'
+ )
+
+ def test_create_drop_index_wo_always(self):
+ context = self._fixture()
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table) as batch_op:
+ batch_op.create_index(
+ batch_op.f('ix_data'), ['data'], unique=True)
+
+ context.assert_(
+ 'CREATE UNIQUE INDEX ix_data ON foo (data)'
+ )
+
+ context.clear_assertions()
+
+ Index('ix_data', self.table.c.data, unique=True)
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table) as batch_op:
+ batch_op.drop_index('ix_data')
+
+ context.assert_(
+ 'DROP INDEX ix_data'
+ )
+
+ def test_create_drop_index_w_other_ops(self):
+ context = self._fixture()
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table) as batch_op:
+ batch_op.alter_column('data', type_=Integer)
+ batch_op.create_index(
+ batch_op.f('ix_data'), ['data'], unique=True)
+
+ context.assert_(
+ 'CREATE TABLE _alembic_batch_temp (id INTEGER NOT NULL, '
+ 'data INTEGER, x INTEGER, PRIMARY KEY (id))',
+ 'CREATE UNIQUE INDEX ix_data ON _alembic_batch_temp (data)',
+ 'INSERT INTO _alembic_batch_temp (id, data, x) SELECT foo.id, '
+ 'CAST(foo.data AS INTEGER) AS anon_1, foo.x FROM foo',
+ 'DROP TABLE foo',
+ 'ALTER TABLE _alembic_batch_temp RENAME TO foo'
+ )
+
+ context.clear_assertions()
+
+ Index('ix_data', self.table.c.data, unique=True)
+ with self.op.batch_alter_table(
+ "foo", copy_from=self.table) as batch_op:
+ batch_op.drop_index('ix_data')
+ batch_op.alter_column('data', type_=String)
+
+ context.assert_(
+ 'CREATE TABLE _alembic_batch_temp (id INTEGER NOT NULL, '
+ 'data VARCHAR, x INTEGER, PRIMARY KEY (id))',
+ 'INSERT INTO _alembic_batch_temp (id, data, x) SELECT foo.id, '
+ 'CAST(foo.data AS VARCHAR) AS anon_1, foo.x FROM foo',
+ 'DROP TABLE foo',
+ 'ALTER TABLE _alembic_batch_temp RENAME TO foo'
+ )
+
+
class BatchRoundTripTest(TestBase):
__requires__ = ('sqlalchemy_08', )
__only_on__ = "sqlite"