add
This commit is contained in:
@@ -0,0 +1,915 @@
|
||||
# sql/cache_key.py
|
||||
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import zip_longest
|
||||
import typing
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import MutableMapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Protocol
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import Union
|
||||
|
||||
from . import _cache_key_cy
|
||||
from ._cache_key_cy import CacheConst as CacheConst
|
||||
from ._cache_key_cy import CacheTraverseTarget as CacheTraverseTarget
|
||||
from .visitors import anon_map
|
||||
from .visitors import HasTraversalDispatch
|
||||
from .visitors import HasTraverseInternals
|
||||
from .visitors import InternalTraversal
|
||||
from .visitors import prefix_anon_map
|
||||
from .. import util
|
||||
from ..inspection import inspect
|
||||
from ..util import HasMemoized
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from .elements import BindParameter
|
||||
from .elements import ClauseElement
|
||||
from .elements import ColumnElement
|
||||
from .visitors import _TraverseInternalsType
|
||||
from ..engine.interfaces import _CoreSingleExecuteParams
|
||||
|
||||
|
||||
class _CacheKeyTraversalDispatchType(Protocol):
|
||||
def __call__(
|
||||
s,
|
||||
self: HasCacheKey,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
id_: int,
|
||||
cls: Type[HasCacheKey],
|
||||
) -> Optional[Tuple[Any, ...]]: ...
|
||||
|
||||
|
||||
NO_CACHE: Final = CacheConst.NO_CACHE
|
||||
|
||||
_CacheKeyTraversalType = Union[
|
||||
"_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None]
|
||||
]
|
||||
|
||||
|
||||
CACHE_IN_PLACE: Final = CacheTraverseTarget.CACHE_IN_PLACE
|
||||
CALL_GEN_CACHE_KEY: Final = CacheTraverseTarget.CALL_GEN_CACHE_KEY
|
||||
STATIC_CACHE_KEY: Final = CacheTraverseTarget.STATIC_CACHE_KEY
|
||||
PROPAGATE_ATTRS: Final = CacheTraverseTarget.PROPAGATE_ATTRS
|
||||
ANON_NAME: Final = CacheTraverseTarget.ANON_NAME
|
||||
|
||||
|
||||
class HasCacheKey(_cache_key_cy.BaseHasCacheKey):
|
||||
"""Mixin for objects which can produce a cache key.
|
||||
|
||||
This class is usually in a hierarchy that starts with the
|
||||
:class:`.HasTraverseInternals` base, but this is optional. Currently,
|
||||
the class should be able to work on its own without including
|
||||
:class:`.HasTraverseInternals`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.CacheKey`
|
||||
|
||||
:ref:`sql_caching`
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
_cache_key_traversal: _CacheKeyTraversalType = NO_CACHE
|
||||
|
||||
_is_has_cache_key = True
|
||||
|
||||
_hierarchy_supports_caching = True
|
||||
"""private attribute which may be set to False to prevent the
|
||||
inherit_cache warning from being emitted for a hierarchy of subclasses.
|
||||
|
||||
Currently applies to the :class:`.ExecutableDDLElement` hierarchy which
|
||||
does not implement caching.
|
||||
|
||||
"""
|
||||
|
||||
inherit_cache: Optional[bool] = None
|
||||
"""Indicate if this :class:`.HasCacheKey` instance should make use of the
|
||||
cache key generation scheme used by its immediate superclass.
|
||||
|
||||
The attribute defaults to ``None``, which indicates that a construct has
|
||||
not yet taken into account whether or not its appropriate for it to
|
||||
participate in caching; this is functionally equivalent to setting the
|
||||
value to ``False``, except that a warning is also emitted.
|
||||
|
||||
This flag can be set to ``True`` on a particular class, if the SQL that
|
||||
corresponds to the object does not change based on attributes which
|
||||
are local to this class, and not its superclass.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`compilerext_caching` - General guideslines for setting the
|
||||
:attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
|
||||
defined SQL constructs.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
_generated_cache_key_traversal: Any
|
||||
|
||||
@classmethod
|
||||
def _generate_cache_attrs(
|
||||
cls,
|
||||
) -> Union[_CacheKeyTraversalDispatchType, Literal[CacheConst.NO_CACHE]]:
|
||||
"""generate cache key dispatcher for a new class.
|
||||
|
||||
This sets the _generated_cache_key_traversal attribute once called
|
||||
so should only be called once per class.
|
||||
|
||||
"""
|
||||
inherit_cache = cls.__dict__.get("inherit_cache", None)
|
||||
inherit = bool(inherit_cache)
|
||||
|
||||
if inherit:
|
||||
_cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
|
||||
if _cache_key_traversal is None:
|
||||
try:
|
||||
assert issubclass(cls, HasTraverseInternals)
|
||||
_cache_key_traversal = cls._traverse_internals
|
||||
except AttributeError:
|
||||
cls._generated_cache_key_traversal = NO_CACHE
|
||||
return NO_CACHE
|
||||
|
||||
assert _cache_key_traversal is not NO_CACHE, (
|
||||
f"class {cls} has _cache_key_traversal=NO_CACHE, "
|
||||
"which conflicts with inherit_cache=True"
|
||||
)
|
||||
|
||||
# TODO: wouldn't we instead get this from our superclass?
|
||||
# also, our superclass may not have this yet, but in any case,
|
||||
# we'd generate for the superclass that has it. this is a little
|
||||
# more complicated, so for the moment this is a little less
|
||||
# efficient on startup but simpler.
|
||||
return cast(
|
||||
_CacheKeyTraversalDispatchType,
|
||||
_cache_key_traversal_visitor.generate_class_attrs(
|
||||
cls,
|
||||
_cache_key_traversal,
|
||||
"_generated_cache_key_traversal",
|
||||
),
|
||||
)
|
||||
else:
|
||||
_cache_key_traversal = cls.__dict__.get(
|
||||
"_cache_key_traversal", None
|
||||
)
|
||||
if _cache_key_traversal is None:
|
||||
_cache_key_traversal = cls.__dict__.get(
|
||||
"_traverse_internals", None
|
||||
)
|
||||
if _cache_key_traversal is None:
|
||||
cls._generated_cache_key_traversal = NO_CACHE
|
||||
if (
|
||||
inherit_cache is None
|
||||
and cls._hierarchy_supports_caching
|
||||
):
|
||||
util.warn(
|
||||
"Class %s will not make use of SQL compilation "
|
||||
"caching as it does not set the 'inherit_cache' "
|
||||
"attribute to ``True``. This can have "
|
||||
"significant performance implications including "
|
||||
"some performance degradations in comparison to "
|
||||
"prior SQLAlchemy versions. Set this attribute "
|
||||
"to True if this object can make use of the cache "
|
||||
"key generated by the superclass. Alternatively, "
|
||||
"this attribute may be set to False which will "
|
||||
"disable this warning." % (cls.__name__),
|
||||
code="cprf",
|
||||
)
|
||||
return NO_CACHE
|
||||
|
||||
return cast(
|
||||
_CacheKeyTraversalDispatchType,
|
||||
_cache_key_traversal_visitor.generate_class_attrs(
|
||||
cls,
|
||||
_cache_key_traversal,
|
||||
"_generated_cache_key_traversal",
|
||||
),
|
||||
)
|
||||
|
||||
def _generate_cache_key(self) -> Optional[CacheKey]:
|
||||
"""return a cache key.
|
||||
|
||||
The cache key is a tuple which can contain any series of
|
||||
objects that are hashable and also identifies
|
||||
this object uniquely within the presence of a larger SQL expression
|
||||
or statement, for the purposes of caching the resulting query.
|
||||
|
||||
The cache key should be based on the SQL compiled structure that would
|
||||
ultimately be produced. That is, two structures that are composed in
|
||||
exactly the same way should produce the same cache key; any difference
|
||||
in the structures that would affect the SQL string or the type handlers
|
||||
should result in a different cache key.
|
||||
|
||||
The cache key returned by this method is an instance of
|
||||
:class:`.CacheKey`, which consists of a tuple representing the
|
||||
cache key, as well as a list of :class:`.BindParameter` objects
|
||||
which are extracted from the expression. While two expressions
|
||||
that produce identical cache key tuples will themselves generate
|
||||
identical SQL strings, the list of :class:`.BindParameter` objects
|
||||
indicates the bound values which may have different values in
|
||||
each one; these bound parameters must be consulted in order to
|
||||
execute the statement with the correct parameters.
|
||||
|
||||
a :class:`_expression.ClauseElement` structure that does not implement
|
||||
a :meth:`._gen_cache_key` method and does not implement a
|
||||
:attr:`.traverse_internals` attribute will not be cacheable; when
|
||||
such an element is embedded into a larger structure, this method
|
||||
will return None, indicating no cache key is available.
|
||||
|
||||
"""
|
||||
|
||||
bindparams: List[BindParameter[Any]] = []
|
||||
|
||||
_anon_map = anon_map()
|
||||
key = self._gen_cache_key(_anon_map, bindparams)
|
||||
if NO_CACHE in _anon_map:
|
||||
return None
|
||||
else:
|
||||
assert key is not None
|
||||
return CacheKey(
|
||||
key,
|
||||
bindparams,
|
||||
_anon_map.get(CacheConst.PARAMS), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey):
|
||||
pass
|
||||
|
||||
|
||||
class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
|
||||
__slots__ = ()
|
||||
|
||||
@HasMemoized.memoized_instancemethod
|
||||
def _generate_cache_key(self) -> Optional[CacheKey]:
|
||||
return HasCacheKey._generate_cache_key(self)
|
||||
|
||||
|
||||
class SlotsMemoizedHasCacheKey(HasCacheKey, util.MemoizedSlots):
|
||||
__slots__ = ()
|
||||
|
||||
def _memoized_method__generate_cache_key(self) -> Optional[CacheKey]:
|
||||
return HasCacheKey._generate_cache_key(self)
|
||||
|
||||
|
||||
class CacheKey(NamedTuple):
|
||||
"""The key used to identify a SQL statement construct in the
|
||||
SQL compilation cache.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`sql_caching`
|
||||
|
||||
"""
|
||||
|
||||
key: Tuple[Any, ...]
|
||||
bindparams: Sequence[BindParameter[Any]]
|
||||
params: _CoreSingleExecuteParams | None
|
||||
|
||||
# can't set __hash__ attribute because it interferes
|
||||
# with namedtuple
|
||||
# can't use "if not TYPE_CHECKING" because mypy rejects it
|
||||
# inside of a NamedTuple
|
||||
def __hash__(self) -> Optional[int]: # type: ignore[override]
|
||||
"""CacheKey itself is not hashable - hash the .key portion"""
|
||||
return None
|
||||
|
||||
def to_offline_string(
|
||||
self,
|
||||
statement_cache: MutableMapping[Any, str],
|
||||
statement: ClauseElement,
|
||||
parameters: _CoreSingleExecuteParams,
|
||||
) -> str:
|
||||
"""Generate an "offline string" form of this :class:`.CacheKey`
|
||||
|
||||
The "offline string" is basically the string SQL for the
|
||||
statement plus a repr of the bound parameter values in series.
|
||||
Whereas the :class:`.CacheKey` object is dependent on in-memory
|
||||
identities in order to work as a cache key, the "offline" version
|
||||
is suitable for a cache that will work for other processes as well.
|
||||
|
||||
The given ``statement_cache`` is a dictionary-like object where the
|
||||
string form of the statement itself will be cached. This dictionary
|
||||
should be in a longer lived scope in order to reduce the time spent
|
||||
stringifying statements.
|
||||
|
||||
|
||||
"""
|
||||
if self.key not in statement_cache:
|
||||
statement_cache[self.key] = sql_str = str(statement)
|
||||
else:
|
||||
sql_str = statement_cache[self.key]
|
||||
|
||||
if not self.bindparams:
|
||||
param_tuple = tuple(parameters[key] for key in sorted(parameters))
|
||||
else:
|
||||
param_tuple = tuple(
|
||||
parameters.get(bindparam.key, bindparam.value)
|
||||
for bindparam in self.bindparams
|
||||
)
|
||||
|
||||
return repr((sql_str, param_tuple))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return other is not None and bool(self.key == other.key)
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return other is None or not (self.key == other.key)
|
||||
|
||||
@classmethod
|
||||
def _diff_tuples(cls, left: CacheKey, right: CacheKey) -> str:
|
||||
ck1 = CacheKey(left, [], None)
|
||||
ck2 = CacheKey(right, [], None)
|
||||
return ck1._diff(ck2)
|
||||
|
||||
def _whats_different(self, other: CacheKey) -> Iterator[str]:
|
||||
k1 = self.key
|
||||
k2 = other.key
|
||||
|
||||
stack: List[int] = []
|
||||
pickup_index = 0
|
||||
while True:
|
||||
s1, s2 = k1, k2
|
||||
for idx in stack:
|
||||
s1 = s1[idx]
|
||||
s2 = s2[idx]
|
||||
|
||||
for idx, (e1, e2) in enumerate(zip_longest(s1, s2)):
|
||||
if idx < pickup_index:
|
||||
continue
|
||||
if e1 != e2:
|
||||
if isinstance(e1, tuple) and isinstance(e2, tuple):
|
||||
stack.append(idx)
|
||||
break
|
||||
else:
|
||||
yield "key%s[%d]: %s != %s" % (
|
||||
"".join("[%d]" % id_ for id_ in stack),
|
||||
idx,
|
||||
e1,
|
||||
e2,
|
||||
)
|
||||
else:
|
||||
stack.pop(-1)
|
||||
break
|
||||
|
||||
def _diff(self, other: CacheKey) -> str:
|
||||
return ", ".join(self._whats_different(other))
|
||||
|
||||
def __str__(self) -> str:
|
||||
stack: List[Union[Tuple[Any, ...], HasCacheKey]] = [self.key]
|
||||
|
||||
output = []
|
||||
sentinel = object()
|
||||
indent = -1
|
||||
while stack:
|
||||
elem = stack.pop(0)
|
||||
if elem is sentinel:
|
||||
output.append((" " * (indent * 2)) + "),")
|
||||
indent -= 1
|
||||
elif isinstance(elem, tuple):
|
||||
if not elem:
|
||||
output.append((" " * ((indent + 1) * 2)) + "()")
|
||||
else:
|
||||
indent += 1
|
||||
stack = list(elem) + [sentinel] + stack
|
||||
output.append((" " * (indent * 2)) + "(")
|
||||
else:
|
||||
if isinstance(elem, HasCacheKey):
|
||||
repr_ = "<%s object at %s>" % (
|
||||
type(elem).__name__,
|
||||
hex(id(elem)),
|
||||
)
|
||||
else:
|
||||
repr_ = repr(elem)
|
||||
output.append((" " * (indent * 2)) + " " + repr_ + ", ")
|
||||
|
||||
return "CacheKey(key=%s)" % ("\n".join(output),)
|
||||
|
||||
def _generate_param_dict(self) -> Dict[str, Any]:
|
||||
"""used for testing"""
|
||||
|
||||
_anon_map = prefix_anon_map()
|
||||
return {b.key % _anon_map: b.effective_value for b in self.bindparams}
|
||||
|
||||
@util.preload_module("sqlalchemy.sql.elements")
|
||||
def _apply_params_to_element(
|
||||
self, original_cache_key: CacheKey, target_element: ColumnElement[Any]
|
||||
) -> ColumnElement[Any]:
|
||||
if target_element._is_immutable or original_cache_key is self:
|
||||
return target_element
|
||||
|
||||
elements = util.preloaded.sql_elements
|
||||
return elements._OverrideBinds(
|
||||
target_element, self.bindparams, original_cache_key.bindparams
|
||||
)
|
||||
|
||||
|
||||
def _ad_hoc_cache_key_from_args(
|
||||
tokens: Tuple[Any, ...],
|
||||
traverse_args: Iterable[Tuple[str, InternalTraversal]],
|
||||
args: Iterable[Any],
|
||||
) -> Tuple[Any, ...]:
|
||||
"""a quick cache key generator used by reflection.flexi_cache."""
|
||||
bindparams: List[BindParameter[Any]] = []
|
||||
|
||||
_anon_map = anon_map()
|
||||
|
||||
tup = tokens
|
||||
|
||||
for (attrname, sym), arg in zip(traverse_args, args):
|
||||
key = sym.name
|
||||
visit_key = key.replace("dp_", "visit_")
|
||||
|
||||
if arg is None:
|
||||
tup += (attrname, None)
|
||||
continue
|
||||
|
||||
meth = getattr(_cache_key_traversal_visitor, visit_key)
|
||||
if meth is CACHE_IN_PLACE:
|
||||
tup += (attrname, arg)
|
||||
elif meth in (
|
||||
CALL_GEN_CACHE_KEY,
|
||||
STATIC_CACHE_KEY,
|
||||
ANON_NAME,
|
||||
PROPAGATE_ATTRS,
|
||||
):
|
||||
raise NotImplementedError(
|
||||
f"Haven't implemented symbol {meth} for ad-hoc key from args"
|
||||
)
|
||||
else:
|
||||
tup += meth(attrname, arg, None, _anon_map, bindparams)
|
||||
return tup
|
||||
|
||||
|
||||
class _CacheKeyTraversal(
|
||||
HasTraversalDispatch, _cache_key_cy._BaseCacheKeyTraversal
|
||||
):
|
||||
# the dispatch symbols below are resolved at class setup time by
|
||||
# _generate_class_attrs(), which records the handler for each attribute
|
||||
# as an integer "kind" that the compiled traversal switches on directly;
|
||||
# only the symbols that have no such inline handler are left as actual
|
||||
# methods on this class
|
||||
|
||||
visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
|
||||
visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
|
||||
visit_annotations_key = InternalTraversal.dp_annotations_key
|
||||
visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
|
||||
visit_memoized_select_entities = (
|
||||
InternalTraversal.dp_memoized_select_entities
|
||||
)
|
||||
|
||||
visit_string = visit_boolean = visit_operator = visit_plain_obj = (
|
||||
CACHE_IN_PLACE
|
||||
)
|
||||
visit_statement_hint_list = CACHE_IN_PLACE
|
||||
visit_type = STATIC_CACHE_KEY
|
||||
visit_anon_name = ANON_NAME
|
||||
|
||||
visit_propagate_attrs = PROPAGATE_ATTRS
|
||||
|
||||
def visit_compile_state_funcs(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return tuple([(fn.__code__, c_key) for fn, c_key in obj])
|
||||
|
||||
def visit_inspectable(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
|
||||
|
||||
def visit_string_list(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return tuple(obj)
|
||||
|
||||
def visit_multi(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
(
|
||||
obj._gen_cache_key(anon_map, bindparams)
|
||||
if isinstance(obj, HasCacheKey)
|
||||
else obj
|
||||
),
|
||||
)
|
||||
|
||||
def visit_multi_list(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(
|
||||
elem._gen_cache_key(anon_map, bindparams)
|
||||
if isinstance(elem, HasCacheKey)
|
||||
else elem
|
||||
)
|
||||
for elem in obj
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_has_cache_key_tuples(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
tuple(
|
||||
[
|
||||
elem._gen_cache_key(anon_map, bindparams)
|
||||
for elem in tup_elem
|
||||
]
|
||||
)
|
||||
for tup_elem in obj
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_has_cache_key_list(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
|
||||
)
|
||||
|
||||
def visit_executable_options(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
elem._gen_cache_key(anon_map, bindparams)
|
||||
for elem in obj
|
||||
if elem._is_has_cache_key
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
visit_clauseelement_tuples = visit_has_cache_key_tuples
|
||||
|
||||
def visit_fromclause_ordered_set(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
|
||||
)
|
||||
|
||||
def visit_clauseelement_unordered_set(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
cache_keys = [
|
||||
elem._gen_cache_key(anon_map, bindparams) for elem in obj
|
||||
]
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
sorted(cache_keys)
|
||||
), # cache keys all start with (id_, class)
|
||||
)
|
||||
|
||||
def visit_named_ddl_element(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (attrname, obj.name)
|
||||
|
||||
def visit_prefix_sequence(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(clause._gen_cache_key(anon_map, bindparams), strval)
|
||||
for clause, strval in obj
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_setup_join_tuple(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return tuple(
|
||||
[
|
||||
(
|
||||
target._gen_cache_key(anon_map, bindparams),
|
||||
(
|
||||
onclause._gen_cache_key(anon_map, bindparams)
|
||||
if onclause is not None
|
||||
else None
|
||||
),
|
||||
(
|
||||
from_._gen_cache_key(anon_map, bindparams)
|
||||
if from_ is not None
|
||||
else None
|
||||
),
|
||||
tuple([(key, flags[key]) for key in sorted(flags)]),
|
||||
)
|
||||
for (target, onclause, from_, flags) in obj
|
||||
]
|
||||
)
|
||||
|
||||
def visit_table_hint_list(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(
|
||||
clause._gen_cache_key(anon_map, bindparams),
|
||||
dialect_name,
|
||||
text,
|
||||
)
|
||||
for (clause, dialect_name), text in obj.items()
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_plain_dict(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
|
||||
|
||||
def visit_dialect_options(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(
|
||||
dialect_name,
|
||||
tuple(
|
||||
[
|
||||
(key, obj[dialect_name][key])
|
||||
for key in sorted(obj[dialect_name])
|
||||
]
|
||||
),
|
||||
)
|
||||
for dialect_name in sorted(obj)
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_string_clauseelement_dict(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(key, obj[key]._gen_cache_key(anon_map, bindparams))
|
||||
for key in sorted(obj)
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_string_multi_dict(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(
|
||||
key,
|
||||
(
|
||||
value._gen_cache_key(anon_map, bindparams)
|
||||
if isinstance(value, HasCacheKey)
|
||||
else value
|
||||
),
|
||||
)
|
||||
for key, value in [(key, obj[key]) for key in sorted(obj)]
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_fromclause_canonical_column_collection(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
# inlining into the internals of ColumnCollection
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
col._gen_cache_key(anon_map, bindparams)
|
||||
for k, col, _ in obj._collection
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_unknown_structure(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
anon_map[NO_CACHE] = True
|
||||
return ()
|
||||
|
||||
def visit_dml_ordered_values(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(
|
||||
(
|
||||
key._gen_cache_key(anon_map, bindparams)
|
||||
if hasattr(key, "__clause_element__")
|
||||
else key
|
||||
),
|
||||
value._gen_cache_key(anon_map, bindparams),
|
||||
)
|
||||
for key, value in obj
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_dml_values(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
# in py37 we can assume two dictionaries created in the same
|
||||
# insert ordering will retain that sorting
|
||||
return (
|
||||
attrname,
|
||||
tuple(
|
||||
[
|
||||
(
|
||||
(
|
||||
k._gen_cache_key(anon_map, bindparams)
|
||||
if hasattr(k, "__clause_element__")
|
||||
else k
|
||||
),
|
||||
obj[k]._gen_cache_key(anon_map, bindparams),
|
||||
)
|
||||
for k in obj
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def visit_dml_multi_values(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
# multivalues are simply not cacheable right now
|
||||
anon_map[NO_CACHE] = True
|
||||
return ()
|
||||
|
||||
def visit_params(
|
||||
self,
|
||||
attrname: str,
|
||||
obj: Any,
|
||||
parent: Any,
|
||||
anon_map: anon_map,
|
||||
bindparams: List[BindParameter[Any]],
|
||||
) -> Tuple[Any, ...]:
|
||||
if obj:
|
||||
if CacheConst.PARAMS in anon_map:
|
||||
to_set = anon_map[CacheConst.PARAMS] | obj
|
||||
else:
|
||||
to_set = obj
|
||||
anon_map[CacheConst.PARAMS] = to_set
|
||||
return ()
|
||||
|
||||
|
||||
_cache_key_traversal_visitor = _CacheKeyTraversal()
|
||||
Reference in New Issue
Block a user