]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
allow Executable to be accepted by Session.execute()
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 15 Jan 2021 04:01:13 +0000 (23:01 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 15 Jan 2021 04:35:41 +0000 (23:35 -0500)
Fixed an issue where the API to create a custom executable SQL construct
using the ``sqlalchemy.ext.compiles`` extension according to the
documentation that's been up for many years would no longer function if
only ``Executable, ClauseElement`` were used as the base classes,
additional classes were needed if wanting to use
:meth:`_orm.Session.execute`. This has been resolved so that those extra
classes aren't needed.

Change-Id: I99b8acd88515c2a52842d62974199121e64c0381

doc/build/changelog/unreleased_14/simplify_executable.rst [new file with mode: 0644]
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/elements.py
test/ext/test_compiler.py

diff --git a/doc/build/changelog/unreleased_14/simplify_executable.rst b/doc/build/changelog/unreleased_14/simplify_executable.rst
new file mode 100644 (file)
index 0000000..6c91c13
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, orm
+
+    Fixed an issue where the API to create a custom executable SQL construct
+    using the ``sqlalchemy.ext.compiles`` extension according to the
+    documentation that's been up for many years would no longer function if
+    only ``Executable, ClauseElement`` were used as the base classes,
+    additional classes were needed if wanting to use
+    :meth:`_orm.Session.execute`. This has been resolved so that those extra
+    classes aren't needed.
index a1426b62852357afcc3107e347971be46b08d885..550111020eb05b048f8bde3d556a6fbd3e6420e7 100644 (file)
@@ -729,7 +729,7 @@ class ExecutableOption(HasCopyInternals, HasCacheKey):
         return c
 
 
-class Executable(Generative):
+class Executable(roles.CoerceTextStatementRole, Generative):
     """Mark a :class:`_expression.ClauseElement` as supporting execution.
 
     :class:`.Executable` is a superclass for all "statement" types
index d3c767b5db39153560957d2c21d226e5e88acb98..5ea3526eaf85883da45fba259fee1806f666080f 100644 (file)
@@ -1502,7 +1502,6 @@ class TextClause(
     roles.OrderByRole,
     roles.FromClauseRole,
     roles.SelectStatementRole,
-    roles.CoerceTextStatementRole,
     roles.BinaryElementRole,
     roles.InElementRole,
     Executable,
index c10e27180bf99deeb7670affd1a4c0dabc22a697..534b839be1c603316d1b57882c8c7ad2ca452dd6 100644 (file)
@@ -7,10 +7,13 @@ from sqlalchemy import literal_column
 from sqlalchemy import MetaData
 from sqlalchemy import Numeric
 from sqlalchemy import select
+from sqlalchemy import String
 from sqlalchemy import Table
 from sqlalchemy import table
+from sqlalchemy import testing
 from sqlalchemy.ext.compiler import compiles
 from sqlalchemy.ext.compiler import deregister
+from sqlalchemy.orm import Session
 from sqlalchemy.schema import CreateColumn
 from sqlalchemy.schema import CreateTable
 from sqlalchemy.schema import DDLElement
@@ -18,6 +21,7 @@ from sqlalchemy.sql.elements import ColumnElement
 from sqlalchemy.sql.expression import BindParameter
 from sqlalchemy.sql.expression import ClauseElement
 from sqlalchemy.sql.expression import ColumnClause
+from sqlalchemy.sql.expression import Executable
 from sqlalchemy.sql.expression import FunctionElement
 from sqlalchemy.sql.expression import Select
 from sqlalchemy.sql.sqltypes import NULLTYPE
@@ -491,3 +495,72 @@ class DefaultOnExistingTest(fixtures.TestBase, AssertsCompiledSQL):
             {"a": 1, "b": 2},
             use_default_dialect=True,
         )
+
+
+class ExecuteTest(fixtures.TablesTest):
+    """test that Executable constructs work at a rudimentary level."""
+
+    __requires__ = ("standard_cursor_sql",)
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table(
+            "some_table",
+            metadata,
+            Column("id", Integer, primary_key=True, autoincrement=False),
+            Column("data", String(50)),
+        )
+
+    @testing.fixture()
+    def insert_fixture(self):
+        class MyInsert(Executable, ClauseElement):
+            pass
+
+        @compiles(MyInsert)
+        def _run_myinsert(element, compiler, **kw):
+            return "INSERT INTO some_table (id, data) VALUES(1, 'some data')"
+
+        return MyInsert
+
+    @testing.fixture()
+    def select_fixture(self):
+        class MySelect(Executable, ClauseElement):
+            pass
+
+        @compiles(MySelect)
+        def _run_myinsert(element, compiler, **kw):
+            return "SELECT id, data FROM some_table"
+
+        return MySelect
+
+    def test_insert(self, connection, insert_fixture):
+        connection.execute(insert_fixture())
+
+        some_table = self.tables.some_table
+        eq_(connection.scalar(select(some_table.c.data)), "some data")
+
+    def test_insert_session(self, connection, insert_fixture):
+        with Session(connection) as session:
+            session.execute(insert_fixture())
+
+        some_table = self.tables.some_table
+
+        eq_(connection.scalar(select(some_table.c.data)), "some data")
+
+    def test_select(self, connection, select_fixture):
+        some_table = self.tables.some_table
+
+        connection.execute(some_table.insert().values(id=1, data="some data"))
+        result = connection.execute(select_fixture())
+
+        eq_(result.first(), (1, "some data"))
+
+    def test_select_session(self, connection, select_fixture):
+        some_table = self.tables.some_table
+
+        connection.execute(some_table.insert().values(id=1, data="some data"))
+
+        with Session(connection) as session:
+            result = session.execute(select_fixture())
+
+            eq_(result.first(), (1, "some data"))