diff options
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/psycopg2.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 26 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 62 | ||||
-rw-r--r-- | test/dialect/test_postgresql.py | 12 | ||||
-rw-r--r-- | test/orm/test_query.py | 22 | ||||
-rw-r--r-- | test/sql/test_generative.py | 36 |
8 files changed, 89 insertions, 85 deletions
@@ -177,7 +177,7 @@ CHANGES used with dialects that don't support "rowcount" adequately. [ticket:1569] - - added "statement_options()" to Query, to so options can be + - added "execution_options()" to Query, to so options can be passed to the resulting statement. Currently only Select-statements have these options, and the only option used is "stream_results", and the only dialect which knows @@ -304,7 +304,7 @@ CHANGES as appropriate for more complex situations. [ticket:1628] - - Added "statement_options()" to Selects, which set statement + - Added "execution_options()" to Selects, which set statement specific options. These enable e.g. dialect specific options such as whether to enable using server side cursors, etc. diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 7733aadcd..54283581d 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -35,10 +35,10 @@ Transactions The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations. -Statement options ------------------ +Per-Statement Execution Options +------------------------------- -The following statement options are respected: +The following per-statement execution options are respected: * *stream_results* - Enable or disable usage of server side cursors for the SELECT-statement. If *None* or not set, the *server_side_cursors* option of the connection is used. If @@ -112,7 +112,7 @@ SERVER_SIDE_CURSOR_RE = re.compile( class PostgreSQL_psycopg2ExecutionContext(PGExecutionContext): def create_cursor(self): # TODO: coverage for server side cursors + select.for_update() - stream_results_option = self.statement_options.get('stream_results') + stream_results_option = self.execution_options.get('stream_results') is_server_side = ( # Enabled for this statement ... (stream_results_option or diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index baa92b5d4..5d37741bd 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -223,7 +223,7 @@ class DefaultDialect(base.Dialect): class DefaultExecutionContext(base.ExecutionContext): - statement_options = util.frozendict() + execution_options = util.frozendict() def __init__(self, dialect, connection, compiled_sql=None, compiled_ddl=None, statement=None, parameters=None): self.dialect = dialect @@ -269,7 +269,7 @@ class DefaultExecutionContext(base.ExecutionContext): self.isupdate = compiled.isupdate self.isdelete = compiled.isdelete self.should_autocommit = compiled.statement._autocommit - self.statement_options = compiled.statement._statement_options + self.execution_options = compiled.statement._execution_options if self.should_autocommit is expression.PARSE_AUTOCOMMIT: self.should_autocommit = self.should_autocommit_text(self.statement) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 444ad37bc..0a7a738c8 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -80,7 +80,7 @@ class Query(object): _filter_aliases = None _from_obj_alias = None _joinpath = _joinpoint = util.frozendict() - _statement_options = util.frozendict() + _execution_options = util.frozendict() _params = util.frozendict() _attributes = util.frozendict() _with_options = () @@ -490,14 +490,14 @@ class Query(object): Also note that many DBAPIs do not "stream" results, pre-buffering all rows before making them available, including mysql-python and - psycopg2. yield_per() will also set the ``stream_results`` statement + psycopg2. yield_per() will also set the ``stream_results`` execution option to ``True``, which currently is only understood by psycopg2 and causes server side cursors to be used. """ self._yield_per = count - self._statement_options = self._statement_options.copy() - self._statement_options['stream_results'] = True + self._execution_options = self._execution_options.copy() + self._execution_options['stream_results'] = True def get(self, ident): """Return an instance of the object based on the given identifier, or None if not found. @@ -676,19 +676,21 @@ class Query(object): opt.process_query(self) @_generative() - def statement_options(self, **kwargs): - """ Set non-SQL options for the resulting statement, such as dialect-specific options. + def execution_options(self, **kwargs): + """ Set non-SQL options for the resulting statement, + such as dialect-specific options. The only option currently understood is ``stream_results=True``, only used by Psycopg2 to enable "server side cursors". This option - only has a useful effect if used in conjunction with :meth:`~sqlalchemy.orm.query.Query.yield_per()`, + only has a useful effect if used in conjunction with + :meth:`~sqlalchemy.orm.query.Query.yield_per()`, which currently sets ``stream_results`` to ``True`` automatically. """ - _statement_options = self._statement_options.copy() + _execution_options = self._execution_options.copy() for key, value in kwargs.items(): - _statement_options[key] = value - self._statement_options = _statement_options + _execution_options[key] = value + self._execution_options = _execution_options @_generative() def with_lockmode(self, mode): @@ -1938,7 +1940,7 @@ class Query(object): context.adapter = sql_util.ColumnAdapter(inner, equivs) - statement = sql.select([inner] + context.secondary_columns, for_update=for_update, use_labels=labels, statement_options=self._statement_options) + statement = sql.select([inner] + context.secondary_columns, for_update=for_update, use_labels=labels, execution_options=self._execution_options) from_clause = inner for eager_join in eager_joins: @@ -1970,7 +1972,7 @@ class Query(object): for_update=for_update, correlate=False, order_by=context.order_by, - statement_options=self._statement_options, + execution_options=self._execution_options, **self._select_args ) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 2dc13ee82..26338359a 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -977,6 +977,15 @@ def _corresponding_column_or_error(fromclause, column, require_embedded=False): ) return c +@util.decorator +def _generative(fn, *args, **kw): + """Mark a method as generative.""" + + self = args[0]._generate() + fn(self, *args[1:], **kw) + return self + + def is_column(col): """True if ``col`` is an instance of :class:`ColumnElement`.""" @@ -2182,7 +2191,20 @@ class _Executable(object): """Mark a ClauseElement as supporting execution.""" supports_execution = True - _statement_options = util.frozendict() + _execution_options = util.frozendict() + + @_generative + def execution_options(self, **kwargs): + """ Set non-SQL options for the statement, such as dialect-specific options. + + The options available are covered in the respective dialect's section. + + """ + _execution_options = self._execution_options.copy() + for key, value in kwargs.items(): + _execution_options[key] = value + self._execution_options = _execution_options + class _TextClause(_Executable, ClauseElement): """Represent a literal SQL text fragment. @@ -2202,14 +2224,14 @@ class _TextClause(_Executable, ClauseElement): _hide_froms = [] def __init__(self, text = "", bind=None, - bindparams=None, typemap=None, autocommit=PARSE_AUTOCOMMIT, statement_options=None): + bindparams=None, typemap=None, autocommit=PARSE_AUTOCOMMIT, execution_options=None): self._bind = bind self.bindparams = {} self.typemap = typemap self._autocommit = autocommit - self._statement_options = statement_options - if self._statement_options is None: - self._statement_options = {} + self._execution_options = execution_options + if self._execution_options is None: + self._execution_options = {} if typemap is not None: for key in typemap.keys(): typemap[key] = sqltypes.to_instance(typemap[key]) @@ -2799,7 +2821,7 @@ class Alias(FromClause): self.supports_execution = baseselectable.supports_execution if self.supports_execution: self._autocommit = baseselectable._autocommit - self._statement_options = baseselectable._statement_options + self._execution_options = baseselectable._execution_options self.element = selectable if alias is None: if self.original.named_with_column: @@ -3160,14 +3182,6 @@ class TableClause(_Immutable, FromClause): def _from_objects(self): return [self] -@util.decorator -def _generative(fn, *args, **kw): - """Mark a method as generative.""" - - self = args[0]._generate() - fn(self, *args[1:], **kw) - return self - class _SelectBaseMixin(_Executable): """Base class for :class:`Select` and ``CompoundSelects``.""" @@ -3180,16 +3194,16 @@ class _SelectBaseMixin(_Executable): group_by=None, bind=None, autocommit=False, - statement_options=None): + execution_options=None): self.use_labels = use_labels self.for_update = for_update self._autocommit = autocommit self._limit = limit self._offset = offset self._bind = bind - self._statement_options = statement_options - if self._statement_options is None: - self._statement_options = dict() + self._execution_options = execution_options + if self._execution_options is None: + self._execution_options = dict() self._order_by_clause = ClauseList(*util.to_list(order_by) or []) self._group_by_clause = ClauseList(*util.to_list(group_by) or []) @@ -3301,18 +3315,6 @@ class _SelectBaseMixin(_Executable): def _from_objects(self): return [self] - @_generative - def statement_options(self, **kwargs): - """ Set non-SQL options for the statement, such as dialect-specific options. - - The options available are covered in the respective dialect's section. - - """ - _statement_options = self._statement_options.copy() - for key, value in kwargs.items(): - _statement_options[key] = value - self._statement_options = _statement_options - class _ScalarSelect(_Grouping): _from_objects = [] diff --git a/test/dialect/test_postgresql.py b/test/dialect/test_postgresql.py index 5d6bcaf6d..c841accda 100644 --- a/test/dialect/test_postgresql.py +++ b/test/dialect/test_postgresql.py @@ -1455,19 +1455,19 @@ class ServerSideCursorsTest(TestBase, AssertsExecutionResults): # It should be off globally ... assert not result.cursor.name - s = select([1]).statement_options(stream_results=True) + s = select([1]).execution_options(stream_results=True) result = engine.execute(s) # ... but enabled for this one. assert result.cursor.name def test_ss_explicitly_disabled(self): - s = select([1]).statement_options(stream_results=False) + s = select([1]).execution_options(stream_results=False) result = ss_engine.execute(s) assert not result.cursor.name def test_aliases_and_ss(self): engine = engines.testing_engine(options={'server_side_cursors':False}) - s1 = select([1]).statement_options(stream_results=True).alias() + s1 = select([1]).execution_options(stream_results=True).alias() result = engine.execute(s1) assert result.cursor.name @@ -1500,12 +1500,12 @@ class ServerSideCursorsTest(TestBase, AssertsExecutionResults): assert not result.cursor.name, result.cursor.name result.close() - q = sess.query(Foo).statement_options(stream_results=True) + q = sess.query(Foo).execution_options(stream_results=True) result = engine.execute(q.statement) assert result.cursor.name result.close() - result = sess.query(Foo).statement_options(stream_results=True).subquery().execute() + result = sess.query(Foo).execution_options(stream_results=True).subquery().execute() assert result.cursor.name result.close() finally: @@ -1516,7 +1516,7 @@ class ServerSideCursorsTest(TestBase, AssertsExecutionResults): s = text('select 42') result = engine.execute(s) assert not result.cursor.name - s = text('select 42', statement_options=dict(stream_results=True)) + s = text('select 42', execution_options=dict(stream_results=True)) result = engine.execute(s) assert result.cursor.name diff --git a/test/orm/test_query.py b/test/orm/test_query.py index cd89f4d92..22dd20209 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -3748,27 +3748,27 @@ class UpdateDeleteTest(_base.MappedTest): class StatementOptionsTest(QueryTest): - """ Make sure a Query's statement_options are passed on to the + """ Make sure a Query's execution_options are passed on to the resulting statement. """ def test_query_with_statement_option(self): sess = create_session(bind=testing.db, autocommit=False) q1 = sess.query(User) - assert q1._statement_options == dict() - q2 = q1.statement_options(foo='bar', stream_results=True) + assert q1._execution_options == dict() + q2 = q1.execution_options(foo='bar', stream_results=True) # q1's options should be unchanged. - assert q1._statement_options == dict() + assert q1._execution_options == dict() # q2 should have them set. - assert q2._statement_options == dict(foo='bar', stream_results=True) - q3 = q2.statement_options(foo='not bar', answer=42) - assert q2._statement_options == dict(foo='bar', stream_results=True) + assert q2._execution_options == dict(foo='bar', stream_results=True) + q3 = q2.execution_options(foo='not bar', answer=42) + assert q2._execution_options == dict(foo='bar', stream_results=True) q3_options = dict(foo='not bar', stream_results=True, answer=42) - assert q3._statement_options == q3_options - assert q3.statement._statement_options == q3_options - assert q3._compile_context().statement._statement_options == q3_options - assert q3.subquery().original._statement_options == q3_options + 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 diff --git a/test/sql/test_generative.py b/test/sql/test_generative.py index a9a1f59dd..893f2abd7 100644 --- a/test/sql/test_generative.py +++ b/test/sql/test_generative.py @@ -785,27 +785,27 @@ class SelectTest(TestBase, AssertsCompiledSQL): self.assert_compile(select_copy, "SELECT FOOBER table1.col1, table1.col2, table1.col3 FROM table1") self.assert_compile(s, "SELECT table1.col1, table1.col2, table1.col3 FROM table1") - def test_statement_options(self): - s = select().statement_options(foo='bar') - s2 = s.statement_options(bar='baz') - s3 = s.statement_options(foo='not bar') + def test_execution_options(self): + s = select().execution_options(foo='bar') + s2 = s.execution_options(bar='baz') + s3 = s.execution_options(foo='not bar') # The original select should not be modified. - assert s._statement_options == dict(foo='bar') - # s2 should have its statement_options based on s, though. - assert s2._statement_options == dict(foo='bar', bar='baz') - assert s3._statement_options == dict(foo='not bar') - - def test_statement_options_in_kwargs(self): - s = select(statement_options=dict(foo='bar')) - s2 = s.statement_options(bar='baz') + assert s._execution_options == dict(foo='bar') + # s2 should have its execution_options based on s, though. + assert s2._execution_options == dict(foo='bar', bar='baz') + assert s3._execution_options == dict(foo='not bar') + + def test_execution_options_in_kwargs(self): + s = select(execution_options=dict(foo='bar')) + s2 = s.execution_options(bar='baz') # The original select should not be modified. - assert s._statement_options == dict(foo='bar') - # s2 should have its statement_options based on s, though. - assert s2._statement_options == dict(foo='bar', bar='baz') + assert s._execution_options == dict(foo='bar') + # s2 should have its execution_options based on s, though. + assert s2._execution_options == dict(foo='bar', bar='baz') - def test_statement_options_in_text(self): - s = text('select 42', statement_options=dict(foo='bar')) - assert s._statement_options == dict(foo='bar') + def test_execution_options_in_text(self): + s = text('select 42', execution_options=dict(foo='bar')) + assert s._execution_options == dict(foo='bar') class InsertTest(TestBase, AssertsCompiledSQL): """Tests the generative capability of Insert""" |