From: Mike Bayer Date: Tue, 4 Jan 2011 18:48:46 +0000 (-0500) Subject: - The Index() construct can be created inline with a Table X-Git-Tag: rel_0_7b1~89 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e46301b5154000148155ef00d15b35857ebb31ad;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - The Index() construct can be created inline with a Table definition, using strings as column names, as an alternative to the creation of the index outside of the Table. --- diff --git a/CHANGES b/CHANGES index 65b403d4d1..617e825d3a 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,10 @@ CHANGES as an extension to the asc() and desc() operators, called nullsfirst() and nullslast(). [ticket:723] + - The Index() construct can be created inline with a Table + definition, using strings as column names, as an alternative + to the creation of the index outside of the Table. + - mssql - the String/Unicode types, and their counterparts VARCHAR/ NVARCHAR, emit "max" as the length when no length is diff --git a/doc/build/core/schema.rst b/doc/build/core/schema.rst index 3caced3ae7..639525581c 100644 --- a/doc/build/core/schema.rst +++ b/doc/build/core/schema.rst @@ -1076,10 +1076,6 @@ separate UNIQUE constraint. For indexes with specific names or which encompass more than one column, use the :class:`~sqlalchemy.schema.Index` construct, which requires a name. -Note that the :class:`~sqlalchemy.schema.Index` construct is created -**externally** to the table which it corresponds, using -:class:`~sqlalchemy.schema.Column` objects and not strings. - Below we illustrate a :class:`~sqlalchemy.schema.Table` with several :class:`~sqlalchemy.schema.Index` objects associated. The DDL for "CREATE INDEX" is issued right after the create statements for the table: @@ -1121,6 +1117,28 @@ INDEX" is issued right after the create statements for the table: CREATE UNIQUE INDEX myindex ON mytable (col5, col6) CREATE INDEX idx_col34 ON mytable (col3, col4){stop} +Note in the example above, the :class:`.Index` construct is created +externally to the table which it corresponds, using :class:`.Column` +objects directly. As of SQLAlchemy 0.7, :class:`.Index` also supports +"inline" definition inside the :class:`.Table`, using string names to +identify columns:: + + meta = MetaData() + mytable = Table('mytable', meta, + Column('col1', Integer), + + Column('col2', Integer), + + Column('col3', Integer), + Column('col4', Integer), + + # place an index on col1, col2 + Index('idx_col12', 'col1', 'col2'), + + # place a unique index on col3, col4 + Index('idx_col34', 'col3', 'col4', unique=True) + ) + The :class:`~sqlalchemy.schema.Index` object also supports its own ``create()`` method: .. sourcecode:: python+sql diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 39aaf54880..898a9a7280 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -819,7 +819,7 @@ from multiple collections:: __tablename__='my_model' @declared_attr - def __table_args__(self): + def __table_args__(cls): args = dict() args.update(MySQLSettings.__table_args__) args.update(MyOtherMixin.__table_args__) @@ -827,6 +827,25 @@ from multiple collections:: id = Column(Integer, primary_key=True) +Creating Indexes with Mixins +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To define a named, potentially multicolumn :class:`.Index` that applies to all +tables derived from a mixin, use the "inline" form of :class:`.Index` and establish +it as part of ``__table_args__``:: + + class MyMixin(object): + a = Column(Integer) + b = Column(Integer) + + @declared_attr + def __table_args__(cls): + return (Index('test_idx_%s' % cls.__tablename__, 'a', 'b'),) + + class MyModel(Base,MyMixin): + __tablename__ = 'atable' + c = Column(Integer,primary_key=True) + Class Constructor ================= diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index eb03fae98f..df7dbace02 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -1543,7 +1543,23 @@ class Constraint(SchemaItem): def copy(self, **kw): raise NotImplementedError() -class ColumnCollectionConstraint(Constraint): +class ColumnCollectionMixin(object): + def __init__(self, *columns): + self.columns = expression.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 \ + self._pending_colargs[0].table is not None: + self._set_parent(self._pending_colargs[0].table) + + def _set_parent(self, table): + for col in self._pending_colargs: + if isinstance(col, basestring): + col = table.c[col] + self.columns.add(col) + +class ColumnCollectionConstraint(ColumnCollectionMixin, Constraint): """A constraint that proxies a ColumnCollection.""" def __init__(self, *columns, **kw): @@ -1563,21 +1579,12 @@ class ColumnCollectionConstraint(Constraint): for this constraint. """ - super(ColumnCollectionConstraint, self).__init__(**kw) - self.columns = expression.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 \ - self._pending_colargs[0].table is not None: - self._set_parent(self._pending_colargs[0].table) + ColumnCollectionMixin.__init__(self, *columns) + Constraint.__init__(self, **kw) def _set_parent(self, table): - super(ColumnCollectionConstraint, self)._set_parent(table) - for col in self._pending_colargs: - if isinstance(col, basestring): - col = table.c[col] - self.columns.add(col) + ColumnCollectionMixin._set_parent(self, table) + Constraint._set_parent(self, table) def __contains__(self, x): return x in self.columns @@ -1798,7 +1805,7 @@ class UniqueConstraint(ColumnCollectionConstraint): __visit_name__ = 'unique_constraint' -class Index(SchemaItem): +class Index(ColumnCollectionMixin, SchemaItem): """A table-level INDEX. Defines a composite (one or more column) INDEX. For a no-frills, single @@ -1808,7 +1815,7 @@ class Index(SchemaItem): __visit_name__ = 'index' - def __init__(self, name, *columns, **kwargs): + def __init__(self, name, *columns, **kw): """Construct an index object. :param name: @@ -1825,27 +1832,33 @@ class Index(SchemaItem): Other keyword arguments may be interpreted by specific dialects. """ - - self.name = name - self.columns = expression.ColumnCollection() self.table = None - self.unique = kwargs.pop('unique', False) - self.kwargs = kwargs - - for column in columns: - column = _to_schema_column(column) - if self.table is None: - self._set_parent(column.table) - elif column.table != self.table: - # all columns muse be from same table - raise exc.ArgumentError( - "All index columns must be from same table. " - "%s is from %s not %s" % - (column, column.table, self.table)) - self.columns.add(column) + # will call _set_parent() if table-bound column + # objects are present + ColumnCollectionMixin.__init__(self, *columns) + self.name = name + self.unique = kw.pop('unique', False) + self.kwargs = kw def _set_parent(self, table): + ColumnCollectionMixin._set_parent(self, table) + + if self.table is not None and table is not self.table: + raise exc.ArgumentError( + "Index '%s' is against table '%s', and " + "cannot be associated with table '%s'." % ( + self.name, + self.table.description, + table.description + ) + ) 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) @property diff --git a/test/sql/test_constraints.py b/test/sql/test_constraints.py index fb07bf437e..4bacbd2711 100644 --- a/test/sql/test_constraints.py +++ b/test/sql/test_constraints.py @@ -217,6 +217,43 @@ class ConstraintTest(TestBase, AssertsExecutionResults, AssertsCompiledSQL): dialect=dialect ) + def test_index_declartion_inline(self): + t1 = Table('t1', metadata, + Column('x', Integer), + Column('y', Integer), + Index('foo', 'x', 'y') + ) + self.assert_compile( + schema.CreateIndex(list(t1.indexes)[0]), + "CREATE INDEX foo ON t1 (x, y)" + ) + + def test_index_asserts_cols_standalone(self): + t1 = Table('t1', metadata, + Column('x', Integer) + ) + t2 = Table('t2', metadata, + Column('y', Integer) + ) + assert_raises_message( + exc.ArgumentError, + "Column 't2.y' is not part of table 't1'.", + Index, + "bar", t1.c.x, t2.c.y + ) + + def test_index_asserts_cols_inline(self): + t1 = Table('t1', metadata, + Column('x', Integer) + ) + assert_raises_message( + exc.ArgumentError, + "Index 'bar' is against table 't1', and " + "cannot be associated with table 't2'.", + Table, 't2', metadata, + Column('y', Integer), + Index('bar', t1.c.x) + ) class ConstraintCompilationTest(TestBase, AssertsCompiledSQL):