Commits

Mike Bayer committed a5ced0f

- move documentation of available execution options to Connection - this is the main
place these should be used
- Executable disallows "compiled_cache" option for now which was previously being ignored
[ticket:2131]
- Query now passes execution options to the Connection rather than the statement
so that all options are allowed including compiled cache.

Comments (0)

Files changed (6)

     an 0.7 style count() query [ticket:2130].
     (also in 0.6.7)
 
+  - the Query.execution_options() method now passes
+    those options to the Connection rather than
+    the SELECT statement, so that all available
+    options including isolation level and 
+    compiled cache may be used.  [ticket:2131]
+
 - sql
+  - The "compiled_cache" execution option now raises
+    an error when passed to a SELECT statement 
+    rather than a Connection.  Previously it was
+    being ignored entirely.   We may look into
+    having this option work on a per-statement
+    level at some point. [ticket:2131]
+
   - Restored the "catchall" constructor on the base
     TypeEngine class, with a deprecation warning.
     This so that code which does something like

lib/sqlalchemy/engine/base.py

         accepted by :meth:`.Executable.execution_options`.  Additionally,
         it includes options that are applicable only to 
         :class:`.Connection`.
+
+        :param autocommit: Available on: Connection, statement.
+          When True, a COMMIT will be invoked after execution 
+          when executed in 'autocommit' mode, i.e. when an explicit
+          transaction is not begun on the connection. Note that DBAPI
+          connections by default are always in a transaction - SQLAlchemy uses
+          rules applied to different kinds of statements to determine if
+          COMMIT will be invoked in order to provide its "autocommit" feature.
+          Typically, all INSERT/UPDATE/DELETE statements as well as
+          CREATE/DROP statements have autocommit behavior enabled; SELECT
+          constructs do not. Use this option when invoking a SELECT or other
+          specific SQL construct where COMMIT is desired (typically when
+          calling stored procedures and such), and an explicit
+          transaction is not in progress.
+
+        :param compiled_cache: Available on: Connection.
+          A dictionary where :class:`.Compiled` objects
+          will be cached when the :class:`.Connection` compiles a clause 
+          expression into a :class:`.Compiled` object.   
+          It is the user's responsibility to
+          manage the size of this dictionary, which will have keys
+          corresponding to the dialect, clause element, the column
+          names within the VALUES or SET clause of an INSERT or UPDATE, 
+          as well as the "batch" mode for an INSERT or UPDATE statement.
+          The format of this dictionary is not guaranteed to stay the
+          same in future releases.
+
+          Note that the ORM makes use of its own "compiled" caches for 
+          some operations, including flush operations.  The caching
+          used by the ORM internally supersedes a cache dictionary
+          specified here.
         
-        :param isolation_level: Set the transaction isolation level for
+        :param isolation_level: Available on: Connection.
+          Set the transaction isolation level for
           the lifespan of this connection.   Valid values include
           those string values accepted by the ``isolation_level``
           parameter passed to :func:`.create_engine`, and are
           is returned to the connection pool, i.e.
           the :meth:`.Connection.close` method is called.
 
-        :param \**kw: All options accepted by :meth:`.Executable.execution_options`
-          are also accepted.
+        :param stream_results: Available on: Connection, statement.
+          Indicate to the dialect that results should be 
+          "streamed" and not pre-buffered, if possible.  This is a limitation
+          of many DBAPIs.  The flag is currently understood only by the
+          psycopg2 dialect.
 
         """
         c = self._clone()

lib/sqlalchemy/orm/query.py

         """ Set non-SQL options which take effect during execution.
 
         The options are the same as those accepted by 
-        :meth:`sqlalchemy.sql.expression.Executable.execution_options`.
+        :meth:`.Connection.execution_options`.
 
         Note that the ``stream_results`` execution option is enabled
         automatically if the :meth:`~sqlalchemy.orm.query.Query.yield_per()`
         return self._execute_and_instances(context)
 
     def _execute_and_instances(self, querycontext):
-        result = self.session.connection(
+        conn = self.session.connection(
                         mapper = self._mapper_zero_or_none(),
                         clause = querycontext.statement,
-                        close_with_result=True).execute(querycontext.statement, self._params)
+                        close_with_result=True)
+        if self._execution_options:
+            conn = conn.execution_options(**self._execution_options)
+
+        result = conn.execute(querycontext.statement, self._params)
         return self.instances(result, querycontext)
 
     @property
                                 for_update=for_update, 
                                 use_labels=labels)
 
-            if self._execution_options:
-                statement = statement.execution_options(
-                                                **self._execution_options)
-
             from_clause = inner
             for eager_join in eager_joins:
                 # EagerLoader places a 'stop_on' attribute on the join,
             for hint in self._with_hints:
                 statement = statement.with_hint(*hint)
 
-            if self._execution_options:
-                statement = statement.execution_options(
-                                            **self._execution_options)
-
             if self._correlate:
                 statement = statement.correlate(*self._correlate)
 

