From: Mike Bayer Date: Wed, 23 Oct 2019 17:03:09 +0000 (-0400) Subject: Support unique bound parameters for text() X-Git-Tag: rel_1_3_11~25^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6028185f725d3052be77f617f9e9ad21b6522177;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Support unique bound parameters for text() 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) --- diff --git a/doc/build/changelog/unreleased_13/4933.rst b/doc/build/changelog/unreleased_13/4933.rst new file mode 100644 index 0000000000..d9b8c6c23d --- /dev/null +++ b/doc/build/changelog/unreleased_13/4933.rst @@ -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. + diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 22834dd191..c11e5445fe 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -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: diff --git a/test/sql/test_text.py b/test/sql/test_text.py index 6463e25fea..4e1013b00f 100644 --- a/test/sql/test_text.py +++ b/test/sql/test_text.py @@ -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(