]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
use clone, not constructor, in BindParameter.render_literal_execute()
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 22 Mar 2023 15:56:04 +0000 (11:56 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 22 Mar 2023 15:56:04 +0000 (11:56 -0400)
Fixed issue where the :meth:`_sql.BindParameter.render_literal_execute`
method would fail when called on a parameter that also had ORM annotations
associated with it. In practice, this would be observed as a failure of SQL
compilation when using some combinations of a dialect that uses "FETCH
FIRST" such as Oracle along with a :class:`_sql.Select` construct that uses
:meth:`_sql.Select.limit`, within some ORM contexts, including if the
statement were embedded within a relationship primaryjoin expression.

Fixes: #9526
Change-Id: I2f512b6760a90293d274e60b06a891f10b276ecc

doc/build/changelog/unreleased_20/9526.rst [new file with mode: 0644]
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/testing/assertions.py
test/sql/test_external_traversal.py

diff --git a/doc/build/changelog/unreleased_20/9526.rst b/doc/build/changelog/unreleased_20/9526.rst
new file mode 100644 (file)
index 0000000..bd714eb
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 9526
+
+    Fixed issue where the :meth:`_sql.BindParameter.render_literal_execute`
+    method would fail when called on a parameter that also had ORM annotations
+    associated with it. In practice, this would be observed as a failure of SQL
+    compilation when using some combinations of a dialect that uses "FETCH
+    FIRST" such as Oracle along with a :class:`_sql.Select` construct that uses
+    :meth:`_sql.Select.limit`, within some ORM contexts, including if the
+    statement were embedded within a relationship primaryjoin expression.
+
index ef4587e1874e6b7ea4aa8f7b0ead9d8e00b6e745..8bf834d1174c501e7edd41865523c47038dde9ce 100644 (file)
@@ -2057,12 +2057,9 @@ class BindParameter(roles.InElementRole, KeyedColumnElement[_T]):
             :ref:`engine_thirdparty_caching`
 
         """
-        return self.__class__(
-            self.key,
-            self.value,
-            type_=self.type,
-            literal_execute=True,
-        )
+        c = ClauseElement._clone(self)
+        c.literal_execute = True
+        return c
 
     def _negate_in_binary(self, negated_op, original_op):
         if self.expand_op is original_op:
index c66ba71c3f132ab270741226fc96095dc3c48dc6..a51d831a90f080028c93ce23a55659bc5abf986f 100644 (file)
@@ -498,6 +498,7 @@ class AssertsCompiledSQL:
         default_schema_name=None,
         from_linting=False,
         check_param_order=True,
+        use_literal_execute_for_simple_int=False,
     ):
         if use_default_dialect:
             dialect = default.DefaultDialect()
@@ -541,6 +542,9 @@ class AssertsCompiledSQL:
         if render_postcompile:
             compile_kwargs["render_postcompile"] = True
 
+        if use_literal_execute_for_simple_int:
+            compile_kwargs["use_literal_execute_for_simple_int"] = True
+
         if for_executemany:
             kw["for_executemany"] = True
 
index b8f6e5685abc4119c23aa79d28811137139b1665..e474e75d7568b5cf0b8532b6a18a3d6457a9de34 100644 (file)
@@ -33,6 +33,7 @@ from sqlalchemy.sql import util as sql_util
 from sqlalchemy.sql import visitors
 from sqlalchemy.sql.elements import _clone
 from sqlalchemy.sql.expression import _from_objects
+from sqlalchemy.sql.util import _deep_annotate
 from sqlalchemy.sql.visitors import ClauseVisitor
 from sqlalchemy.sql.visitors import cloned_traverse
 from sqlalchemy.sql.visitors import CloningVisitor
@@ -508,6 +509,71 @@ class ClauseTest(fixtures.TestBase, AssertsCompiledSQL):
 
         self.assert_compile(adapted, expected)
 
+    @testing.variation("annotate", [True, False])
+    def test_bindparam_render_literal_execute(self, annotate):
+        """test #9526"""
+
+        bp = bindparam("some_value")
+
+        if annotate:
+            bp = bp._annotate({"foo": "bar"})
+
+        bp = bp.render_literal_execute()
+        self.assert_compile(
+            column("q") == bp, "q = __[POSTCOMPILE_some_value]"
+        )
+
+    @testing.variation("limit_type", ["limit", "fetch"])
+    @testing.variation("dialect", ["default", "oracle"])
+    def test_annotated_fetch(self, limit_type: testing.Variation, dialect):
+        """test #9526"""
+
+        if limit_type.limit:
+            stmt = select(column("q")).limit(1)
+        elif limit_type.fetch:
+            stmt = select(column("q")).fetch(1)
+        else:
+            limit_type.fail()
+
+        stmt = _deep_annotate(stmt, {"foo": "bar"})
+
+        if limit_type.limit:
+            if dialect.default:
+                self.assert_compile(
+                    stmt,
+                    "SELECT q LIMIT :param_1",
+                    use_literal_execute_for_simple_int=True,
+                    dialect=dialect.name,
+                )
+            elif dialect.oracle:
+                self.assert_compile(
+                    stmt,
+                    "SELECT q FROM DUAL FETCH FIRST "
+                    "__[POSTCOMPILE_param_1] ROWS ONLY",
+                    dialect=dialect.name,
+                )
+            else:
+                dialect.fail()
+        elif limit_type.fetch:
+            if dialect.default:
+                self.assert_compile(
+                    stmt,
+                    "SELECT q FETCH FIRST __[POSTCOMPILE_param_1] ROWS ONLY",
+                    use_literal_execute_for_simple_int=True,
+                    dialect=dialect.name,
+                )
+            elif dialect.oracle:
+                self.assert_compile(
+                    stmt,
+                    "SELECT q FROM DUAL FETCH FIRST "
+                    "__[POSTCOMPILE_param_1] ROWS ONLY",
+                    dialect=dialect.name,
+                )
+            else:
+                dialect.fail()
+        else:
+            limit_type.fail()
+
     @testing.combinations((null(),), (true(),))
     def test_dont_adapt_singleton_elements(self, elem):
         """test :ticket:`6259`"""