]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Support unique bound parameters for text()
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 23 Oct 2019 17:03:09 +0000 (13:03 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 23 Oct 2019 18:12:14 +0000 (14:12 -0400)
The :func:`.text` construct now supports "unique" bound parameters, which
will dynamically uniquify themselves on compilation thus allowing multiple
:func:`.text` constructs with the same bound parameter names to be combined
together.

This is a backport from part of 36e8fe4 / #4808 to 1.3.

Fixes: #4933
Change-Id: Ide4069ff5cccd5ed83a5f314e5f21e51dfe08b7d
(cherry picked from commit 8caaa4fba0fbe08f73d09f5b585f12c0857d0de9)

doc/build/changelog/unreleased_13/4933.rst [new file with mode: 0644]
lib/sqlalchemy/sql/elements.py
test/sql/test_text.py

diff --git a/doc/build/changelog/unreleased_13/4933.rst b/doc/build/changelog/unreleased_13/4933.rst
new file mode 100644 (file)
index 0000000..d9b8c6c
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: usecase, sql
+    :tickets: 4933
+
+    The :func:`.text` construct now supports "unique" bound parameters, which
+    will dynamically uniquify themselves on compilation thus allowing multiple
+    :func:`.text` constructs with the same bound parameter names to be combined
+    together.
+
index 22834dd1913a7e1fc24105d8ff2318ce28986501..c11e5445fe06f471e80015773f1b49edf563b130 100644 (file)
@@ -1563,19 +1563,51 @@ class TextClause(Executable, ClauseElement):
                 timestamp=datetime.datetime(2012, 10, 8, 15, 12, 5)
             )
 
+        The :meth:`.TextClause.bindparams` method also supports the concept of
+        **unique** bound parameters.  These are parameters that are
+        "uniquified" on name at statement compilation time, so that  multiple
+        :func:`.text` constructs may be combined together without the names
+        conflicting.  To use this feature, specify the
+        :paramref:`.BindParameter.unique` flag on each :func:`.bindparam`
+        object::
+
+            stmt1 = text("select id from table where name=:name").bindparams(
+                bindparam("name", value='name1', unique=True)
+            )
+            stmt2 = text("select id from table where name=:name").bindparams(
+                bindparam("name", value='name2', unique=True)
+            )
+
+            union = union_all(
+                stmt1.columns(column("id")),
+                stmt2.columns(column("id"))
+            )
+
+        The above statement will render as::
+
+            select id from table where name=:name_1
+            UNION ALL select id from table where name=:name_2
+
+        .. versionadded:: 1.3.11  Added support for the
+           :paramref:`.BindParameter.unique` flag to work with :func:`.text`
+           constructs.
+
         """
         self._bindparams = new_params = self._bindparams.copy()
 
         for bind in binds:
             try:
-                existing = new_params[bind.key]
+                # the regex used for text() currently will not match
+                # a unique/anonymous key in any case, so use the _orig_key
+                # so that a text() construct can support unique parameters
+                existing = new_params[bind._orig_key]
             except KeyError:
                 raise exc.ArgumentError(
                     "This text() construct doesn't define a "
-                    "bound parameter named %r" % bind.key
+                    "bound parameter named %r" % bind._orig_key
                 )
             else:
-                new_params[existing.key] = bind
+                new_params[existing._orig_key] = bind
 
         for key, value in names_to_values.items():
             try:
index 6463e25feac00ff4bcfe7dcf4bd0eacf08a36496..4e1013b00fa689a56c6791421aa277919683c04d 100644 (file)
@@ -279,6 +279,19 @@ class BindParamTest(fixtures.TestBase, AssertsCompiledSQL):
             dialect="postgresql",
         )
 
+    def test_unique_binds(self):
+        # unique binds can be used in text() however they uniquify across
+        # multiple text() constructs only, not within a single text
+
+        t1 = text("select :foo").bindparams(bindparam("foo", 5, unique=True))
+        t2 = text("select :foo").bindparams(bindparam("foo", 10, unique=True))
+        stmt = select([t1, t2])
+        self.assert_compile(
+            stmt,
+            "SELECT select :foo_1, select :foo_2",
+            checkparams={"foo_1": 5, "foo_2": 10},
+        )
+
     def test_binds_compiled_positional(self):
         self.assert_compile(
             text(