]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- The Index() construct can be created inline with a Table
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 4 Jan 2011 18:48:46 +0000 (13:48 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 4 Jan 2011 18:48:46 +0000 (13:48 -0500)
definition, using strings as column names, as an alternative
to the creation of the index outside of the Table.

CHANGES
doc/build/core/schema.rst
lib/sqlalchemy/ext/declarative.py
lib/sqlalchemy/schema.py
test/sql/test_constraints.py

diff --git a/CHANGES b/CHANGES
index 65b403d4d135ddb1983f1afb7345f49a4c5b8051..617e825d3acb55621a7fc2f85aad4c10d2263276 100644 (file)
--- 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 
index 3caced3ae72a36a52fe3e3476eaf5ee521b91d1f..639525581ce1301b17dd98c2d19a62f6fad95ae4 100644 (file)
@@ -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
index 39aaf54880dcc61856f0bc90b9fd432b207cdaa3..898a9a7280e48dbc62163cd2175e53b1f42bbe69 100755 (executable)
@@ -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
 =================
index eb03fae98f8f27163378dca2c478e995dad9007d..df7dbace02b3627966b9e70c8b110daa075f722c 100644 (file)
@@ -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
index fb07bf437e9949c50c86785391267f11e30c3fb5..4bacbd27110ba8184773e70abbc242ba27f0a2cf 100644 (file)
@@ -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):