]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [feature] Added a hook to the system of rendering
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 9 Sep 2012 18:39:25 +0000 (14:39 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 9 Sep 2012 18:39:25 +0000 (14:39 -0400)
CREATE TABLE that provides access to the render for each
Column individually, by constructing a @compiles
function against the new schema.CreateColumn
construct.  [ticket:2463]

CHANGES
doc/build/core/schema.rst
lib/sqlalchemy/ext/compiler.py
lib/sqlalchemy/schema.py
lib/sqlalchemy/sql/compiler.py
test/ext/test_compiler.py
test/sql/test_metadata.py

diff --git a/CHANGES b/CHANGES
index 161dd0eb241742aaadf9c583fc10c964b88ab2d0..631ecc62e2c78b476480020d0a8ed2f18271adbb 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -530,6 +530,12 @@ underneath "0.7.xx".
     can be altered, such as in the case for Firebird
     STARTING WITH [ticket:2470]
 
+  - [feature] Added a hook to the system of rendering
+    CREATE TABLE that provides access to the render for each
+    Column individually, by constructing a @compiles
+    function against the new schema.CreateColumn
+    construct.  [ticket:2463]
+
   - [bug] Fixes to the interpretation of the
     Column "default" parameter as a callable
     to not pass ExecutionContext into a keyword
index 4e812d658711d607641a53fd0427b1b5df115976..4daf0a9626808b88552e1e693a13a222ace042ba 100644 (file)
@@ -325,6 +325,7 @@ Column, Table, MetaData API
 
 .. autoclass:: Column
     :members:
+    :inherited-members:
     :undoc-members:
     :show-inheritance:
 
@@ -339,6 +340,7 @@ Column, Table, MetaData API
 
 .. autoclass:: Table
     :members:
+    :inherited-members:
     :undoc-members:
     :show-inheritance:
 
@@ -1375,6 +1377,11 @@ DDL Expression Constructs API
     :undoc-members:
     :show-inheritance:
 
+.. autoclass:: CreateColumn
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 .. autoclass:: CreateSequence
     :members:
     :undoc-members:
index d5fbec518325ab5a0943e5a30d0210ff5820e23b..e3e66836488bcd3e53226aa3476ce10810fc366f 100644 (file)
@@ -368,8 +368,12 @@ Example usage::
 
 """
 from .. import exc
+from ..sql import visitors
 
 def compiles(class_, *specs):
+    """Register a function as a compiler for a
+    given :class:`.ClauseElement` type."""
+
     def decorate(fn):
         existing = class_.__dict__.get('_compiler_dispatcher', None)
         existing_dispatch = class_.__dict__.get('_compiler_dispatch')
@@ -392,6 +396,17 @@ def compiles(class_, *specs):
         return fn
     return decorate
 
+def deregister(class_):
+    """Remove all custom compilers associated with a given
+    :class:`.ClauseElement` type."""
+
+    if hasattr(class_, '_compiler_dispatcher'):
+        # regenerate default _compiler_dispatch
+        visitors._generate_dispatch(class_)
+        # remove custom directive
+        del class_._compiler_dispatcher
+
+
 class _dispatcher(object):
     def __init__(self):
         self.specs = {}
index cd14a6452af0b8ee358ad199bdb78353a80be299..1c535461a930c15100a4b5830ce8030d22bf8866 100644 (file)
@@ -3186,6 +3186,94 @@ class CreateTable(_CreateDropBase):
 
     __visit_name__ = "create_table"
 
+    def __init__(self, element, on=None, bind=None):
+        """Create a :class:`.CreateTable` construct.
+
+        :param element: a :class:`.Table` that's the subject
+         of the CREATE
+        :param on: See the description for 'on' in :class:`.DDL`.
+        :param bind: See the description for 'bind' in :class:`.DDL`.
+
+        """
+        super(CreateTable, self).__init__(element, on=on, bind=bind)
+        self.columns = [CreateColumn(column)
+            for column in element.columns
+        ]
+
+class CreateColumn(visitors.Visitable):
+    """Represent a :class:`.Column` as rendered in a CREATE TABLE statement,
+    via the :class:`.CreateTable` construct.
+
+    This is provided to support custom column DDL within the generation
+    of CREATE TABLE statements, by using the
+    compiler extension documented in :ref:`sqlalchemy.ext.compiler_toplevel`
+    to extend :class:`.CreateColumn`.
+
+    Typical integration is to examine the incoming :class:`.Column`
+    object, and to redirect compilation if a particular flag or condition
+    is found::
+
+        from sqlalchemy import schema
+        from sqlalchemy.ext.compiler import compiles
+
+        @compiles(schema.CreateColumn)
+        def compile(element, compiler, **kw):
+            column = element.element
+
+            if "special" not in column.info:
+                return compiler.visit_create_column(element, **kw)
+
+            text = "%s SPECIAL DIRECTIVE %s" % (
+                    column.name,
+                    compiler.type_compiler.process(column.type)
+                )
+            default = compiler.get_column_default_string(column)
+            if default is not None:
+                text += " DEFAULT " + default
+
+            if not column.nullable:
+                text += " NOT NULL"
+
+            if column.constraints:
+                text += " ".join(
+                            compiler.process(const)
+                            for const in column.constraints)
+            return text
+
+    The above construct can be applied to a :class:`.Table` as follows::
+
+        from sqlalchemy import Table, Metadata, Column, Integer, String
+        from sqlalchemy import schema
+
+        metadata = MetaData()
+
+        table = Table('mytable', MetaData(),
+                Column('x', Integer, info={"special":True}, primary_key=True),
+                Column('y', String(50)),
+                Column('z', String(20), info={"special":True})
+            )
+
+        metadata.create_all(conn)
+
+    Above, the directives we've added to the :attr:`.Column.info` collection
+    will be detected by our custom compilation scheme::
+
+        CREATE TABLE mytable (
+                x SPECIAL DIRECTIVE INTEGER NOT NULL,
+                y VARCHAR(50),
+                z SPECIAL DIRECTIVE VARCHAR(20),
+            PRIMARY KEY (x)
+        )
+
+    .. versionadded:: 0.8 The :class:`.CreateColumn` construct was added
+       to support custom column creation styles.
+
+    """
+    __visit_name__ = 'create_column'
+
+    def __init__(self, element):
+        self.element = element
+
 class DropTable(_CreateDropBase):
     """Represent a DROP TABLE statement."""
 
index 3778c768374def7cf55a060f87aeb97e2656ceb0..8a7f05eb33fb1dea5af60f0d2dd5d2f60685b551 100644 (file)
@@ -1728,6 +1728,10 @@ class DDLCompiler(engine.Compiled):
     def sql_compiler(self):
         return self.dialect.statement_compiler(self.dialect, None)
 
+    @util.memoized_property
+    def type_compiler(self):
+        return self.dialect.type_compiler
+
     @property
     def preparer(self):
         return self.dialect.identifier_preparer
@@ -1776,21 +1780,16 @@ class DDLCompiler(engine.Compiled):
 
         # if only one primary key, specify it along with the column
         first_pk = False
-        for column in table.columns:
+        for create_column in create.columns:
+            column = create_column.element
             try:
                 text += separator
                 separator = ", \n"
-                text += "\t" + self.get_column_specification(
-                                                column,
-                                                first_pk=column.primary_key and \
-                                                not first_pk
-                                            )
+                text += "\t" + self.process(create_column,
+                                    first_pk=column.primary_key
+                                    and not first_pk)
                 if column.primary_key:
                     first_pk = True
-                const = " ".join(self.process(constraint) \
-                                for constraint in column.constraints)
-                if const:
-                    text += " " + const
             except exc.CompileError, ce:
                 # Py3K
                 #raise exc.CompileError("(in table '%s', column '%s'): %s"
@@ -1815,6 +1814,20 @@ class DDLCompiler(engine.Compiled):
         text += "\n)%s\n\n" % self.post_create_table(table)
         return text
 
+    def visit_create_column(self, create, first_pk=False):
+        column = create.element
+
+        text = self.get_column_specification(
+                        column,
+                        first_pk=first_pk
+                    )
+        const = " ".join(self.process(constraint) \
+                        for constraint in column.constraints)
+        if const:
+            text += " " + const
+
+        return text
+
     def create_table_constraints(self, table):
 
         # On some DB order is significant: visit PK first, then the
index 0f61ca8d8d39a67287c004a2bcf7ec13a101cf81..d4931931aff6775c34eb8d28b599c40e87ede267 100644 (file)
@@ -5,7 +5,7 @@ from sqlalchemy.sql.expression import ClauseElement, ColumnClause,\
                                     BindParameter
 
 from sqlalchemy.schema import DDLElement
-from sqlalchemy.ext.compiler import compiles
+from sqlalchemy.ext.compiler import compiles, deregister
 from sqlalchemy import exc
 from sqlalchemy.sql import table, column, visitors
 from test.lib.testing import assert_raises_message
@@ -322,9 +322,7 @@ class DefaultOnExistingTest(fixtures.TestBase, AssertsCompiledSQL):
 
     def teardown(self):
         for cls in (Select, BindParameter):
-            if hasattr(cls, '_compiler_dispatcher'):
-                visitors._generate_dispatch(cls)
-                del cls._compiler_dispatcher
+            deregister(cls)
 
     def test_select(self):
         t1 = table('t1', column('c1'), column('c2'))
index 739065d0db77a7bc8d9f1bacb7c5577e9bb74913..0aa5e534108e63c8439586808d70fb35c89a7ccf 100644 (file)
@@ -1204,6 +1204,48 @@ class ColumnDefinitionTest(AssertsCompiledSQL, fixtures.TestBase):
             getattr, select([t1.select().alias()]), 'c'
         )
 
+    def test_custom_create(self):
+        from sqlalchemy.ext.compiler import compiles, deregister
+
+        @compiles(schema.CreateColumn)
+        def compile(element, compiler, **kw):
+            column = element.element
+
+            if "special" not in column.info:
+                return compiler.visit_create_column(element, **kw)
+
+            text = "%s SPECIAL DIRECTIVE %s" % (
+                    column.name,
+                    compiler.type_compiler.process(column.type)
+                )
+            default = compiler.get_column_default_string(column)
+            if default is not None:
+                text += " DEFAULT " + default
+
+            if not column.nullable:
+                text += " NOT NULL"
+
+            if column.constraints:
+                text += " ".join(
+                            compiler.process(const)
+                            for const in column.constraints)
+            return text
+
+        t = Table('mytable', MetaData(),
+                Column('x', Integer, info={"special": True}, primary_key=True),
+                Column('y', String(50)),
+                Column('z', String(20), info={"special": True})
+            )
+
+        self.assert_compile(
+            schema.CreateTable(t),
+            "CREATE TABLE mytable (x SPECIAL DIRECTIVE INTEGER "
+                "NOT NULL, y VARCHAR(50), "
+                "z SPECIAL DIRECTIVE VARCHAR(20), PRIMARY KEY (x))"
+        )
+
+        deregister(schema.CreateColumn)
+
 class ColumnDefaultsTest(fixtures.TestBase):
     """test assignment of default fixures to columns"""