series as well. For changes that are specific to 1.0 with an emphasis
on compatibility concerns, see :doc:`/changelog/migration_10`.
+ .. change::
+ :tags: bug, schema
+ :tickets: 3299, 3067
+
+ The :class:`.CheckConstraint` construct now supports naming
+ conventions that include the token ``%(column_0_name)s``; the
+ constraint expression is scanned for columns. Additionally,
+ naming conventions for check constraints that don't include the
+ ``%(constraint_name)s`` token will now work for :class:`.SchemaType`-
+ generated constraints, such as those of :class:`.Boolean` and
+ :class:`.Enum`; this stopped working in 0.9.7 due to :ticket:`3067`.
+
+ .. seealso::
+
+ :ref:`naming_check_constraints`
+
+ :ref:`naming_schematypes`
+
+
.. change::
:tags: feature, postgresql, pypy
:tickets: 3052
undergoing maintenance releases as of May, 2014,
and SQLAlchemy version 1.0, as of yet unreleased.
- Document last updated: January 4, 2015
+ Document last updated: January 30, 2015
Introduction
============
:ref:`use_alter` - full description of the new behavior.
-
:ticket:`3282`
+
+CHECK Constraints now support the ``%(column_0_name)s`` token in naming conventions
+-----------------------------------------------------------------------------------
+
+The ``%(column_0_name)s`` will derive from the first column found in the
+expression of a :class:`.CheckConstraint`::
+
+ metadata = MetaData(
+ naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"}
+ )
+
+ foo = Table('foo', metadata,
+ Column('value', Integer),
+ )
+
+ CheckConstraint(foo.c.value > 5)
+
+Will render::
+
+ CREATE TABLE foo (
+ flag BOOL,
+ CONSTRAINT ck_foo_flag CHECK (flag IN (0, 1))
+ )
+
+The combination of naming conventions with the constraint produced by a
+:class:`.SchemaType` such as :class:`.Boolean` or :class:`.Enum` will also
+now make use of all CHECK constraint conventions.
+
+.. seealso::
+
+ :ref:`naming_check_constraints`
+
+ :ref:`naming_schematypes`
+
+:ticket:`3299`
+
+
.. _change_2051:
.. _feature_insert_from_select_defaults:
.. versionadded:: 0.9.2 Added the :paramref:`.MetaData.naming_convention` argument.
+.. _naming_check_constraints:
+
+Naming CHECK Constraints
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`.CheckConstraint` object is configured against an arbitrary
+SQL expression, which can have any number of columns present, and additionally
+is often configured using a raw SQL string. Therefore a common convention
+to use with :class:`.CheckConstraint` is one where we expect the object
+to have a name already, and we then enhance it with other convention elements.
+A typical convention is ``"ck_%(table_name)s_%(constraint_name)s"``::
+
+ metadata = MetaData(
+ naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}
+ )
+
+ Table('foo', metadata,
+ Column('value', Integer),
+ CheckConstraint('value > 5', name='value_gt_5')
+ )
+
+The above table will produce the name ``ck_foo_value_gt_5``::
+
+ CREATE TABLE foo (
+ value INTEGER,
+ CONSTRAINT ck_foo_value_gt_5 CHECK (value > 5)
+ )
+
+:class:`.CheckConstraint` also supports the ``%(columns_0_name)s``
+token; we can make use of this by ensuring we use a :class:`.Column` or
+:func:`.sql.expression.column` element within the constraint's expression,
+either by declaring the constraint separate from the table::
+
+ metadata = MetaData(
+ naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"}
+ )
+
+ foo = Table('foo', metadata,
+ Column('value', Integer)
+ )
+
+ CheckConstraint(foo.c.value > 5)
+
+or by using a :func:`.sql.expression.column` inline::
+
+ from sqlalchemy import column
+
+ metadata = MetaData(
+ naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"}
+ )
+
+ foo = Table('foo', metadata,
+ Column('value', Integer),
+ CheckConstraint(column('value') > 5)
+ )
+
+Both will produce the name ``ck_foo_value``::
+
+ CREATE TABLE foo (
+ value INTEGER,
+ CONSTRAINT ck_foo_value CHECK (value > 5)
+ )
+
+The determination of the name of "column zero" is performed by scanning
+the given expression for column objects. If the expression has more than
+one column present, the scan does use a deterministic search, however the
+structure of the expression will determine which column is noted as
+"column zero".
+
+.. versionadded:: 1.0.0 The :class:`.CheckConstraint` object now supports
+ the ``column_0_name`` naming convention token.
+
+.. _naming_schematypes:
+
+Configuring Naming for Boolean, Enum, and other schema types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`.SchemaType` class refers to type objects such as :class:`.Boolean`
+and :class:`.Enum` which generate a CHECK constraint accompanying the type.
+The name for the constraint here is most directly set up by sending
+the "name" parameter, e.g. :paramref:`.Boolean.name`::
+
+ Table('foo', metadata,
+ Column('flag', Boolean(name='ck_foo_flag'))
+ )
+
+The naming convention feature may be combined with these types as well,
+normally by using a convention which includes ``%(constraint_name)s``
+and then applying a name to the type::
+
+ metadata = MetaData(
+ naming_convention={"ck": "ck_%(table_name)s_%(constraint_name)s"}
+ )
+
+ Table('foo', metadata,
+ Column('flag', Boolean(name='flag_bool'))
+ )
+
+The above table will produce the constraint name ``ck_foo_flag_bool``::
+
+ CREATE TABLE foo (
+ flag BOOL,
+ CONSTRAINT ck_foo_flag_bool CHECK (flag IN (0, 1))
+ )
+
+The :class:`.SchemaType` classes use special internal symbols so that
+the naming convention is only determined at DDL compile time. On Postgresql,
+there's a native BOOLEAN type, so the CHECK constraint of :class:`.Boolean`
+is not needed; we are safe to set up a :class:`.Boolean` type without a
+name, even though a naming convention is in place for check constraints.
+This convention will only be consulted for the CHECK constraint if we
+run against a database without a native BOOLEAN type like SQLite or
+MySQL.
+
+The CHECK constraint may also make use of the ``column_0_name`` token,
+which works nicely with :class:`.SchemaType` since these constraints have
+only one column::
+
+ metadata = MetaData(
+ naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"}
+ )
+
+ Table('foo', metadata,
+ Column('flag', Boolean())
+ )
+
+The above schema will produce::
+
+ CREATE TABLE foo (
+ flag BOOL,
+ CONSTRAINT ck_foo_flag CHECK (flag IN (0, 1))
+ )
+
+.. versionchanged:: 1.0 Constraint naming conventions that don't include
+ ``%(constraint_name)s`` again work with :class:`.SchemaType` constraints.
+
Constraints API
---------------
.. autoclass:: Constraint
if isinstance(const.name, conv):
return const.name
- elif convention is not None and (
- const.name is None or not isinstance(const.name, conv) and
- "constraint_name" in convention
- ):
+ elif convention is not None and \
+ not isinstance(const.name, conv) and \
+ (
+ const.name is None or
+ "constraint_name" in convention or
+ isinstance(const.name, _defer_name)):
return conv(
convention % ConventionDict(const, table,
metadata.naming_convention)
"""
- def __init__(self, *columns):
+ _allow_multiple_tables = False
+
+ def __init__(self, *columns, **kw):
+ _autoattach = kw.pop('_autoattach', True)
self.columns = ColumnCollection()
self._pending_colargs = [_to_schema_column_or_string(c)
for c in columns]
- if self._pending_colargs and \
- isinstance(self._pending_colargs[0], Column) and \
- isinstance(self._pending_colargs[0].table, Table):
- self._set_parent_with_dispatch(self._pending_colargs[0].table)
+ if _autoattach and self._pending_colargs:
+ columns = [
+ c for c in self._pending_colargs
+ if isinstance(c, Column) and
+ isinstance(c.table, Table)
+ ]
+
+ tables = set([c.table for c in columns])
+ if len(tables) == 1:
+ self._set_parent_with_dispatch(tables.pop())
+ elif len(tables) > 1 and not self._allow_multiple_tables:
+ table = columns[0].table
+ others = [c for c in columns[1:] if c.table is not table]
+ if others:
+ raise exc.ArgumentError(
+ "Column(s) %s are not part of table '%s'." %
+ (", ".join("'%s'" % c for c in others),
+ table.description)
+ )
def _set_parent(self, table):
for col in self._pending_colargs:
arguments are propagated to the :class:`.Constraint` superclass.
"""
+ _autoattach = kw.pop('_autoattach', True)
Constraint.__init__(self, **kw)
- ColumnCollectionMixin.__init__(self, *columns)
+ ColumnCollectionMixin.__init__(self, *columns, _autoattach=_autoattach)
def _set_parent(self, table):
Constraint._set_parent(self, table)
return len(self.columns._data)
-class CheckConstraint(Constraint):
+class CheckConstraint(ColumnCollectionConstraint):
"""A table- or column-level CHECK constraint.
Can be included in the definition of a Table or Column.
"""
+ _allow_multiple_tables = True
+
def __init__(self, sqltext, name=None, deferrable=None,
initially=None, table=None, info=None, _create_rule=None,
_autoattach=True, _type_bound=False):
"""
+ self.sqltext = _literal_as_text(sqltext, warn=False)
+
+ columns = []
+ visitors.traverse(self.sqltext, {}, {'column': columns.append})
+
super(CheckConstraint, self).\
__init__(
- name, deferrable, initially, _create_rule, info=info,
- _type_bound=_type_bound)
- self.sqltext = _literal_as_text(sqltext, warn=False)
+ name=name, deferrable=deferrable,
+ initially=initially, _create_rule=_create_rule, info=info,
+ _type_bound=_type_bound, _autoattach=_autoattach,
+ *columns)
if table is not None:
self._set_parent_with_dispatch(table)
- elif _autoattach:
- cols = _find_columns(self.sqltext)
- tables = set([c.table for c in cols
- if isinstance(c.table, Table)])
- if len(tables) == 1:
- self._set_parent_with_dispatch(
- tables.pop())
def __visit_name__(self):
if isinstance(self.parent, Table):
self._validate_dest_table(table)
-
def copy(self, schema=None, target_table=None, **kw):
fkc = ForeignKeyConstraint(
[x.parent.key for x in self.elements],
)
)
self.table = table
- for c in self.columns:
- if c.table != self.table:
- raise exc.ArgumentError(
- "Column '%s' is not part of table '%s'." %
- (c, self.table.description)
- )
table.indexes.add(self)
self.expressions = [
)
assert_raises_message(
exc.ArgumentError,
- "Column 't2.y' is not part of table 't1'.",
+ r"Column\(s\) 't2.y' are not part of table 't1'.",
Index,
"bar", t1.c.x, t2.c.y
)
")"
)
+ def test_schematype_ck_name_boolean_not_on_name(self):
+ m1 = MetaData(naming_convention={
+ "ck": "ck_%(table_name)s_%(column_0_name)s"})
+
+ u1 = Table('user', m1,
+ Column('x', Boolean())
+ )
+ # constraint is not hit
+ eq_(
+ [c for c in u1.constraints
+ if isinstance(c, CheckConstraint)][0].name, "_unnamed_"
+ )
+ # but is hit at compile time
+ self.assert_compile(
+ schema.CreateTable(u1),
+ 'CREATE TABLE "user" ('
+ "x BOOLEAN, "
+ "CONSTRAINT ck_user_x CHECK (x IN (0, 1))"
+ ")"
+ )
+
def test_schematype_ck_name_enum(self):
m1 = MetaData(naming_convention={
"ck": "ck_%(table_name)s_%(constraint_name)s"})