summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/compiler.py
blob: 5e9a17fcbc5a61cc9e2374af98c1c5b84838748a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Provides an API for creation of custom ClauseElements and compilers.

Synopsis
========

Usage involves the creation of one or more :class:`~sqlalchemy.sql.expression.ClauseElement`
subclasses and one or more callables defining its compilation::

    from sqlalchemy.ext.compiler import compiles
    from sqlalchemy.sql.expression import ColumnClause
    
    class MyColumn(ColumnClause):
        pass
            
    @compiles(MyColumn)
    def compile_mycolumn(element, compiler, **kw):
        return "[%s]" % element.name
        
Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`, the
base expression element for column objects.  The ``compiles`` decorator registers
itself with the ``MyColumn`` class so that it is invoked when the object 
is compiled to a string::

    from sqlalchemy import select
    
    s = select([MyColumn('x'), MyColumn('y')])
    print str(s)
    
Produces::

    SELECT [x], [y]

Dialect-specific compilation rules
==================================

Compilers can also be made dialect-specific.  The appropriate compiler will be invoked
for the dialect in use::

    from sqlalchemy.schema import DDLElement  # this is a SQLA 0.6 construct

    class AlterColumn(DDLElement):

        def __init__(self, column, cmd):
            self.column = column
            self.cmd = cmd

    @compiles(AlterColumn)
    def visit_alter_column(element, compiler, **kw):
        return "ALTER COLUMN %s ..." % element.column.name

    @compiles(AlterColumn, 'postgres')
    def visit_alter_column(element, compiler, **kw):
        return "ALTER TABLE %s ALTER COLUMN %s ..." % (element.table.name, element.column.name)

The second ``visit_alter_table`` will be invoked when any ``postgres`` dialect is used.

Compiling sub-elements of a custom expression construct
=======================================================

The ``compiler`` argument is the :class:`~sqlalchemy.engine.base.Compiled` object
in use.  This object can be inspected for any information about the in-progress 
compilation, including ``compiler.dialect``, ``compiler.statement`` etc.
The :class:`~sqlalchemy.sql.compiler.SQLCompiler` and :class:`~sqlalchemy.sql.compiler.DDLCompiler`
both include a ``process()`` method which can be used for compilation of embedded attributes::

    class InsertFromSelect(ClauseElement):
        def __init__(self, table, select):
            self.table = table
            self.select = select

    @compiles(InsertFromSelect)
    def visit_insert_from_select(element, compiler, **kw):
        return "INSERT INTO %s (%s)" % (
            compiler.process(element.table, asfrom=True),
            compiler.process(element.select)
        )

    insert = InsertFromSelect(t1, select([t1]).where(t1.c.x>5))
    print insert
    
Produces::

    "INSERT INTO mytable (SELECT mytable.x, mytable.y, mytable.z FROM mytable WHERE mytable.x > :x_1)"

.. versionadded:: 0.6
    :class:`~sqlalchemy.sql.compiler.DDLCompiler`\ .

Changing the default compilation of existing constructs
=======================================================

The compiler extension applies just as well to the existing constructs.  When overriding
the compilation of a built in SQL construct, the @compiles decorator is invoked upon
the appropriate class (be sure to use the class, i.e. ``Insert`` or ``Select``, instead of the creation function such as ``insert()`` or ``select()``).

Within the new compilation function, to get at the "original" compilation routine,
use the appropriate visit_XXX method - this because compiler.process() will call upon the 
overriding routine and cause an endless loop.   Such as, to add "prefix" to all insert statements::

    from sqlalchemy.sql.expression import Insert

    @compiles(Insert)
    def prefix_inserts(insert, compiler, **kw):
        return compiler.visit_insert(insert.prefix_with("some prefix"), **kw)

The above compiler will prefix all INSERT statements with "some prefix" when compiled.

"""

def compiles(class_, *specs):
    def decorate(fn):
        existing = getattr(class_, '_compiler_dispatcher', None)
        if not existing:
            existing = _dispatcher()

            # TODO: why is the lambda needed ?
            setattr(class_, '_compiler_dispatch', lambda *arg, **kw: existing(*arg, **kw))
            setattr(class_, '_compiler_dispatcher', existing)
        
        if specs:
            for s in specs:
                existing.specs[s] = fn
        else:
            existing.specs['default'] = fn
        return fn
    return decorate
    
class _dispatcher(object):
    def __init__(self):
        self.specs = {}
    
    def __call__(self, element, compiler, **kw):
        # TODO: yes, this could also switch off of DBAPI in use.
        fn = self.specs.get(compiler.dialect.name, None)
        if not fn:
            fn = self.specs['default']
        return fn(element, compiler, **kw)