diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2009-05-17 22:58:21 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2009-05-17 22:58:21 +0000 |
commit | d7e531ce9f0def1f08d78b3a7a7d6f268c5eb0bb (patch) | |
tree | 4c5484051bc323bf20a3ad6b1b9a87bbed478d36 /lib/sqlalchemy/ext/compiler.py | |
parent | d1d3c1ad930e5bbccb081f69be570479c3512ef3 (diff) | |
download | sqlalchemy-d7e531ce9f0def1f08d78b3a7a7d6f268c5eb0bb.tar.gz |
- Back-ported the "compiler" extension from SQLA 0.6. Thisrel_0_5_4
is a standardized interface which allows the creation of custom
ClauseElement subclasses and compilers. In particular it's
handy as an alternative to text() when you'd like to
build a construct that has database-specific compilations.
See the extension docs for details.
Diffstat (limited to 'lib/sqlalchemy/ext/compiler.py')
-rw-r--r-- | lib/sqlalchemy/ext/compiler.py | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/lib/sqlalchemy/ext/compiler.py b/lib/sqlalchemy/ext/compiler.py new file mode 100644 index 000000000..0e3db00e0 --- /dev/null +++ b/lib/sqlalchemy/ext/compiler.py @@ -0,0 +1,110 @@ +"""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] + +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. + +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` (DDLCompiler is 0.6. only) +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)" + + +""" + +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) + |