diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-05-23 14:26:22 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-05-23 14:26:52 -0400 |
commit | 4fe31fe3c62968515ec1117ff13490cd0fc38bfc (patch) | |
tree | 5f63e5efca55579f34e6dfcf6ad13cfecb06fef7 | |
parent | 8a74972499e5d5b3f98876407d42555e0235e99b (diff) | |
download | sqlalchemy-4fe31fe3c62968515ec1117ff13490cd0fc38bfc.tar.gz |
- document the compiler_kwargs accessor
- add new FAQ for rendering SQL as a string
-rw-r--r-- | doc/build/faq.rst | 101 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 20 |
2 files changed, 121 insertions, 0 deletions
diff --git a/doc/build/faq.rst b/doc/build/faq.rst index a1753bbcc..8862cac95 100644 --- a/doc/build/faq.rst +++ b/doc/build/faq.rst @@ -243,6 +243,107 @@ techniques can be seen at `Naming Conventions <http://www.sqlalchemy.org/trac/wi SQL Expressions ================= +.. _faq_sql_expression_string: + +How do I render SQL expressions as strings, possibly with bound parameters inlined? +------------------------------------------------------------------------------------ + +The "stringification" of a SQLAlchemy statement or Query in the vast majority +of cases is as simple as:: + + print str(statement) + +this applies both to an ORM :class:`~.orm.query.Query` as well as any :func:`.select` or other +statement. Additionally, to get the statement as compiled to a +specific dialect or engine, if the statement itself is not already +bound to one you can pass this in to :meth:`.ClauseElement.compile`:: + + print statement.compile(someengine) + +or without an :class:`.Engine`:: + + from sqlalchemy.dialects import postgresql + print statement.compile(dialect=postgresql.dialect()) + +When given an ORM :class:`~.orm.query.Query` object, in order to get at the +:meth:`.ClauseElement.compile` +method we only need access the :attr:`~.orm.query.Query.statement` +accessor first:: + + statement = query.statement + print statement.compile(someengine) + +The above forms will render the SQL statement as it is passed to the Python +:term:`DBAPI`, which includes that bound parameters are not rendered inline. +SQLAlchemy normally does not stringify bound parameters, as this is handled +appropriately by the Python DBAPI, not to mention bypassing bound +parameters is probably the most widely exploited security hole in +modern web applications. SQLAlchemy has limited ability to do this +stringification in certain circumstances such as that of emitting DDL. +In order to access this functionality one can use the ``literal_binds`` +flag, passed to ``compile_kwargs``:: + + from sqlalchemy.sql import table, column, select + + t = table('t', column('x')) + + s = select([t]).where(t.c.x == 5) + + print s.compile(compile_kwargs={"literal_binds": True}) + +the above approach has the caveats that it is only supported for basic +types, such as ints and strings, and furthermore if a :func:`.bindparam` +without a pre-set value is used directly, it won't be able to +stringify that either. Additionally, the ``compile_kwargs`` argument +itself is only available as of SQLAlchemy 0.9; and there are slightly +more verbose ways of getting ``literal_binds`` injected with 0.8. + +.. topic:: Applying compiler kw arguments prior to 0.9 + + We can provide a fixed set of ``**kw`` by calling upon ``process()`` + directly:: + + compiled = s.compile() + print compiled.process(s, literal_binds=True) + + Note that in this approach the statement is actually being stringified + twice, hence using ``compile_kwargs`` in 0.9 should be preferred. + + +If we want to skip using ``literal_binds`` altogether due to the above +caveats, we can take the approach of replacing them out ahead of time +with whatever we want:: + + from sqlalchemy.sql import visitors, text + + def replace_bindparam(element): + if hasattr(element, 'effective_value'): + return text(str(element.effective_value)) + + s = visitors.replacement_traverse(s, {}, replace_bindparam) + print s + +Still another approach to injecting functionality where bound +parameters are concerned is to use the :doc:`Compilation Extension +API <core/compiler>`:: + + from sqlalchemy.ext.compiler import compiles + from sqlalchemy.sql.expression import BindParameter + + s = select([t]).where(t.c.x == 5) + + @compiles(BindParameter) + def as_str(element, compiler, **kw): + if 'binds_as_str' in kw: + return str(element.effective_value) + else: + return compiler.visit_bindparam(element, **kw) + + print s.compile(compile_kwargs={"binds_as_str": True}) + +Above, we pass a self-defined flag ``binds_as_str`` through the compiler, +which we then intercept within our custom render method for :class:`.BindParameter`. + Why does ``.col.in_([])`` Produce ``col != col``? Why not ``1=0``? ------------------------------------------------------------------- diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 9f08aea67..13cf9aa91 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -460,6 +460,26 @@ class ClauseElement(Visitable): also refer to any server-side default generation function associated with a primary key `Column`. + :param compile_kwargs: optional dictionary of additional parameters + that will be passed through to the compiler within all "visit" + methods. This allows any custom flag to be passed through to + a custom compilation construct, for example. It is also used + for the case of passing the ``literal_binds`` flag through:: + + from sqlalchemy.sql import table, column, select + + t = table('t', column('x')) + + s = select([t]).where(t.c.x == 5) + + print s.compile(compile_kwargs={"literal_binds": True}) + + .. versionadded:: 0.9.0 + + .. seealso:: + + :ref:`faq_sql_expression_string` + """ if not dialect: |