diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-12-16 17:06:43 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-07-03 23:39:51 -0400 |
commit | 3dc9a4a2392d033f9d1bd79dd6b6ecea6281a61c (patch) | |
tree | 1041bccb37422f526dccb5b1e57ffad1c702549b /lib/sqlalchemy/sql/traversals.py | |
parent | 5060043e8e95ab0aab5f63ed288c1426c46da66e (diff) | |
download | sqlalchemy-3dc9a4a2392d033f9d1bd79dd6b6ecea6281a61c.tar.gz |
introduce deferred lambdas
The coercions system allows us to add in lambdas as arguments
to Core and ORM elements without changing them at all. By allowing
the lambda to produce a deterministic cache key where we can also
cheat and yank out literal parameters means we can move towards
having 90% of "baked" functionality in a clearer way right in
Core / ORM.
As a second step, we can have whole statements inside the lambda,
and can then add generation with __add__(), so then we have
100% of "baked" functionality with full support of ad-hoc
literal values.
Adds some more short_selects tests for the moment for comparison.
Other tweaks inside cache key generation as we're trying to
approach a certain level of performance such that we can
remove the use of "baked" from the loader strategies.
As we have not yet closed #4639, however the caching feature
has been fully integrated as of
b0cfa7379cf8513a821a3dbe3028c4965d9f85bd, we will also
add complete caching documentation here and close that issue
as well.
Closes: #4639
Fixes: #5380
Change-Id: If91f61527236fd4d7ae3cad1f24c38be921c90ba
Diffstat (limited to 'lib/sqlalchemy/sql/traversals.py')
-rw-r--r-- | lib/sqlalchemy/sql/traversals.py | 91 |
1 files changed, 43 insertions, 48 deletions
diff --git a/lib/sqlalchemy/sql/traversals.py b/lib/sqlalchemy/sql/traversals.py index 8d01b7ff7..f41480a94 100644 --- a/lib/sqlalchemy/sql/traversals.py +++ b/lib/sqlalchemy/sql/traversals.py @@ -115,45 +115,37 @@ class HasCacheKey(object): in the structures that would affect the SQL string or the type handlers should result in a different cache key. - If a structure cannot produce a useful cache key, it should raise - NotImplementedError, which will result in the entire structure - for which it's part of not being useful as a cache key. - + If a structure cannot produce a useful cache key, the NO_CACHE + symbol should be added to the anon_map and the method should + return None. """ - elements = util.preloaded.sql_elements - idself = id(self) + cls = self.__class__ - if anon_map is not None: - if idself in anon_map: - return (anon_map[idself], self.__class__) - else: - # inline of - # id_ = anon_map[idself] - anon_map[idself] = id_ = str(anon_map.index) - anon_map.index += 1 + if idself in anon_map: + return (anon_map[idself], cls) else: - id_ = None + # inline of + # id_ = anon_map[idself] + anon_map[idself] = id_ = str(anon_map.index) + anon_map.index += 1 try: - dispatcher = self.__class__.__dict__[ - "_generated_cache_key_traversal" - ] + dispatcher = cls.__dict__["_generated_cache_key_traversal"] except KeyError: # most of the dispatchers are generated up front # in sqlalchemy/sql/__init__.py -> # traversals.py-> _preconfigure_traversals(). # this block will generate any remaining dispatchers. - dispatcher = self.__class__._generate_cache_attrs() + dispatcher = cls._generate_cache_attrs() if dispatcher is NO_CACHE: - if anon_map is not None: - anon_map[NO_CACHE] = True + anon_map[NO_CACHE] = True return None - result = (id_, self.__class__) + result = (id_, cls) # inline of _cache_key_traversal_visitor.run_generated_dispatch() @@ -163,15 +155,12 @@ class HasCacheKey(object): if obj is not None: # TODO: see if C code can help here as Python lacks an # efficient switch construct - if meth is CACHE_IN_PLACE: - # cache in place is always going to be a Python - # tuple, dict, list, etc. so we can do a boolean check - if obj: - result += (attrname, obj) - elif meth is STATIC_CACHE_KEY: + + if meth is STATIC_CACHE_KEY: result += (attrname, obj._static_cache_key) elif meth is ANON_NAME: - if elements._anonymous_label in obj.__class__.__mro__: + elements = util.preloaded.sql_elements + if isinstance(obj, elements._anonymous_label): obj = obj.apply_map(anon_map) result += (attrname, obj) elif meth is CALL_GEN_CACHE_KEY: @@ -179,8 +168,14 @@ class HasCacheKey(object): attrname, obj._gen_cache_key(anon_map, bindparams), ) - elif meth is PROPAGATE_ATTRS: - if obj: + + # remaining cache functions are against + # Python tuples, dicts, lists, etc. so we can skip + # if they are empty + elif obj: + if meth is CACHE_IN_PLACE: + result += (attrname, obj) + elif meth is PROPAGATE_ATTRS: result += ( attrname, obj["compile_state_plugin"], @@ -188,16 +183,14 @@ class HasCacheKey(object): anon_map, bindparams ), ) - elif meth is InternalTraversal.dp_annotations_key: - # obj is here is the _annotations dict. however, - # we want to use the memoized cache key version of it. - # for Columns, this should be long lived. For select() - # statements, not so much, but they usually won't have - # annotations. - if obj: + elif meth is InternalTraversal.dp_annotations_key: + # obj is here is the _annotations dict. however, we + # want to use the memoized cache key version of it. for + # Columns, this should be long lived. For select() + # statements, not so much, but they usually won't have + # annotations. result += self._annotations_cache_key - elif meth is InternalTraversal.dp_clauseelement_list: - if obj: + elif meth is InternalTraversal.dp_clauseelement_list: result += ( attrname, tuple( @@ -207,14 +200,7 @@ class HasCacheKey(object): ] ), ) - else: - # note that all the "ClauseElement" standalone cases - # here have been handled by inlines above; so we can - # safely assume the object is a standard list/tuple/dict - # which we can skip if it evaluates to false. - # improvement would be to have this as a flag delivered - # up front in the dispatcher list - if obj: + else: result += meth( attrname, obj, self, anon_map, bindparams ) @@ -384,6 +370,14 @@ class CacheKey(namedtuple("CacheKey", ["key", "bindparams"])): return "CacheKey(key=%s)" % ("\n".join(output),) + def _generate_param_dict(self): + """used for testing""" + + from .compiler import prefix_anon_map + + _anon_map = prefix_anon_map() + return {b.key % _anon_map: b.effective_value for b in self.bindparams} + def _clone(element, **kw): return element._clone() @@ -506,6 +500,7 @@ class _CacheKey(ExtendedInternalTraversal): ): if not obj: return () + return ( attrname, tuple( |