diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-02-03 10:42:08 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-02-03 10:42:08 -0500 |
commit | 52728f7052e643e9890da75ef2c756d4bead41f0 (patch) | |
tree | 8bfe8a805028563213209322056618f7d34aedbc | |
parent | a7eeac60cae28bb553327d317a88adb22c799ef3 (diff) | |
download | sqlalchemy-52728f7052e643e9890da75ef2c756d4bead41f0.tar.gz |
Add coercions to literal()
To prevent literal() from receiving a ClauseElement which
then leads to confusing results, add a new LiteralValueRole
coercion that does an _is_literal() check and implement
for literal().
Fixes: #5639
Change-Id: Ibd686544af2d7c013765278f984baf237de88caf
-rw-r--r-- | lib/sqlalchemy/sql/coercions.py | 23 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/roles.py | 4 | ||||
-rw-r--r-- | test/sql/test_roles.py | 13 |
4 files changed, 38 insertions, 4 deletions
diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 3b972be41..0d5ffd43a 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -138,7 +138,7 @@ def expect(role, element, apply_propagate_attrs=None, argname=None, **kw): ): resolved = None - if impl._resolve_string_only: + if impl._resolve_literal_only: resolved = impl._literal_coercion(element, **kw) else: @@ -223,7 +223,7 @@ class RoleImpl(object): raise NotImplementedError() _post_coercion = None - _resolve_string_only = False + _resolve_literal_only = False def __init__(self, role_class): self._role_class = role_class @@ -274,7 +274,7 @@ class _Deannotate(object): class _StringOnly(object): __slots__ = () - _resolve_string_only = True + _resolve_literal_only = True class _ReturnsStringKey(object): @@ -380,6 +380,23 @@ class _CoerceLiterals(object): self._raise_for_expected(element, argname) +class LiteralValueImpl(RoleImpl): + _resolve_literal_only = True + + def _implicit_coercions( + self, element, resolved, argname, type_=None, **kw + ): + if not _is_literal(resolved): + self._raise_for_expected( + element, resolved=resolved, argname=argname, **kw + ) + + return elements.BindParameter(None, element, type_=type_, unique=True) + + def _literal_coercion(self, element, argname=None, type_=None, **kw): + return element + + class _SelectIsNotFrom(object): __slots__ = () diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index a9f21cd5f..3d6643503 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -150,7 +150,7 @@ def literal(value, type_=None): will provide bind-parameter translation for this literal. """ - return BindParameter(None, value, type_=type_, unique=True) + return coercions.expect(roles.LiteralValueRole, value, type_=type_) def outparam(key, type_=None): diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py index 2c4ff75c4..4f9ef1f75 100644 --- a/lib/sqlalchemy/sql/roles.py +++ b/lib/sqlalchemy/sql/roles.py @@ -36,6 +36,10 @@ class HasCacheKeyRole(SQLRole): _role_name = "Cacheable Core or ORM object" +class LiteralValueRole(SQLRole): + _role_name = "Literal Python value" + + class ColumnArgumentRole(SQLRole): _role_name = "Column expression" diff --git a/test/sql/test_roles.py b/test/sql/test_roles.py index 0ef90e89e..4d905aca9 100644 --- a/test/sql/test_roles.py +++ b/test/sql/test_roles.py @@ -145,6 +145,19 @@ class RoleTest(fixtures.TestBase): ) ) + def test_no_clauseelement_in_bind(self): + with testing.expect_raises_message( + exc.ArgumentError, + r"Literal Python value expected, got BindParameter", + ): + literal(bindparam("x")) + + with testing.expect_raises_message( + exc.ArgumentError, + r"Literal Python value expected, got .*ColumnClause", + ): + literal(column("q")) + def test_scalar_select_no_coercion(self): with testing.expect_warnings( "implicitly coercing SELECT object to scalar subquery" |