]> 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:19:24 +0000 (17:19 -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

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 d71c85d609037b44f36566b4bc56a04cad9ea0d4..ae7358870ff850b2b18e30d4f4c23961518d8409 100644 (file)
@@ -6,6 +6,7 @@
 # the MIT License: https://www.opensource.org/licenses/mit-license.php
 
 import collections.abc as collections_abc
+import inspect
 import itertools
 import operator
 import types
@@ -618,6 +619,10 @@ class AnalyzedCode:
         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 43aed0672542e5b0c6be00ec36cf242702dacdd9..96a6ecbf4830c7e69e9abca5bab3610c7a60a8bd 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 SelectBase.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"))