From: Mike Bayer Date: Sun, 17 Apr 2011 20:10:59 +0000 (-0400) Subject: - move documentation of available execution options to Connection - this is the main X-Git-Tag: rel_0_7b4~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=566f4688777118adfa51009adc19be9f1625167e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. --- diff --git a/CHANGES b/CHANGES index 9f2d1c31a6..29fdb757e2 100644 --- a/CHANGES +++ b/CHANGES @@ -74,7 +74,20 @@ CHANGES 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 diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 64dc653cae..00eae60a1c 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -894,8 +894,40 @@ class Connection(Connectable): 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 @@ -910,8 +942,11 @@ class Connection(Connectable): 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() diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index ed6d02f2ce..07df0de429 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -920,7 +920,7 @@ class Query(object): """ 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()` @@ -1749,10 +1749,14 @@ class Query(object): 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 @@ -2448,10 +2452,6 @@ class Query(object): 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, @@ -2501,10 +2501,6 @@ class Query(object): 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) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index c552f055f3..1013f9397b 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -2678,55 +2678,29 @@ class Executable(_Generative): 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:`.Connection.execution_options()` - :meth:`.Query.execution_options()` - applies options to - the statement - generated by a :class:`.orm.Query` object. + :meth:`.Query.execution_options()` """ if 'isolation_level' in kw: @@ -2736,7 +2710,11 @@ class Executable(_Generative): "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): diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 946f24a5d1..838bbcde06 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -1790,9 +1790,9 @@ class ImmediateTest(_fixtures.FixtureTest): sess.bind = testing.db eq_(sess.query().value(sa.literal_column('1').label('x')), 1) -class StatementOptionsTest(QueryTest): +class ExecutionOptionsTest(QueryTest): - def test_query_with_statement_option(self): + def test_option_building(self): User = self.classes.User sess = create_session(bind=testing.db, autocommit=False) @@ -1809,13 +1809,22 @@ class StatementOptionsTest(QueryTest): 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.""" diff --git a/test/sql/test_generative.py b/test/sql/test_generative.py index 0634ded40b..47e45bbb94 100644 --- a/test/sql/test_generative.py +++ b/test/sql/test_generative.py @@ -3,9 +3,9 @@ from sqlalchemy.sql import table, column, ClauseElement 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 @@ -1074,6 +1074,18 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL): 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'))