]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Propagate execution_options at compile stage
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 5 Oct 2016 17:59:58 +0000 (13:59 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 5 Oct 2016 20:47:31 +0000 (16:47 -0400)
Compiler can now set up execution options and additionally
will propagate autocommit from embedded CTEs.

Change-Id: I19db7b8fe4d84549ea95342e8d2040189fed1bbe
Fixes: #3805
doc/build/changelog/changelog_11.rst
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/sql/compiler.py
test/sql/test_compiler.py
test/sql/test_cte.py

index d7046dee315d84c49d97bbaab4c863efdef96ca8..1db99b466a4ee97ce6906090a6a913dc47699b0a 100644 (file)
 .. changelog::
     :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
index 891103ee0f98e22ab4fad5c9c98ca81fbe0b34df..05a9939183b38d42ddb1861bfbbac8c086544e83 100644 (file)
@@ -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 = (
index 6527eb8c653bdbb465f252995c13a7f853b5caa4..b8c897cb90759af5df0990ff3ac56e55ec96b518 100644 (file)
@@ -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:
index 6896c98578998207fdef264e1569da07ecae51ca..a85786bed8e5aaa75ae854d2563cc4e968ceac74 100644 (file)
@@ -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'
 
index 35d4552571d7cfcf5f9f30ca27bfee3c3f116980..f81a1d8e525643413e22ce1d4c7bdf65420b2afd 100644 (file)
@@ -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,