summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/lambdas.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-08-15 18:12:42 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-08-17 14:30:24 -0400
commit09df554a85ff1f9e35e4275465ba4eca029a61b3 (patch)
tree8286ebf4bb54e103e10a0be1ebd3cab2152e16b4 /lib/sqlalchemy/sql/lambdas.py
parent76b506ed51e31b922014a30de2a5952d1a6ad891 (diff)
downloadsqlalchemy-09df554a85ff1f9e35e4275465ba4eca029a61b3.tar.gz
honor NO_CACHE in lambdas
Fixed issue in lambda caching system where an element of a query that produces no cache key, like a custom option or clause element, would still populate the expression in the "lambda cache" inappropriately. This was discovered as part of :ticket:`6887` but is a separate issue. References: #6887 Change-Id: I1665f4320254ddc63a0abf3088e9daeaffbd1840
Diffstat (limited to 'lib/sqlalchemy/sql/lambdas.py')
-rw-r--r--lib/sqlalchemy/sql/lambdas.py99
1 files changed, 70 insertions, 29 deletions
diff --git a/lib/sqlalchemy/sql/lambdas.py b/lib/sqlalchemy/sql/lambdas.py
index d33e8ebfb..36e470ce7 100644
--- a/lib/sqlalchemy/sql/lambdas.py
+++ b/lib/sqlalchemy/sql/lambdas.py
@@ -182,28 +182,49 @@ class LambdaElement(elements.ClauseElement):
self._resolved_bindparams = bindparams = []
- anon_map = traversals.anon_map()
- cache_key = tuple(
- [
- getter(closure, opts, anon_map, bindparams)
- for getter in tracker.closure_trackers
- ]
- )
-
if self.parent_lambda is not None:
- cache_key = self.parent_lambda.closure_cache_key + cache_key
+ parent_closure_cache_key = self.parent_lambda.closure_cache_key
+ else:
+ parent_closure_cache_key = ()
+
+ if parent_closure_cache_key is not traversals.NO_CACHE:
+ anon_map = traversals.anon_map()
+ cache_key = tuple(
+ [
+ getter(closure, opts, anon_map, bindparams)
+ for getter in tracker.closure_trackers
+ ]
+ )
- self.closure_cache_key = cache_key
+ if traversals.NO_CACHE not in anon_map:
+ cache_key = parent_closure_cache_key + cache_key
- try:
- rec = lambda_cache[tracker_key + cache_key]
- except KeyError:
+ self.closure_cache_key = cache_key
+
+ try:
+ rec = lambda_cache[tracker_key + cache_key]
+ except KeyError:
+ rec = None
+ else:
+ cache_key = traversals.NO_CACHE
+ rec = None
+
+ else:
+ cache_key = traversals.NO_CACHE
rec = None
+ self.closure_cache_key = cache_key
+
if rec is None:
- rec = AnalyzedFunction(tracker, self, apply_propagate_attrs, fn)
- rec.closure_bindparams = bindparams
- lambda_cache[tracker_key + cache_key] = rec
+ if cache_key is not traversals.NO_CACHE:
+ rec = AnalyzedFunction(
+ tracker, self, apply_propagate_attrs, fn
+ )
+ rec.closure_bindparams = bindparams
+ lambda_cache[tracker_key + cache_key] = rec
+ else:
+ rec = NonAnalyzedFunction(self._invoke_user_fn(fn))
+
else:
bindparams[:] = [
orig_bind._with_value(new_bind.value, maintain_key=True)
@@ -212,21 +233,24 @@ class LambdaElement(elements.ClauseElement):
)
]
- if self.parent_lambda is not None:
- bindparams[:0] = self.parent_lambda._resolved_bindparams
-
self._rec = rec
- lambda_element = self
- while lambda_element is not None:
- rec = lambda_element._rec
- if rec.bindparam_trackers:
- tracker_instrumented_fn = rec.tracker_instrumented_fn
- for tracker in rec.bindparam_trackers:
- tracker(
- lambda_element.fn, tracker_instrumented_fn, bindparams
- )
- lambda_element = lambda_element.parent_lambda
+ if cache_key is not traversals.NO_CACHE:
+ if self.parent_lambda is not None:
+ bindparams[:0] = self.parent_lambda._resolved_bindparams
+
+ lambda_element = self
+ while lambda_element is not None:
+ rec = lambda_element._rec
+ if rec.bindparam_trackers:
+ tracker_instrumented_fn = rec.tracker_instrumented_fn
+ for tracker in rec.bindparam_trackers:
+ tracker(
+ lambda_element.fn,
+ tracker_instrumented_fn,
+ bindparams,
+ )
+ lambda_element = lambda_element.parent_lambda
return rec
@@ -304,6 +328,9 @@ class LambdaElement(elements.ClauseElement):
return expr
def _gen_cache_key(self, anon_map, bindparams):
+ if self.closure_cache_key is traversals.NO_CACHE:
+ anon_map[traversals.NO_CACHE] = True
+ return None
cache_key = (
self.fn.__code__,
@@ -914,6 +941,20 @@ class AnalyzedCode(object):
)
+class NonAnalyzedFunction(object):
+ __slots__ = ("expr",)
+
+ closure_bindparams = None
+ bindparam_trackers = None
+
+ def __init__(self, expr):
+ self.expr = expr
+
+ @property
+ def expected_expr(self):
+ return self.expr
+
+
class AnalyzedFunction(object):
__slots__ = (
"analyzed_code",