summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-05-23 14:26:22 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-05-23 14:26:52 -0400
commit4fe31fe3c62968515ec1117ff13490cd0fc38bfc (patch)
tree5f63e5efca55579f34e6dfcf6ad13cfecb06fef7
parent8a74972499e5d5b3f98876407d42555e0235e99b (diff)
downloadsqlalchemy-4fe31fe3c62968515ec1117ff13490cd0fc38bfc.tar.gz
- document the compiler_kwargs accessor
- add new FAQ for rendering SQL as a string
-rw-r--r--doc/build/faq.rst101
-rw-r--r--lib/sqlalchemy/sql/elements.py20
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: