From: Mike Bayer Date: Wed, 12 Mar 2014 21:34:01 +0000 (-0400) Subject: Extensive changes have been made to more fully support SQLAlchemy's new X-Git-Tag: rel_0_6_4~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a6b02d1019dda9d824e0f9a0248865cf03c560a8;p=thirdparty%2Fsqlalchemy%2Falembic.git Extensive changes have been made to more fully support SQLAlchemy's new naming conventions feature. Note that while SQLAlchemy has added this feature as of 0.9.2, some additional fixes in 0.9.4 are needed to resolve some of the issues: 1. The :class:`.Operations` object now takes into account the naming conventions that are present on the :class:`.MetaData` object that's associated using :paramref:`~.EnvironmentContext.configure.target_metadata`. When :class:`.Operations` renders a constraint directive like ``ADD CONSTRAINT``, it now will make use of this naming convention when it produces its own temporary :class:`.MetaData` object. 2. Note however that the autogenerate feature in most cases generates constraints like foreign keys and unique constraints with the final names intact; the only exception are the constraints implicit with a schema-type like Boolean or Enum. In most of these cases, the naming convention feature will not take effect for these constraints and will instead use the given name as is, with one exception.... 3. Naming conventions which use the ``"%(constraint_name)s"`` token, that is, produce a new name that uses the original name as a component, will still be pulled into the naming convention converter and be converted. The problem arises when autogenerate renders a constraint with it's already-generated name present in the migration file's source code, the name will be doubled up at render time due to the combination of #1 and #2. So to work around this, autogenerate now renders these already-tokenized names using the new :meth:`.Operations.f` component. This component is only generated if **SQLAlchemy 0.9.4** or greater is in use. Therefore it is highly recommended that an upgrade to Alembic 0.6.4 be accompanied by an upgrade of SQLAlchemy 0.9.4, if the new naming conventions feature is used. fixes #183 --- diff --git a/alembic/autogenerate/render.py b/alembic/autogenerate/render.py index 4c7b4f5e..be3bb45c 100644 --- a/alembic/autogenerate/render.py +++ b/alembic/autogenerate/render.py @@ -6,6 +6,25 @@ from ..compat import string_types log = logging.getLogger(__name__) +try: + from sqlalchemy.sql.naming import conv + def _render_gen_name(autogen_context, name): + if isinstance(name, conv): + return _f_name(_alembic_autogenerate_prefix(autogen_context), name) + else: + return name +except ImportError: + def _render_gen_name(autogen_context, name): + return name + +class _f_name(object): + def __init__(self, prefix, name): + self.prefix = prefix + self.name = name + + def __repr__(self): + return "%sf(%r)" % (self.prefix, self.name) + def _render_potential_expr(value, autogen_context): if isinstance(value, sql.ClauseElement): if compat.sqla_08: @@ -66,7 +85,7 @@ def _add_index(index, autogen_context): text = "%(prefix)screate_index('%(name)s', '%(table)s', %(columns)s, "\ "unique=%(unique)r%(schema)s%(kwargs)s)" % { 'prefix': _alembic_autogenerate_prefix(autogen_context), - 'name': index.name, + 'name': _render_gen_name(autogen_context, index.name), 'table': index.table.name, 'columns': _get_index_column_names(index), 'unique': index.unique or False, @@ -86,7 +105,7 @@ def _drop_index(index, autogen_context): text = "%(prefix)sdrop_index('%(name)s', "\ "table_name='%(table_name)s'%(schema)s)" % { 'prefix': _alembic_autogenerate_prefix(autogen_context), - 'name': index.name, + 'name': _render_gen_name(autogen_context, index.name), 'table_name': index.table.name, 'schema': ((", schema='%s'" % index.table.schema) if index.table.schema else '') @@ -118,10 +137,11 @@ def _uq_constraint(constraint, autogen_context, alter): if alter and constraint.table.schema: opts.append(("schema", str(constraint.table.schema))) if not alter and constraint.name: - opts.append(("name", constraint.name)) + opts.append(("name", _render_gen_name(autogen_context, constraint.name))) if alter: - args = [repr(constraint.name), repr(constraint.table.name)] + args = [repr(_render_gen_name(autogen_context, constraint.name)), + repr(constraint.table.name)] args.append(repr([col.name for col in constraint.columns])) args.extend(["%s=%r" % (k, v) for k, v in opts]) return "%(prefix)screate_unique_constraint(%(args)s)" % { @@ -166,7 +186,7 @@ def _drop_constraint(constraint, autogen_context): """ text = "%(prefix)sdrop_constraint(%(name)r, '%(table_name)s'%(schema)s)" % { 'prefix': _alembic_autogenerate_prefix(autogen_context), - 'name': constraint.name, + 'name': _render_gen_name(autogen_context, constraint.name), 'table_name': constraint.table.name, 'schema': (", schema='%s'" % constraint.table.schema) if constraint.table.schema else '', @@ -343,7 +363,7 @@ def _render_primary_key(constraint, autogen_context): opts = [] if constraint.name: - opts.append(("name", repr(constraint.name))) + opts.append(("name", repr(_render_gen_name(autogen_context, constraint.name)))) return "%(prefix)sPrimaryKeyConstraint(%(args)s)" % { "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), "args": ", ".join( @@ -377,7 +397,7 @@ def _render_foreign_key(constraint, autogen_context): opts = [] if constraint.name: - opts.append(("name", repr(constraint.name))) + opts.append(("name", repr(_render_gen_name(autogen_context, constraint.name)))) if constraint.onupdate: opts.append(("onupdate", repr(constraint.onupdate))) if constraint.ondelete: @@ -417,7 +437,7 @@ def _render_check_constraint(constraint, autogen_context): return None opts = [] if constraint.name: - opts.append(("name", repr(constraint.name))) + opts.append(("name", repr(_render_gen_name(autogen_context, constraint.name)))) return "%(prefix)sCheckConstraint(%(sqltext)r%(opts)s)" % { "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), "opts": ", " + (", ".join("%s=%s" % (k, v) diff --git a/alembic/operations.py b/alembic/operations.py index 21038f83..828b4200 100644 --- a/alembic/operations.py +++ b/alembic/operations.py @@ -9,6 +9,11 @@ from .ddl import impl __all__ = ('Operations',) +try: + from sqlalchemy.sql.naming import conv +except: + conv = None + class Operations(object): """Define high level migration operations. @@ -55,7 +60,7 @@ class Operations(object): def _primary_key_constraint(self, name, table_name, cols, schema=None): - m = sa_schema.MetaData() + m = self._metadata() columns = [sa_schema.Column(n, NULLTYPE) for n in cols] t1 = sa_schema.Table(table_name, m, *columns, @@ -69,7 +74,7 @@ class Operations(object): onupdate=None, ondelete=None, deferrable=None, source_schema=None, referent_schema=None): - m = sa_schema.MetaData() + m = self._metadata() if source == referent: t1_cols = local_cols + remote_cols else: @@ -97,7 +102,7 @@ class Operations(object): return f def _unique_constraint(self, name, source, local_cols, schema=None, **kw): - t = sa_schema.Table(source, sa_schema.MetaData(), + t = sa_schema.Table(source, self._metadata(), *[sa_schema.Column(n, NULLTYPE) for n in local_cols], schema=schema) kw['name'] = name @@ -108,14 +113,22 @@ class Operations(object): return uq def _check_constraint(self, name, source, condition, schema=None, **kw): - t = sa_schema.Table(source, sa_schema.MetaData(), + t = sa_schema.Table(source, self._metadata(), sa_schema.Column('x', Integer), schema=schema) ck = sa_schema.CheckConstraint(condition, name=name, **kw) t.append_constraint(ck) return ck + def _metadata(self): + kw = {} + if 'target_metadata' in self.migration_context.opts: + mt = self.migration_context.opts['target_metadata'] + if hasattr(mt, 'naming_convention'): + kw['naming_convention'] = mt.naming_convention + return sa_schema.MetaData(**kw) + def _table(self, name, *columns, **kw): - m = sa_schema.MetaData() + m = self._metadata() t = sa_schema.Table(name, m, *columns, **kw) for f in t.foreign_keys: self._ensure_table_for_fk(m, f) @@ -125,7 +138,7 @@ class Operations(object): return sa_schema.Column(name, type_, **kw) def _index(self, name, tablename, columns, schema=None, **kw): - t = sa_schema.Table(tablename or 'no_table', sa_schema.MetaData(), + t = sa_schema.Table(tablename or 'no_table', self._metadata(), *[sa_schema.Column(n, NULLTYPE) for n in columns], schema=schema ) @@ -309,6 +322,52 @@ class Operations(object): if _count_constraint(constraint): self.impl.add_constraint(constraint) + def f(self, name): + """Indicate a string name that has already had a naming convention + applied to it. + + This feature combines with the SQLAlchemy ``naming_convention`` feature + to disambiguate constraint names that have already had naming + conventions applied to them, versus those that have not. This is + necessary in the case that the ``"%(constraint_name)s"`` token + is used within a naming convention, so that it can be identified + that this particular name should remain fixed. + + If the :meth:`.Operations.f` is used on a constraint, the naming + convention will not take effect:: + + op.add_column('t', 'x', Boolean(name=op.f('ck_bool_t_x'))) + + Above, the CHECK constraint generated will have the name ``ck_bool_t_x`` + regardless of whether or not a naming convention is in use. + + Alternatively, if a naming convention is in use, and 'f' is not used, + names will be converted along conventions. If the ``target_metadata`` + contains the naming convention + ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the + output of the following: + + op.add_column('t', 'x', Boolean(name='x')) + + will be:: + + CONSTRAINT ck_bool_t_x CHECK (x in (1, 0))) + + The function is rendered in the output of autogenerate when + a particular constraint name is already converted, for SQLAlchemy + version **0.9.4 and greater only**. Even though ``naming_convention`` + was introduced in 0.9.2, the string disambiguation service is new + as of 0.9.4. + + .. versionadded:: 0.6.4 + + """ + if conv: + return conv(name) + else: + raise NotImplementedError( + "op.f() feature requires SQLAlchemy 0.9.4 or greater.") + def add_column(self, table_name, column, schema=None): """Issue an "add column" instruction using the current migration context. diff --git a/alembic/util.py b/alembic/util.py index 9f035dec..63e92690 100644 --- a/alembic/util.py +++ b/alembic/util.py @@ -25,6 +25,7 @@ sqla_07 = _vers > (0, 7, 2) sqla_08 = _vers >= (0, 8, 0, 'b2') sqla_09 = _vers >= (0, 9, 0) sqla_092 = _vers >= (0, 9, 2) +sqla_094 = _vers >= (0, 9, 4) if not sqla_07: raise CommandError( "SQLAlchemy 0.7.3 or greater is required. ") diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index f32bb9b3..b090a1f6 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -5,6 +5,48 @@ Changelog .. changelog:: :version: 0.6.4 + .. change:: + :tags: bug + :tickets: 183 + + Extensive changes have been made to more fully support SQLAlchemy's new + naming conventions feature. Note that while SQLAlchemy has added this + feature as of 0.9.2, some additional fixes in 0.9.4 are needed to + resolve some of the issues: + + 1. The :class:`.Operations` object now takes into account the naming + conventions that are present on the :class:`.MetaData` object that's + associated using :paramref:`~.EnvironmentContext.configure.target_metadata`. + When :class:`.Operations` renders a constraint directive like + ``ADD CONSTRAINT``, it now will make use of this naming convention + when it produces its own temporary :class:`.MetaData` object. + + 2. Note however that the autogenerate feature in most cases generates + constraints like foreign keys and unique constraints with the + final names intact; the only exception are the constraints implicit + with a schema-type like Boolean or Enum. In most of these cases, + the naming convention feature will not take effect for these constraints + and will instead use the given name as is, with one exception.... + + 3. Naming conventions which use the ``"%(constraint_name)s"`` token, that + is, produce a new name that uses the original name as a component, + will still be pulled into the naming convention converter and be + converted. The problem arises when autogenerate renders a constraint + with it's already-generated name present in the migration file's source + code, the name will be doubled up at render time due to the combination + of #1 and #2. So to work around this, autogenerate now renders these + already-tokenized names using the new :meth:`.Operations.f` component. + This component is only generated if **SQLAlchemy 0.9.4** or greater + is in use. + + Therefore it is highly recommended that an upgrade to Alembic 0.6.4 + be accompanied by an upgrade of SQLAlchemy 0.9.4, if the new naming + conventions feature is used. + + .. seealso:: + + :ref:`autogen_naming_conventions` + .. change:: :tags: bug :tickets: 160 diff --git a/docs/build/tutorial.rst b/docs/build/tutorial.rst index b0e69e87..1819f2f9 100644 --- a/docs/build/tutorial.rst +++ b/docs/build/tutorial.rst @@ -1009,6 +1009,72 @@ If we define our models using a :class:`~sqlalchemy.schema.MetaData` as above, t naming convention dictionary will be used to provide names for all constraints and indexes. +.. _autogen_naming_conventions: + +Integration of Naming Conventions into Operations, Autogenerate +--------------------------------------------------------------- + +As of Alembic 0.6.4, the naming convention feature is integrated into the +:class:`.Operations` object, so that the convention takes effect for any +constraint that is otherwise unnamed. The naming convention is passed to +:class:`.Operations` using the :paramref:`.MigrationsContext.configure.target_metadata` +parameter in ``env.py``, which is normally configured when autogenerate is +used:: + + # in your application's model: + + meta = MetaData(naming_convention={ + "ix": 'ix_%(column_0_label)s', + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" + }) + + # .. in your Alembic env.py: + + # add your model's MetaData object here + # for 'autogenerate' support + from myapp import mymodel + target_metadata = mymodel.Base.metadata + + # ... + + def run_migrations_online(): + + # ... + + context.configure( + connection=connection, + target_metadata=target_metadata + ) + +Above, when we render a directive like the following:: + + op.add_column('sometable', Column('q', Boolean(name='q_bool'))) + +The Boolean type will render a CHECK constraint with the name +``"ck_sometable_q_bool"``, assuming the backend in use does not support +native boolean types. + +We can also use op directives with constraints and not give them a name +at all, if the naming convention doesn't require one. The value of +``None`` will be converted into a name that follows the appopriate naming +conventions:: + + def upgrade(): + op.create_unique_constraint(None, 'some_table', 'x') + +When autogenerate renders constraints in a migration script, it renders them +typically with their completed name. If using at least Alembic 0.6.4 as well +as SQLAlchemy 0.9.4, these will be rendered with a special directive +:meth:`.Operations.f` which denotes that the string has already been +tokenized:: + + def upgrade(): + op.create_unique_constraint(op.f('uq_const_x'), 'some_table', 'x') + + For more detail on the naming convention feature, see :ref:`sqla:constraint_naming_conventions`. diff --git a/tests/__init__.py b/tests/__init__.py index 585e79bf..3c4633e8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,7 +7,7 @@ import textwrap from nose import SkipTest from sqlalchemy.engine import default -from sqlalchemy import create_engine, text +from sqlalchemy import create_engine, text, MetaData from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.util import decorator @@ -91,6 +91,12 @@ def requires_092(fn, *arg, **kw): raise SkipTest("SQLAlchemy 0.9.2 or greater required") return fn(*arg, **kw) +@decorator +def requires_094(fn, *arg, **kw): + if not util.sqla_094: + raise SkipTest("SQLAlchemy 0.9.4 or greater required") + return fn(*arg, **kw) + _dialects = {} def _get_dialect(name): if name is None or name == 'default': @@ -160,7 +166,7 @@ def assert_raises_message(except_cls, msg, callable_, *args, **kwargs): assert re.search(msg, str(e)), "%r !~ %s" % (msg, e) print(text_type(e)) -def op_fixture(dialect='default', as_sql=False): +def op_fixture(dialect='default', as_sql=False, naming_convention=None): impl = _impls[dialect] class Impl(impl): def __init__(self, dialect, as_sql): @@ -180,12 +186,19 @@ def op_fixture(dialect='default', as_sql=False): sql ) + opts = {} + if naming_convention: + if not util.sqla_092: + raise SkipTest( + "naming_convention feature requires " + "sqla 0.9.2 or greater") + opts['target_metadata'] = MetaData(naming_convention=naming_convention) class ctx(MigrationContext): def __init__(self, dialect='default', as_sql=False): self.dialect = _get_dialect(dialect) self.impl = Impl(self.dialect, as_sql) - + self.opts = opts self.as_sql = as_sql def assert_(self, *sql): diff --git a/tests/test_autogen_render.py b/tests/test_autogen_render.py index c36edc10..af12f9e8 100644 --- a/tests/test_autogen_render.py +++ b/tests/test_autogen_render.py @@ -12,7 +12,7 @@ from sqlalchemy.dialects import mysql, postgresql from sqlalchemy.sql import and_, column, literal_column from alembic import autogenerate, util, compat -from . import eq_, eq_ignore_whitespace, requires_092, requires_09 +from . import eq_, eq_ignore_whitespace, requires_092, requires_09, requires_094 py3k = sys.version_info >= (3, ) @@ -576,6 +576,7 @@ render:primary_key\n)""" "sa.CheckConstraint('im a constraint', name='cc1')" ) + def test_render_check_constraint_sqlexpr(self): c = column('c') five = literal_column('5') @@ -710,7 +711,7 @@ render:primary_key\n)""" class RenderNamingConventionTest(TestCase): @classmethod - @requires_092 + @requires_094 def setup_class(cls): cls.autogen_context = { 'opts': { @@ -736,6 +737,16 @@ class RenderNamingConventionTest(TestCase): naming_convention=convention ) + def test_schema_type_boolean(self): + t = Table('t', self.metadata, Column('c', Boolean(name='xyz'))) + eq_ignore_whitespace( + autogenerate.render._add_column( + None, "t", t.c.c, + self.autogen_context), + "op.add_column('t', " + "sa.Column('c', sa.Boolean(name='xyz'), nullable=True))" + ) + def test_explicit_unique_constraint(self): t = Table('t', self.metadata, Column('c', Integer)) eq_ignore_whitespace( @@ -743,7 +754,7 @@ class RenderNamingConventionTest(TestCase): UniqueConstraint(t.c.c, deferrable='XYZ'), self.autogen_context ), - "sa.UniqueConstraint('c', deferrable='XYZ', name='uq_ct_t_c')" + "sa.UniqueConstraint('c', deferrable='XYZ', name=op.f('uq_ct_t_c'))" ) def test_explicit_named_unique_constraint(self): @@ -763,7 +774,7 @@ class RenderNamingConventionTest(TestCase): autogenerate.render._render_unique_constraint(uq, self.autogen_context ), - "sa.UniqueConstraint('c', name='uq_ct_t_c')" + "sa.UniqueConstraint('c', name=op.f('uq_ct_t_c'))" ) def test_inline_pk_constraint(self): @@ -771,7 +782,7 @@ class RenderNamingConventionTest(TestCase): eq_ignore_whitespace( autogenerate.render._add_table(t, self.autogen_context), "op.create_table('t',sa.Column('c', sa.Integer(), nullable=False)," - "sa.PrimaryKeyConstraint('c', name='pk_ct_t'))" + "sa.PrimaryKeyConstraint('c', name=op.f('pk_ct_t')))" ) def test_inline_ck_constraint(self): @@ -779,7 +790,7 @@ class RenderNamingConventionTest(TestCase): eq_ignore_whitespace( autogenerate.render._add_table(t, self.autogen_context), "op.create_table('t',sa.Column('c', sa.Integer(), nullable=True)," - "sa.CheckConstraint('c > 5', name='ck_ct_t'))" + "sa.CheckConstraint('c > 5', name=op.f('ck_ct_t')))" ) def test_inline_fk(self): @@ -787,5 +798,28 @@ class RenderNamingConventionTest(TestCase): eq_ignore_whitespace( autogenerate.render._add_table(t, self.autogen_context), "op.create_table('t',sa.Column('c', sa.Integer(), nullable=True)," - "sa.ForeignKeyConstraint(['c'], ['q.id'], name='fk_ct_t_c_q'))" + "sa.ForeignKeyConstraint(['c'], ['q.id'], name=op.f('fk_ct_t_c_q')))" + ) + + def test_render_check_constraint_renamed(self): + """test that constraints from autogenerate render with + the naming convention name explicitly. These names should + be frozen into the migration scripts so that they remain + the same if the application's naming convention changes. + + However, op.create_table() and others need to be careful that + these don't double up when the "%(constraint_name)s" token is + used. + + """ + m1 = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + ck = CheckConstraint("im a constraint", name="cc1") + Table('t', m1, Column('x'), ck) + + eq_ignore_whitespace( + autogenerate.render._render_check_constraint( + ck, + self.autogen_context + ), + "sa.CheckConstraint('im a constraint', name=op.f('ck_t_cc1'))" ) diff --git a/tests/test_op.py b/tests/test_op.py index e5f6be53..a243aeb5 100644 --- a/tests/test_op.py +++ b/tests/test_op.py @@ -1,12 +1,12 @@ """Test against the builders in the op.* module.""" from sqlalchemy import Integer, Column, ForeignKey, \ - Table, String, Boolean + Table, String, Boolean, MetaData, CheckConstraint from sqlalchemy.sql import column, func, text from sqlalchemy import event from alembic import op -from . import op_fixture, assert_raises_message +from . import op_fixture, assert_raises_message, requires_094 @event.listens_for(Table, "after_parent_attach") def _add_cols(table, metadata): @@ -94,6 +94,7 @@ def test_add_column_schema_type(): 'ALTER TABLE t1 ADD CHECK (c1 IN (0, 1))' ) + def test_add_column_schema_schema_type(): """Test that a schema type generates its constraints....""" context = op_fixture() @@ -418,6 +419,7 @@ def test_add_primary_key_constraint_schema(): "ALTER TABLE bar.t1 ADD CONSTRAINT pk_test PRIMARY KEY (foo)" ) + def test_add_check_constraint(): context = op_fixture() op.create_check_constraint( diff --git a/tests/test_op_naming_convention.py b/tests/test_op_naming_convention.py new file mode 100644 index 00000000..b0b5b767 --- /dev/null +++ b/tests/test_op_naming_convention.py @@ -0,0 +1,166 @@ +from sqlalchemy import Integer, Column, ForeignKey, \ + Table, String, Boolean, MetaData, CheckConstraint +from sqlalchemy.sql import column, func, text +from sqlalchemy import event + +from alembic import op +from . import op_fixture, assert_raises_message, requires_094 + +@requires_094 +def test_add_check_constraint(): + context = op_fixture(naming_convention={ + "ck": "ck_%(table_name)s_%(constraint_name)s" + }) + op.create_check_constraint( + "foo", + "user_table", + func.len(column('name')) > 5 + ) + context.assert_( + "ALTER TABLE user_table ADD CONSTRAINT ck_user_table_foo " + "CHECK (len(name) > 5)" + ) + +@requires_094 +def test_add_check_constraint_name_is_none(): + context = op_fixture(naming_convention={ + "ck": "ck_%(table_name)s_foo" + }) + op.create_check_constraint( + None, + "user_table", + func.len(column('name')) > 5 + ) + context.assert_( + "ALTER TABLE user_table ADD CONSTRAINT ck_user_table_foo " + "CHECK (len(name) > 5)" + ) + +@requires_094 +def test_add_unique_constraint_name_is_none(): + context = op_fixture(naming_convention={ + "uq": "uq_%(table_name)s_foo" + }) + op.create_unique_constraint( + None, + "user_table", + 'x' + ) + context.assert_( + "ALTER TABLE user_table ADD CONSTRAINT uq_user_table_foo UNIQUE (x)" + ) + + +@requires_094 +def test_add_index_name_is_none(): + context = op_fixture(naming_convention={ + "ix": "ix_%(table_name)s_foo" + }) + op.create_index( + None, + "user_table", + 'x' + ) + context.assert_( + "CREATE INDEX ix_user_table_foo ON user_table (x)" + ) + + + +@requires_094 +def test_add_check_constraint_already_named_from_schema(): + m1 = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + ck = CheckConstraint("im a constraint", name="cc1") + Table('t', m1, Column('x'), ck) + + context = op_fixture( + naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + + op.create_table( + "some_table", + Column('x', Integer, ck), + ) + context.assert_( + "CREATE TABLE some_table " + "(x INTEGER CONSTRAINT ck_t_cc1 CHECK (im a constraint))" + ) + +@requires_094 +def test_add_check_constraint_inline_on_table(): + context = op_fixture( + naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + op.create_table( + "some_table", + Column('x', Integer), + CheckConstraint("im a constraint", name="cc1") + ) + context.assert_( + "CREATE TABLE some_table " + "(x INTEGER, CONSTRAINT ck_some_table_cc1 CHECK (im a constraint))" + ) + +@requires_094 +def test_add_check_constraint_inline_on_table_w_f(): + context = op_fixture( + naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + op.create_table( + "some_table", + Column('x', Integer), + CheckConstraint("im a constraint", name=op.f("ck_some_table_cc1")) + ) + context.assert_( + "CREATE TABLE some_table " + "(x INTEGER, CONSTRAINT ck_some_table_cc1 CHECK (im a constraint))" + ) + +@requires_094 +def test_add_check_constraint_inline_on_column(): + context = op_fixture( + naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + op.create_table( + "some_table", + Column('x', Integer, CheckConstraint("im a constraint", name="cc1")) + ) + context.assert_( + "CREATE TABLE some_table " + "(x INTEGER CONSTRAINT ck_some_table_cc1 CHECK (im a constraint))" + ) + +@requires_094 +def test_add_check_constraint_inline_on_column_w_f(): + context = op_fixture( + naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}) + op.create_table( + "some_table", + Column('x', Integer, CheckConstraint("im a constraint", name=op.f("ck_q_cc1"))) + ) + context.assert_( + "CREATE TABLE some_table " + "(x INTEGER CONSTRAINT ck_q_cc1 CHECK (im a constraint))" + ) + + +@requires_094 +def test_add_column_schema_type(): + context = op_fixture(naming_convention={ + "ck": "ck_%(table_name)s_%(constraint_name)s" + }) + op.add_column('t1', Column('c1', Boolean(name='foo'), nullable=False)) + context.assert_( + 'ALTER TABLE t1 ADD COLUMN c1 BOOLEAN NOT NULL', + 'ALTER TABLE t1 ADD CONSTRAINT ck_t1_foo CHECK (c1 IN (0, 1))' + ) + + +@requires_094 +def test_add_column_schema_type_w_f(): + context = op_fixture(naming_convention={ + "ck": "ck_%(table_name)s_%(constraint_name)s" + }) + op.add_column('t1', Column('c1', Boolean(name=op.f('foo')), nullable=False)) + context.assert_( + 'ALTER TABLE t1 ADD COLUMN c1 BOOLEAN NOT NULL', + 'ALTER TABLE t1 ADD CONSTRAINT foo CHECK (c1 IN (0, 1))' + ) + +