]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add coercions to literal()
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 3 Feb 2021 15:42:08 +0000 (10:42 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 3 Feb 2021 15:42:08 +0000 (10:42 -0500)
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

lib/sqlalchemy/sql/coercions.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/roles.py
test/sql/test_roles.py

index 3b972be41d39b1862b60dbec4fabadad51e5a6ab..0d5ffd43aa180790103c8a346fccef1144692e27 100644 (file)
@@ -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__ = ()
 
index a9f21cd5f8405a2b22238874b083b2078d620c0e..3d66435039c5b96996f8f2c3e75cb8773c200151 100644 (file)
@@ -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):
index 2c4ff75c4e77932a2fa21a40f5498ac7760a8cb3..4f9ef1f75caeb7b387ed04468848525c60384d2c 100644 (file)
@@ -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"
 
index 0ef90e89e974b3ec74a3c011c5f544eed46a89de..4d905aca94e227d2115fd29c524e8417152c5d4a 100644 (file)
@@ -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"