]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
document expanding bound parameters, in_(), and baked query use cases
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 27 Aug 2018 18:51:59 +0000 (14:51 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 27 Aug 2018 18:51:59 +0000 (14:51 -0400)
Change-Id: I09ccd73ebabbea4b5098ba5b170786ac065444c8

doc/build/orm/extensions/baked.rst
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/operators.py

index 593293b4606c639254d4efae80d09184bff7b348..83faf299e7f4ab876e8100085b2e7bb90791c624 100644 (file)
@@ -332,6 +332,66 @@ to arrive at the current "baked" approach.   Starting from the
 management,  removal of all redundant Python execution, and queries built up
 with conditionals needed to be addressed, leading to the final approach.
 
+Special Query Techniques
+------------------------
+
+This section will describe some techniques for specific query situations.
+
+.. _baked_in:
+
+Using IN expressions
+^^^^^^^^^^^^^^^^^^^^
+
+The :meth:`.ColumnOperators.in_` method in SQLAlchemy historically renders
+a variable set of bound parameters based on the list of items that's passed
+to the method.   This doesn't work for baked queries as the length of that
+list can change on different calls.  To solve this problem, the
+:paramref:`.bindparam.expanding` parameter supports a late-rendered IN
+expression that is safe to be cached inside of baked query.  The actual list
+of elements is rendered at statement execution time, rather than at
+statement compilation time::
+
+    bakery = baked.bakery()
+
+    baked_query = bakery(lambda session: session.query(User))
+    baked_query += lambda q: q.filter(
+      User.name.in_(bindparam('username', expanding=True)))
+
+    result = baked_query.with_session(session).params(
+      username=['ed', 'fred']).all()
+
+.. seealso::
+
+  :paramref:`.bindparam.expanding`
+
+  :meth:`.ColumnOperators.in_`
+
+Using Subqueries
+^^^^^^^^^^^^^^^^
+
+When using :class:`.Query` objects, it is often needed that one :class:`.Query`
+object is used to generate a subquery within another.   In the case where the
+:class:`.Query` is currently in baked form, an interim method may be used to
+retrieve the :class:`.Query` object, using the semi-private ``_as_query()``
+method.  This method requires that a :class:`.Session` is passed, which should
+be relative to the :class:`.Session` that is in the lambda callable, e.g.
+``q.session`` or ``s``::
+
+    bakery = baked.bakery()
+
+    my_subq = bakery(lambda s: s.query(User.id))
+    my_subq += lambda q: q.filter(User.id == Address.user_id)
+
+    # select a correlated subquery in the top columns list
+    my_q = bakery(
+      lambda s: s.query(Address.id, my_subq._as_query(s).as_scalar()))
+
+    # use a correlated subquery in some of the criteria
+    my_q += lambda q: q.filter(my_subq._as_query(q.session).exists())
+
+A future feature will provide a public method for the above use case.
+
+
 Disabling Baked Queries Session-wide
 ------------------------------------
 
index d79ba05af60b6dfe8273c8473c711fe16d2234e4..dd16b68628dde3e533798a40dce74544ec76ebad 100644 (file)
@@ -1064,6 +1064,12 @@ class BindParameter(ColumnElement):
           This is to allow statement caching to be used in conjunction with
           an IN clause.
 
+          .. seealso::
+
+            :meth:`.ColumnOperators.in_`
+
+            :ref:`baked_in` - with baked queries
+
           .. note:: The "expanding" feature does not support "executemany"-
              style parameter sets.
 
index a9f4e3e3ed2fa9c48dd1a96d046ae7451f498744..5b4a28a06d398f95d41fc2948246402e04cb67c3 100644 (file)
@@ -494,20 +494,84 @@ class ColumnOperators(Operators):
     def in_(self, other):
         """Implement the ``in`` operator.
 
-        In a column context, produces the clause ``a IN other``.
-        "other" may be a tuple/list of column expressions,
-        or a :func:`~.expression.select` construct.
+        In a column context, produces the clause ``column IN <other>``.
 
-        In the case that ``other`` is an empty sequence, the compiler
-        produces an "empty in" expression.   This defaults to the
-        expression "1 != 1" to produce false in all cases.  The
-        :paramref:`.create_engine.empty_in_strategy` may be used to
-        alter this behavior.
+        The given parameter ``other`` may be:
 
-        .. versionchanged:: 1.2  The :meth:`.ColumnOperators.in_` and
-           :meth:`.ColumnOperators.notin_` operators
-           now produce a "static" expression for an empty IN sequence
-           by default.
+        * A list of literal values, e.g.::
+
+            stmt.where(column.in_([1, 2, 3]))
+
+          In this calling form, the list of items is converted to a set of
+          bound parameters the same length as the list given::
+
+            WHERE COL IN (?, ?, ?)
+
+        * An empty list, e.g.::
+
+            stmt.where(column.in_([]))
+
+          In this calling form, the expression renders a "false" expression,
+          e.g.::
+
+            WHERE 1 != 1
+
+          This "false" expression has historically had different behaviors
+          in older SQLAlchemy versions, see
+          :paramref:`.create_engine.empty_in_strategy` for behavioral options.
+
+          .. versionchanged:: 1.2 simplified the behavior of "empty in"
+             expressions
+
+        * A bound parameter, e.g. :func:`.bindparam`, may be used if it
+          includes the :paramref:`.bindparam.expanding` flag::
+
+            stmt.where(column.in_(bindparam('value', expanding=True)))
+
+          In this calling form, the expression renders a special non-SQL
+          placeholder expression that looks like::
+
+            WHERE COL IN ([EXPANDING_value])
+
+          This placeholder expression is intercepted at statement execution
+          time to be converted into the variable number of bound parameter
+          form illustrated earlier.   If the statement were executed as::
+
+            connection.execute(stmt, {"value": [1, 2, 3]})
+
+          The database would be passed a bound parameter for each value::
+
+            WHERE COL IN (?, ?, ?)
+
+          .. versionadded:: 1.2 added "expanding" bound parameters
+
+          If an empty list is passed, a special "empty list" expression,
+          which is specific to the database in use, is rendered.  On
+          SQLite this would be::
+
+            WHERE COL IN (SELECT 1 FROM (SELECT 1) WHERE 1!=1)
+
+          .. versionadded:: 1.3 "expanding" bound parameters now support
+             empty lists
+
+        * a :func:`.select` construct, which is usually a correlated
+          scalar select::
+
+            stmt.where(
+                column.in_(
+                    select([othertable.c.y]).
+                    where(table.c.x == othertable.c.x)
+                )
+            )
+
+          In this calling form, :meth:`.ColumnOperators.in_` renders as given::
+
+            WHERE COL IN (SELECT othertable.y
+            FROM othertable WHERE othertable.x = table.x)
+
+        :param other: a list of literals, a :func:`.select` construct,
+         or a :func:`.bindparam` construct that includes the
+         :paramref:`.bindparam.expanding` flag set to True.
 
         """
         return self.operate(in_op, other)