]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
reject methods as lambda SQL callables
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 18 Jan 2022 22:19:24 +0000 (17:19 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 18 Jan 2022 22:21:59 +0000 (17:21 -0500)
Added an informative error message when a method object is passed to a SQL
construct. Previously, when such a callable were passed, as is a common
typographical error when dealing with method-chained SQL constructs, they
were interpreted as "lambda SQL" targets to be invoked at compilation time,
which would lead to silent failures. As this feature was not intended to be
used with methods, method objects are now rejected.

Fixes: #7032
Change-Id: If714715bd8c11557ab769ee3b1a24264b0b06acc
(cherry picked from commit e28ec27b599558b3e26ced106a972e8b4aa9e668)

doc/build/changelog/unreleased_14/7032.rst [new file with mode: 0644]
lib/sqlalchemy/sql/lambdas.py
test/sql/test_lambdas.py

diff --git a/doc/build/changelog/unreleased_14/7032.rst b/doc/build/changelog/unreleased_14/7032.rst
new file mode 100644 (file)
index 0000000..c837be4
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, sql
+    :tickets: 7032
+
+    Added an informative error message when a method object is passed to a SQL
+    construct. Previously, when such a callable were passed, as is a common
+    typographical error when dealing with method-chained SQL constructs, they
+    were interpreted as "lambda SQL" targets to be invoked at compilation time,
+    which would lead to silent failures. As this feature was not intended to be
+    used with methods, method objects are now rejected.
index e22f8716710ff0be5b15f4d25c2251f6495b5920..5f91559987d3f2553114095c9833d0ab75a046d0 100644 (file)
@@ -5,6 +5,7 @@
 # This module is part of SQLAlchemy and is released under
 # the MIT License: https://www.opensource.org/licenses/mit-license.php
 
+import inspect
 import itertools
 import operator
 import sys
@@ -619,6 +620,10 @@ class AnalyzedCode(object):
         return analyzed
 
     def __init__(self, fn, lambda_element, opts):
+        if inspect.ismethod(fn):
+            raise exc.ArgumentError(
+                "Method %s may not be passed as a SQL expression" % fn
+            )
         closure = fn.__closure__
 
         self.track_bound_values = (
index 76be0af3cea92481536be54ac5d86f3bac188a5c..29e1258efbfe8e2c94caaff9fe7738fc4c51940e 100644 (file)
@@ -8,6 +8,7 @@ from sqlalchemy.sql import and_
 from sqlalchemy.sql import bindparam
 from sqlalchemy.sql import coercions
 from sqlalchemy.sql import column
+from sqlalchemy.sql import func
 from sqlalchemy.sql import join
 from sqlalchemy.sql import lambda_stmt
 from sqlalchemy.sql import lambdas
@@ -38,6 +39,26 @@ class LambdaElementTest(
 ):
     __dialect__ = "default"
 
+    def test_reject_methods(self):
+        """test #7032"""
+
+        t1 = table("t1", column("q"), column("p"))
+
+        subq = select(t1).subquery
+
+        with expect_raises_message(
+            exc.ArgumentError,
+            "Method <bound method Select.*.subquery .* may not be "
+            "passed as a SQL expression",
+        ):
+            select(func.count()).select_from(subq)
+
+        self.assert_compile(
+            select(func.count()).select_from(subq()),
+            "SELECT count(*) AS count_1 FROM "
+            "(SELECT t1.q AS q, t1.p AS p FROM t1) AS anon_1",
+        )
+
     def test_select_whereclause(self):
         t1 = table("t1", column("q"), column("p"))