lib/sqlalchemy/sql/expression.py

     def execution_options(self, **kw):
         """ Set non-SQL options for the statement which take effect during
         execution.
-
-        :param autocommit: when True, a COMMIT will be invoked after execution 
-          when executed in 'autocommit' mode, i.e. when an explicit
-          transaction is not begun on the connection. Note that DBAPI
-          connections by default are always in a transaction - SQLAlchemy uses
-          rules applied to different kinds of statements to determine if
-          COMMIT will be invoked in order to provide its "autocommit" feature.
-          Typically, all INSERT/UPDATE/DELETE statements as well as
-          CREATE/DROP statements have autocommit behavior enabled; SELECT
-          constructs do not. Use this option when invoking a SELECT or other
-          specific SQL construct where COMMIT is desired (typically when
-          calling stored procedures and such).
-
-        :param stream_results: indicate to the dialect that results should be 
-          "streamed" and not pre-buffered, if possible.  This is a limitation
-          of many DBAPIs.  The flag is currently understood only by the
-          psycopg2 dialect.
-
-        :param compiled_cache: a dictionary where :class:`.Compiled` objects
-          will be cached when the :class:`.Connection` compiles a clause 
-          expression into a dialect- and parameter-specific 
-          :class:`.Compiled` object.   It is the user's responsibility to
-          manage the size of this dictionary, which will have keys
-          corresponding to the dialect, clause element, the column
-          names within the VALUES or SET clause of an INSERT or UPDATE, 
-          as well as the "batch" mode for an INSERT or UPDATE statement.
-          The format of this dictionary is not guaranteed to stay the
-          same in future releases.
-
-          This option is usually more appropriate
-          to use via the 
-          :meth:`.Connection.execution_options()`
-          method of :class:`.Connection`, rather than upon individual 
-          statement objects, though the effect is the same.
-          
-          Note that the ORM makes use of its own "compiled" caches for 
-          some operations, including flush operations.  The caching
-          used by the ORM internally supersedes a cache dictionary
-          specified here.
+        
+        Execution options can be set on a per-statement or 
+        per :class:`.Connection` basis.   Additionally, the 
+        :class:`.Engine` and ORM :class:`~.orm.query.Query` objects provide access
+        to execution options which they in turn configure upon connections.
+        
+        The :meth:`execution_options` method is generative.  A new 
+        instance of this statement is returned that contains the options::
+        
+            statement = select([table.c.x, table.c.y])
+            statement = statement.execution_options(autocommit=True)
+        
+        Note that only a subset of possible execution options can be applied
+        to a statement - these include "autocommit" and "stream_results",
+        but not "isolation_level" or "compiled_cache".  
+        See :meth:`.Connection.execution_options` for a full list of 
+        possible options.
 
         See also:
 
-            :meth:`.Connection.execution_options()` -
-            includes a connection-only option to specify transaction isolation
-            level.
-
-            :meth:`.Query.execution_options()` - applies options to 
-                the statement
-            generated by a :class:`.orm.Query` object.
+            :meth:`.Connection.execution_options()`
+
+            :meth:`.Query.execution_options()`
 
         """
         if 'isolation_level' in kw:
                 "per-engine using the isolation_level "
                 "argument to create_engine()."
             )
-
+        if 'compiled_cache' in kw:
+            raise exc.ArgumentError(
+                "'compiled_cache' execution option may only be specified "
+                "on Connection.execution_options(), not per statement."
+            )
         self._execution_options = self._execution_options.union(kw)
 
     def execute(self, *multiparams, **params):

test/orm/test_query.py

         sess.bind = testing.db
         eq_(sess.query().value(sa.literal_column('1').label('x')), 1)
 
-class StatementOptionsTest(QueryTest):
-
-    def test_query_with_statement_option(self):
+class ExecutionOptionsTest(QueryTest):
+
+    def test_option_building(self):
         User = self.classes.User
 
         sess = create_session(bind=testing.db, autocommit=False)
 
         q3_options = dict(foo='not bar', stream_results=True, answer=42)
         assert q3._execution_options == q3_options
-        assert q3.statement._execution_options == q3_options
-        assert q3._compile_context().statement._execution_options == q3_options
-        assert q3.subquery().original._execution_options == q3_options
-
-    # TODO: Test that statement options are passed on to
-    # updates/deletes, but currently there are no such options
-    # applicable for them.
+
+    def test_options_in_connection(self):
+        User = self.classes.User
+
+        execution_options = dict(foo='bar', stream_results=True)
+        class TQuery(Query):
+            def instances(self, result, ctx):
+                eq_(
+                    result.connection._execution_options,
+                    execution_options
+                )
+                return iter([])
+
+        sess = create_session(bind=testing.db, autocommit=False, query_cls=TQuery)
+        q1 = sess.query(User).execution_options(**execution_options)
+        q1.all()
 
 class OptionsTest(QueryTest):
     """Test the _get_paths() method of PropertyOption."""

test/sql/test_generative.py

 from sqlalchemy.sql.expression import  _clone, _from_objects
 from test.lib import *
 from sqlalchemy.sql.visitors import *
-from sqlalchemy import util
+from sqlalchemy import util, exc
 from sqlalchemy.sql import util as sql_util
-from test.lib.testing import eq_
+from test.lib.testing import eq_, assert_raises
 
 class TraversalTest(fixtures.TestBase, AssertsExecutionResults):
     """test ClauseVisitor's traversal, particularly its 
         assert s2._execution_options == dict(foo='bar', bar='baz')
         assert s3._execution_options == dict(foo='not bar')
 
+    def test_invalid_options(self):
+        assert_raises(
+            exc.ArgumentError,
+            select().execution_options, compiled_cache={}
+        )
+
+        assert_raises(
+            exc.ArgumentError,
+            select().execution_options, 
+                isolation_level='READ_COMMITTED'
+        )
+
     # this feature not available yet
     def _NOTYET_test_execution_options_in_kwargs(self):
         s = select(execution_options=dict(foo='bar'))