summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_11.rst11
-rw-r--r--lib/sqlalchemy/engine/default.py4
-rw-r--r--lib/sqlalchemy/sql/compiler.py15
-rw-r--r--test/sql/test_compiler.py33
-rw-r--r--test/sql/test_cte.py9
5 files changed, 69 insertions, 3 deletions
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst
index d7046dee3..1db99b466 100644
--- a/doc/build/changelog/changelog_11.rst
+++ b/doc/build/changelog/changelog_11.rst
@@ -22,6 +22,17 @@
:version: 1.1.0
.. change::
+ :tags: bug, sql
+ :tickets: 3805
+
+ Execution options can now be propagated from within a
+ statement at compile time to the outermost statement, so that
+ if an embedded element wants to set "autocommit" to be True for example,
+ it can propagate this to the enclosing statement. Currently, this
+ feature is enabled for a DML-oriented CTE embedded inside of a SELECT
+ statement, e.g. INSERT/UPDATE/DELETE inside of SELECT.
+
+ .. change::
:tags: bug, orm
:tickets: 3802
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index 891103ee0..05a993918 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -522,7 +522,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
self.compiled = compiled = compiled_ddl
self.isddl = True
- self.execution_options = compiled.statement._execution_options
+ self.execution_options = compiled.execution_options
if connection._execution_options:
self.execution_options = dict(self.execution_options)
self.execution_options.update(connection._execution_options)
@@ -559,7 +559,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
# we get here
assert compiled.can_execute
- self.execution_options = compiled.statement._execution_options.union(
+ self.execution_options = compiled.execution_options.union(
connection._execution_options)
self.result_column_struct = (
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 6527eb8c6..b8c897cb9 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -168,6 +168,12 @@ class Compiled(object):
_cached_metadata = None
+ execution_options = util.immutabledict()
+ """
+ Execution options propagated from the statement. In some cases,
+ sub-elements of the statement can modify these.
+ """
+
def __init__(self, dialect, statement, bind=None,
schema_translate_map=None,
compile_kwargs=util.immutabledict()):
@@ -205,6 +211,8 @@ class Compiled(object):
if statement is not None:
self.statement = statement
self.can_execute = statement.supports_execution
+ if self.can_execute:
+ self.execution_options = statement._execution_options
self.string = self.process(self.statement, **compile_kwargs)
@util.deprecated("0.7", ":class:`.Compiled` objects now compile "
@@ -364,6 +372,7 @@ class SQLCompiler(Compiled):
insert_prefetch = update_prefetch = ()
+
def __init__(self, dialect, statement, column_keys=None,
inline=False, **kwargs):
"""Construct a new :class:`.SQLCompiler` object.
@@ -1296,6 +1305,12 @@ class SQLCompiler(Compiled):
self.ctes_by_name[cte_name] = cte
+ # look for embedded DML ctes and propagate autocommit
+ if 'autocommit' in cte.element._execution_options and \
+ 'autocommit' not in self.execution_options:
+ self.execution_options = self.execution_options.union(
+ {"autocommit": cte.element._execution_options['autocommit']})
+
if cte._cte_alias is not None:
orig_cte = cte._cte_alias
if orig_cte not in self.ctes:
diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py
index 6896c9857..a85786bed 100644
--- a/test/sql/test_compiler.py
+++ b/test/sql/test_compiler.py
@@ -2822,6 +2822,39 @@ class KwargPropagationTest(fixtures.TestBase):
self._do_test(c)
+class ExecutionOptionsTest(fixtures.TestBase):
+ def test_non_dml(self):
+ stmt = table1.select()
+ compiled = stmt.compile()
+
+ eq_(compiled.execution_options, {})
+
+ def test_dml(self):
+ stmt = table1.insert()
+ compiled = stmt.compile()
+
+ eq_(compiled.execution_options, {"autocommit": True})
+
+ def test_embedded_element_true_to_none(self):
+ stmt = table1.insert().cte()
+ eq_(stmt._execution_options, {"autocommit": True})
+ s2 = select([table1]).select_from(stmt)
+ eq_(s2._execution_options, {})
+
+ compiled = s2.compile()
+ eq_(compiled.execution_options, {"autocommit": True})
+
+ def test_embedded_element_true_to_false(self):
+ stmt = table1.insert().cte()
+ eq_(stmt._execution_options, {"autocommit": True})
+ s2 = select([table1]).select_from(stmt).\
+ execution_options(autocommit=False)
+ eq_(s2._execution_options, {"autocommit": False})
+
+ compiled = s2.compile()
+ eq_(compiled.execution_options, {"autocommit": False})
+
+
class CRUDTest(fixtures.TestBase, AssertsCompiledSQL):
__dialect__ = 'default'
diff --git a/test/sql/test_cte.py b/test/sql/test_cte.py
index 35d455257..f81a1d8e5 100644
--- a/test/sql/test_cte.py
+++ b/test/sql/test_cte.py
@@ -1,4 +1,4 @@
-from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import fixtures, eq_
from sqlalchemy.testing import AssertsCompiledSQL, assert_raises_message
from sqlalchemy.sql import table, column, select, func, literal, exists, and_
from sqlalchemy.dialects import mssql
@@ -589,6 +589,8 @@ class CTETest(fixtures.TestBase, AssertsCompiledSQL):
t = products.update().values(price='someprice').\
returning(*products.c).cte('t')
stmt = t.select()
+ assert 'autocommit' not in stmt._execution_options
+ eq_(stmt.compile().execution_options['autocommit'], True)
self.assert_compile(
stmt,
@@ -648,6 +650,9 @@ class CTETest(fixtures.TestBase, AssertsCompiledSQL):
stmt = select([cte])
+ assert 'autocommit' not in stmt._execution_options
+ eq_(stmt.compile().execution_options['autocommit'], True)
+
self.assert_compile(
stmt,
"WITH pd AS "
@@ -661,8 +666,10 @@ class CTETest(fixtures.TestBase, AssertsCompiledSQL):
products = table('products', column('id'), column('price'))
cte = products.select().cte('pd')
+ assert 'autocommit' not in cte._execution_options
stmt = products.update().where(products.c.price == cte.c.price)
+ eq_(stmt.compile().execution_options['autocommit'], True)
self.assert_compile(
stmt,