]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- document the compiler_kwargs accessor
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 23 May 2014 18:26:22 +0000 (14:26 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 23 May 2014 18:26:52 +0000 (14:26 -0400)
- add new FAQ for rendering SQL as a string

doc/build/faq.rst
lib/sqlalchemy/sql/elements.py

index a1753bbcc093e1ff2cf884494acd1eaf63b52fdd..8862cac954319fd850df27c99732c572bfd7656e 100644 (file)
@@ -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``?
 -------------------------------------------------------------------
 
index 9f08aea6719d9026785a260f027277ea68051ee9..13cf9aa918eaac4f9d845f863c8c3b82c18c30c3 100644 (file)
@@ -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: