Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/sql/traversals.py: 30%
639 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1from collections import deque
2from collections import namedtuple
3import itertools
4import operator
6from . import operators
7from .visitors import ExtendedInternalTraversal
8from .visitors import InternalTraversal
9from .. import util
10from ..inspection import inspect
11from ..util import collections_abc
12from ..util import HasMemoized
13from ..util import py37
15SKIP_TRAVERSE = util.symbol("skip_traverse")
16COMPARE_FAILED = False
17COMPARE_SUCCEEDED = True
18NO_CACHE = util.symbol("no_cache")
19CACHE_IN_PLACE = util.symbol("cache_in_place")
20CALL_GEN_CACHE_KEY = util.symbol("call_gen_cache_key")
21STATIC_CACHE_KEY = util.symbol("static_cache_key")
22PROPAGATE_ATTRS = util.symbol("propagate_attrs")
23ANON_NAME = util.symbol("anon_name")
26def compare(obj1, obj2, **kw):
27 if kw.get("use_proxies", False):
28 strategy = ColIdentityComparatorStrategy()
29 else:
30 strategy = TraversalComparatorStrategy()
32 return strategy.compare(obj1, obj2, **kw)
35def _preconfigure_traversals(target_hierarchy):
36 for cls in util.walk_subclasses(target_hierarchy):
37 if hasattr(cls, "_traverse_internals"):
38 cls._generate_cache_attrs()
39 _copy_internals.generate_dispatch(
40 cls,
41 cls._traverse_internals,
42 "_generated_copy_internals_traversal",
43 )
44 _get_children.generate_dispatch(
45 cls,
46 cls._traverse_internals,
47 "_generated_get_children_traversal",
48 )
51class HasCacheKey(object):
52 """Mixin for objects which can produce a cache key.
54 .. seealso::
56 :class:`.CacheKey`
58 :ref:`sql_caching`
60 """
62 _cache_key_traversal = NO_CACHE
64 _is_has_cache_key = True
66 _hierarchy_supports_caching = True
67 """private attribute which may be set to False to prevent the
68 inherit_cache warning from being emitted for a hierarchy of subclasses.
70 Currently applies to the DDLElement hierarchy which does not implement
71 caching.
73 """
75 inherit_cache = None
76 """Indicate if this :class:`.HasCacheKey` instance should make use of the
77 cache key generation scheme used by its immediate superclass.
79 The attribute defaults to ``None``, which indicates that a construct has
80 not yet taken into account whether or not its appropriate for it to
81 participate in caching; this is functionally equivalent to setting the
82 value to ``False``, except that a warning is also emitted.
84 This flag can be set to ``True`` on a particular class, if the SQL that
85 corresponds to the object does not change based on attributes which
86 are local to this class, and not its superclass.
88 .. seealso::
90 :ref:`compilerext_caching` - General guideslines for setting the
91 :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
92 defined SQL constructs.
94 """
96 __slots__ = ()
98 @classmethod
99 def _generate_cache_attrs(cls):
100 """generate cache key dispatcher for a new class.
102 This sets the _generated_cache_key_traversal attribute once called
103 so should only be called once per class.
105 """
106 inherit_cache = cls.__dict__.get("inherit_cache", None)
107 inherit = bool(inherit_cache)
109 if inherit:
110 _cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
111 if _cache_key_traversal is None:
112 try:
113 _cache_key_traversal = cls._traverse_internals
114 except AttributeError:
115 cls._generated_cache_key_traversal = NO_CACHE
116 return NO_CACHE
118 # TODO: wouldn't we instead get this from our superclass?
119 # also, our superclass may not have this yet, but in any case,
120 # we'd generate for the superclass that has it. this is a little
121 # more complicated, so for the moment this is a little less
122 # efficient on startup but simpler.
123 return _cache_key_traversal_visitor.generate_dispatch(
124 cls, _cache_key_traversal, "_generated_cache_key_traversal"
125 )
126 else:
127 _cache_key_traversal = cls.__dict__.get(
128 "_cache_key_traversal", None
129 )
130 if _cache_key_traversal is None:
131 _cache_key_traversal = cls.__dict__.get(
132 "_traverse_internals", None
133 )
134 if _cache_key_traversal is None:
135 cls._generated_cache_key_traversal = NO_CACHE
136 if (
137 inherit_cache is None
138 and cls._hierarchy_supports_caching
139 ):
140 util.warn(
141 "Class %s will not make use of SQL compilation "
142 "caching as it does not set the 'inherit_cache' "
143 "attribute to ``True``. This can have "
144 "significant performance implications including "
145 "some performance degradations in comparison to "
146 "prior SQLAlchemy versions. Set this attribute "
147 "to True if this object can make use of the cache "
148 "key generated by the superclass. Alternatively, "
149 "this attribute may be set to False which will "
150 "disable this warning." % (cls.__name__),
151 code="cprf",
152 )
153 return NO_CACHE
155 return _cache_key_traversal_visitor.generate_dispatch(
156 cls, _cache_key_traversal, "_generated_cache_key_traversal"
157 )
159 @util.preload_module("sqlalchemy.sql.elements")
160 def _gen_cache_key(self, anon_map, bindparams):
161 """return an optional cache key.
163 The cache key is a tuple which can contain any series of
164 objects that are hashable and also identifies
165 this object uniquely within the presence of a larger SQL expression
166 or statement, for the purposes of caching the resulting query.
168 The cache key should be based on the SQL compiled structure that would
169 ultimately be produced. That is, two structures that are composed in
170 exactly the same way should produce the same cache key; any difference
171 in the structures that would affect the SQL string or the type handlers
172 should result in a different cache key.
174 If a structure cannot produce a useful cache key, the NO_CACHE
175 symbol should be added to the anon_map and the method should
176 return None.
178 """
180 idself = id(self)
181 cls = self.__class__
183 if idself in anon_map:
184 return (anon_map[idself], cls)
185 else:
186 # inline of
187 # id_ = anon_map[idself]
188 anon_map[idself] = id_ = str(anon_map.index)
189 anon_map.index += 1
191 try:
192 dispatcher = cls.__dict__["_generated_cache_key_traversal"]
193 except KeyError:
194 # most of the dispatchers are generated up front
195 # in sqlalchemy/sql/__init__.py ->
196 # traversals.py-> _preconfigure_traversals().
197 # this block will generate any remaining dispatchers.
198 dispatcher = cls._generate_cache_attrs()
200 if dispatcher is NO_CACHE:
201 anon_map[NO_CACHE] = True
202 return None
204 result = (id_, cls)
206 # inline of _cache_key_traversal_visitor.run_generated_dispatch()
208 for attrname, obj, meth in dispatcher(
209 self, _cache_key_traversal_visitor
210 ):
211 if obj is not None:
212 # TODO: see if C code can help here as Python lacks an
213 # efficient switch construct
215 if meth is STATIC_CACHE_KEY:
216 sck = obj._static_cache_key
217 if sck is NO_CACHE:
218 anon_map[NO_CACHE] = True
219 return None
220 result += (attrname, sck)
221 elif meth is ANON_NAME:
222 elements = util.preloaded.sql_elements
223 if isinstance(obj, elements._anonymous_label):
224 obj = obj.apply_map(anon_map)
225 result += (attrname, obj)
226 elif meth is CALL_GEN_CACHE_KEY:
227 result += (
228 attrname,
229 obj._gen_cache_key(anon_map, bindparams),
230 )
232 # remaining cache functions are against
233 # Python tuples, dicts, lists, etc. so we can skip
234 # if they are empty
235 elif obj:
236 if meth is CACHE_IN_PLACE:
237 result += (attrname, obj)
238 elif meth is PROPAGATE_ATTRS:
239 result += (
240 attrname,
241 obj["compile_state_plugin"],
242 obj["plugin_subject"]._gen_cache_key(
243 anon_map, bindparams
244 )
245 if obj["plugin_subject"]
246 else None,
247 )
248 elif meth is InternalTraversal.dp_annotations_key:
249 # obj is here is the _annotations dict. Table uses
250 # a memoized version of it. however in other cases,
251 # we generate it given anon_map as we may be from a
252 # Join, Aliased, etc.
253 # see #8790
255 if self._gen_static_annotations_cache_key: # type: ignore # noqa: E501
256 result += self._annotations_cache_key # type: ignore # noqa: E501
257 else:
258 result += self._gen_annotations_cache_key(anon_map) # type: ignore # noqa: E501
259 elif (
260 meth is InternalTraversal.dp_clauseelement_list
261 or meth is InternalTraversal.dp_clauseelement_tuple
262 or meth
263 is InternalTraversal.dp_memoized_select_entities
264 ):
265 result += (
266 attrname,
267 tuple(
268 [
269 elem._gen_cache_key(anon_map, bindparams)
270 for elem in obj
271 ]
272 ),
273 )
274 else:
275 result += meth(
276 attrname, obj, self, anon_map, bindparams
277 )
278 return result
280 def _generate_cache_key(self):
281 """return a cache key.
283 The cache key is a tuple which can contain any series of
284 objects that are hashable and also identifies
285 this object uniquely within the presence of a larger SQL expression
286 or statement, for the purposes of caching the resulting query.
288 The cache key should be based on the SQL compiled structure that would
289 ultimately be produced. That is, two structures that are composed in
290 exactly the same way should produce the same cache key; any difference
291 in the structures that would affect the SQL string or the type handlers
292 should result in a different cache key.
294 The cache key returned by this method is an instance of
295 :class:`.CacheKey`, which consists of a tuple representing the
296 cache key, as well as a list of :class:`.BindParameter` objects
297 which are extracted from the expression. While two expressions
298 that produce identical cache key tuples will themselves generate
299 identical SQL strings, the list of :class:`.BindParameter` objects
300 indicates the bound values which may have different values in
301 each one; these bound parameters must be consulted in order to
302 execute the statement with the correct parameters.
304 a :class:`_expression.ClauseElement` structure that does not implement
305 a :meth:`._gen_cache_key` method and does not implement a
306 :attr:`.traverse_internals` attribute will not be cacheable; when
307 such an element is embedded into a larger structure, this method
308 will return None, indicating no cache key is available.
310 """
312 bindparams = []
314 _anon_map = anon_map()
315 key = self._gen_cache_key(_anon_map, bindparams)
316 if NO_CACHE in _anon_map:
317 return None
318 else:
319 return CacheKey(key, bindparams)
321 @classmethod
322 def _generate_cache_key_for_object(cls, obj):
323 bindparams = []
325 _anon_map = anon_map()
326 key = obj._gen_cache_key(_anon_map, bindparams)
327 if NO_CACHE in _anon_map:
328 return None
329 else:
330 return CacheKey(key, bindparams)
333class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
334 @HasMemoized.memoized_instancemethod
335 def _generate_cache_key(self):
336 return HasCacheKey._generate_cache_key(self)
339class CacheKey(namedtuple("CacheKey", ["key", "bindparams"])):
340 """The key used to identify a SQL statement construct in the
341 SQL compilation cache.
343 .. seealso::
345 :ref:`sql_caching`
347 """
349 def __hash__(self):
350 """CacheKey itself is not hashable - hash the .key portion"""
352 return None
354 def to_offline_string(self, statement_cache, statement, parameters):
355 """Generate an "offline string" form of this :class:`.CacheKey`
357 The "offline string" is basically the string SQL for the
358 statement plus a repr of the bound parameter values in series.
359 Whereas the :class:`.CacheKey` object is dependent on in-memory
360 identities in order to work as a cache key, the "offline" version
361 is suitable for a cache that will work for other processes as well.
363 The given ``statement_cache`` is a dictionary-like object where the
364 string form of the statement itself will be cached. This dictionary
365 should be in a longer lived scope in order to reduce the time spent
366 stringifying statements.
369 """
370 if self.key not in statement_cache:
371 statement_cache[self.key] = sql_str = str(statement)
372 else:
373 sql_str = statement_cache[self.key]
375 if not self.bindparams:
376 param_tuple = tuple(parameters[key] for key in sorted(parameters))
377 else:
378 param_tuple = tuple(
379 parameters.get(bindparam.key, bindparam.value)
380 for bindparam in self.bindparams
381 )
383 return repr((sql_str, param_tuple))
385 def __eq__(self, other):
386 return self.key == other.key
388 @classmethod
389 def _diff_tuples(cls, left, right):
390 ck1 = CacheKey(left, [])
391 ck2 = CacheKey(right, [])
392 return ck1._diff(ck2)
394 def _whats_different(self, other):
396 k1 = self.key
397 k2 = other.key
399 stack = []
400 pickup_index = 0
401 while True:
402 s1, s2 = k1, k2
403 for idx in stack:
404 s1 = s1[idx]
405 s2 = s2[idx]
407 for idx, (e1, e2) in enumerate(util.zip_longest(s1, s2)):
408 if idx < pickup_index:
409 continue
410 if e1 != e2:
411 if isinstance(e1, tuple) and isinstance(e2, tuple):
412 stack.append(idx)
413 break
414 else:
415 yield "key%s[%d]: %s != %s" % (
416 "".join("[%d]" % id_ for id_ in stack),
417 idx,
418 e1,
419 e2,
420 )
421 else:
422 pickup_index = stack.pop(-1)
423 break
425 def _diff(self, other):
426 return ", ".join(self._whats_different(other))
428 def __str__(self):
429 stack = [self.key]
431 output = []
432 sentinel = object()
433 indent = -1
434 while stack:
435 elem = stack.pop(0)
436 if elem is sentinel:
437 output.append((" " * (indent * 2)) + "),")
438 indent -= 1
439 elif isinstance(elem, tuple):
440 if not elem:
441 output.append((" " * ((indent + 1) * 2)) + "()")
442 else:
443 indent += 1
444 stack = list(elem) + [sentinel] + stack
445 output.append((" " * (indent * 2)) + "(")
446 else:
447 if isinstance(elem, HasCacheKey):
448 repr_ = "<%s object at %s>" % (
449 type(elem).__name__,
450 hex(id(elem)),
451 )
452 else:
453 repr_ = repr(elem)
454 output.append((" " * (indent * 2)) + " " + repr_ + ", ")
456 return "CacheKey(key=%s)" % ("\n".join(output),)
458 def _generate_param_dict(self):
459 """used for testing"""
461 from .compiler import prefix_anon_map
463 _anon_map = prefix_anon_map()
464 return {b.key % _anon_map: b.effective_value for b in self.bindparams}
466 def _apply_params_to_element(self, original_cache_key, target_element):
467 translate = {
468 k.key: v.value
469 for k, v in zip(original_cache_key.bindparams, self.bindparams)
470 }
472 return target_element.params(translate)
475def _clone(element, **kw):
476 return element._clone()
479class _CacheKey(ExtendedInternalTraversal):
480 # very common elements are inlined into the main _get_cache_key() method
481 # to produce a dramatic savings in Python function call overhead
483 visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
484 visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
485 visit_annotations_key = InternalTraversal.dp_annotations_key
486 visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
487 visit_memoized_select_entities = (
488 InternalTraversal.dp_memoized_select_entities
489 )
491 visit_string = (
492 visit_boolean
493 ) = visit_operator = visit_plain_obj = CACHE_IN_PLACE
494 visit_statement_hint_list = CACHE_IN_PLACE
495 visit_type = STATIC_CACHE_KEY
496 visit_anon_name = ANON_NAME
498 visit_propagate_attrs = PROPAGATE_ATTRS
500 def visit_with_context_options(
501 self, attrname, obj, parent, anon_map, bindparams
502 ):
503 return tuple((fn.__code__, c_key) for fn, c_key in obj)
505 def visit_inspectable(self, attrname, obj, parent, anon_map, bindparams):
506 return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
508 def visit_string_list(self, attrname, obj, parent, anon_map, bindparams):
509 return tuple(obj)
511 def visit_multi(self, attrname, obj, parent, anon_map, bindparams):
512 return (
513 attrname,
514 obj._gen_cache_key(anon_map, bindparams)
515 if isinstance(obj, HasCacheKey)
516 else obj,
517 )
519 def visit_multi_list(self, attrname, obj, parent, anon_map, bindparams):
520 return (
521 attrname,
522 tuple(
523 elem._gen_cache_key(anon_map, bindparams)
524 if isinstance(elem, HasCacheKey)
525 else elem
526 for elem in obj
527 ),
528 )
530 def visit_has_cache_key_tuples(
531 self, attrname, obj, parent, anon_map, bindparams
532 ):
533 if not obj:
534 return ()
535 return (
536 attrname,
537 tuple(
538 tuple(
539 elem._gen_cache_key(anon_map, bindparams)
540 for elem in tup_elem
541 )
542 for tup_elem in obj
543 ),
544 )
546 def visit_has_cache_key_list(
547 self, attrname, obj, parent, anon_map, bindparams
548 ):
549 if not obj:
550 return ()
551 return (
552 attrname,
553 tuple(elem._gen_cache_key(anon_map, bindparams) for elem in obj),
554 )
556 def visit_executable_options(
557 self, attrname, obj, parent, anon_map, bindparams
558 ):
559 if not obj:
560 return ()
561 return (
562 attrname,
563 tuple(
564 elem._gen_cache_key(anon_map, bindparams)
565 for elem in obj
566 if elem._is_has_cache_key
567 ),
568 )
570 def visit_inspectable_list(
571 self, attrname, obj, parent, anon_map, bindparams
572 ):
573 return self.visit_has_cache_key_list(
574 attrname, [inspect(o) for o in obj], parent, anon_map, bindparams
575 )
577 def visit_clauseelement_tuples(
578 self, attrname, obj, parent, anon_map, bindparams
579 ):
580 return self.visit_has_cache_key_tuples(
581 attrname, obj, parent, anon_map, bindparams
582 )
584 def visit_fromclause_ordered_set(
585 self, attrname, obj, parent, anon_map, bindparams
586 ):
587 if not obj:
588 return ()
589 return (
590 attrname,
591 tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
592 )
594 def visit_clauseelement_unordered_set(
595 self, attrname, obj, parent, anon_map, bindparams
596 ):
597 if not obj:
598 return ()
599 cache_keys = [
600 elem._gen_cache_key(anon_map, bindparams) for elem in obj
601 ]
602 return (
603 attrname,
604 tuple(
605 sorted(cache_keys)
606 ), # cache keys all start with (id_, class)
607 )
609 def visit_named_ddl_element(
610 self, attrname, obj, parent, anon_map, bindparams
611 ):
612 return (attrname, obj.name)
614 def visit_prefix_sequence(
615 self, attrname, obj, parent, anon_map, bindparams
616 ):
617 if not obj:
618 return ()
620 return (
621 attrname,
622 tuple(
623 [
624 (clause._gen_cache_key(anon_map, bindparams), strval)
625 for clause, strval in obj
626 ]
627 ),
628 )
630 def visit_setup_join_tuple(
631 self, attrname, obj, parent, anon_map, bindparams
632 ):
633 is_legacy = "legacy" in attrname
635 return tuple(
636 (
637 target
638 if is_legacy and isinstance(target, str)
639 else target._gen_cache_key(anon_map, bindparams),
640 onclause
641 if is_legacy and isinstance(onclause, str)
642 else onclause._gen_cache_key(anon_map, bindparams)
643 if onclause is not None
644 else None,
645 from_._gen_cache_key(anon_map, bindparams)
646 if from_ is not None
647 else None,
648 tuple([(key, flags[key]) for key in sorted(flags)]),
649 )
650 for (target, onclause, from_, flags) in obj
651 )
653 def visit_table_hint_list(
654 self, attrname, obj, parent, anon_map, bindparams
655 ):
656 if not obj:
657 return ()
659 return (
660 attrname,
661 tuple(
662 [
663 (
664 clause._gen_cache_key(anon_map, bindparams),
665 dialect_name,
666 text,
667 )
668 for (clause, dialect_name), text in obj.items()
669 ]
670 ),
671 )
673 def visit_plain_dict(self, attrname, obj, parent, anon_map, bindparams):
674 return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
676 def visit_dialect_options(
677 self, attrname, obj, parent, anon_map, bindparams
678 ):
679 return (
680 attrname,
681 tuple(
682 (
683 dialect_name,
684 tuple(
685 [
686 (key, obj[dialect_name][key])
687 for key in sorted(obj[dialect_name])
688 ]
689 ),
690 )
691 for dialect_name in sorted(obj)
692 ),
693 )
695 def visit_string_clauseelement_dict(
696 self, attrname, obj, parent, anon_map, bindparams
697 ):
698 return (
699 attrname,
700 tuple(
701 (key, obj[key]._gen_cache_key(anon_map, bindparams))
702 for key in sorted(obj)
703 ),
704 )
706 def visit_string_multi_dict(
707 self, attrname, obj, parent, anon_map, bindparams
708 ):
709 return (
710 attrname,
711 tuple(
712 (
713 key,
714 value._gen_cache_key(anon_map, bindparams)
715 if isinstance(value, HasCacheKey)
716 else value,
717 )
718 for key, value in [(key, obj[key]) for key in sorted(obj)]
719 ),
720 )
722 def visit_fromclause_canonical_column_collection(
723 self, attrname, obj, parent, anon_map, bindparams
724 ):
725 # inlining into the internals of ColumnCollection
726 return (
727 attrname,
728 tuple(
729 col._gen_cache_key(anon_map, bindparams)
730 for k, col in obj._collection
731 ),
732 )
734 def visit_unknown_structure(
735 self, attrname, obj, parent, anon_map, bindparams
736 ):
737 anon_map[NO_CACHE] = True
738 return ()
740 def visit_dml_ordered_values(
741 self, attrname, obj, parent, anon_map, bindparams
742 ):
743 return (
744 attrname,
745 tuple(
746 (
747 key._gen_cache_key(anon_map, bindparams)
748 if hasattr(key, "__clause_element__")
749 else key,
750 value._gen_cache_key(anon_map, bindparams),
751 )
752 for key, value in obj
753 ),
754 )
756 def visit_dml_values(self, attrname, obj, parent, anon_map, bindparams):
757 if py37:
758 # in py37 we can assume two dictionaries created in the same
759 # insert ordering will retain that sorting
760 return (
761 attrname,
762 tuple(
763 (
764 k._gen_cache_key(anon_map, bindparams)
765 if hasattr(k, "__clause_element__")
766 else k,
767 obj[k]._gen_cache_key(anon_map, bindparams),
768 )
769 for k in obj
770 ),
771 )
772 else:
773 expr_values = {k for k in obj if hasattr(k, "__clause_element__")}
774 if expr_values:
775 # expr values can't be sorted deterministically right now,
776 # so no cache
777 anon_map[NO_CACHE] = True
778 return ()
780 str_values = expr_values.symmetric_difference(obj)
782 return (
783 attrname,
784 tuple(
785 (k, obj[k]._gen_cache_key(anon_map, bindparams))
786 for k in sorted(str_values)
787 ),
788 )
790 def visit_dml_multi_values(
791 self, attrname, obj, parent, anon_map, bindparams
792 ):
793 # multivalues are simply not cacheable right now
794 anon_map[NO_CACHE] = True
795 return ()
798_cache_key_traversal_visitor = _CacheKey()
801class HasCopyInternals(object):
802 def _clone(self, **kw):
803 raise NotImplementedError()
805 def _copy_internals(self, omit_attrs=(), **kw):
806 """Reassign internal elements to be clones of themselves.
808 Called during a copy-and-traverse operation on newly
809 shallow-copied elements to create a deep copy.
811 The given clone function should be used, which may be applying
812 additional transformations to the element (i.e. replacement
813 traversal, cloned traversal, annotations).
815 """
817 try:
818 traverse_internals = self._traverse_internals
819 except AttributeError:
820 # user-defined classes may not have a _traverse_internals
821 return
823 for attrname, obj, meth in _copy_internals.run_generated_dispatch(
824 self, traverse_internals, "_generated_copy_internals_traversal"
825 ):
826 if attrname in omit_attrs:
827 continue
829 if obj is not None:
830 result = meth(attrname, self, obj, **kw)
831 if result is not None:
832 setattr(self, attrname, result)
835class _CopyInternals(InternalTraversal):
836 """Generate a _copy_internals internal traversal dispatch for classes
837 with a _traverse_internals collection."""
839 def visit_clauseelement(
840 self, attrname, parent, element, clone=_clone, **kw
841 ):
842 return clone(element, **kw)
844 def visit_clauseelement_list(
845 self, attrname, parent, element, clone=_clone, **kw
846 ):
847 return [clone(clause, **kw) for clause in element]
849 def visit_clauseelement_tuple(
850 self, attrname, parent, element, clone=_clone, **kw
851 ):
852 return tuple([clone(clause, **kw) for clause in element])
854 def visit_executable_options(
855 self, attrname, parent, element, clone=_clone, **kw
856 ):
857 return tuple([clone(clause, **kw) for clause in element])
859 def visit_clauseelement_unordered_set(
860 self, attrname, parent, element, clone=_clone, **kw
861 ):
862 return {clone(clause, **kw) for clause in element}
864 def visit_clauseelement_tuples(
865 self, attrname, parent, element, clone=_clone, **kw
866 ):
867 return [
868 tuple(clone(tup_elem, **kw) for tup_elem in elem)
869 for elem in element
870 ]
872 def visit_string_clauseelement_dict(
873 self, attrname, parent, element, clone=_clone, **kw
874 ):
875 return dict(
876 (key, clone(value, **kw)) for key, value in element.items()
877 )
879 def visit_setup_join_tuple(
880 self, attrname, parent, element, clone=_clone, **kw
881 ):
882 return tuple(
883 (
884 clone(target, **kw) if target is not None else None,
885 clone(onclause, **kw) if onclause is not None else None,
886 clone(from_, **kw) if from_ is not None else None,
887 flags,
888 )
889 for (target, onclause, from_, flags) in element
890 )
892 def visit_memoized_select_entities(self, attrname, parent, element, **kw):
893 return self.visit_clauseelement_tuple(attrname, parent, element, **kw)
895 def visit_dml_ordered_values(
896 self, attrname, parent, element, clone=_clone, **kw
897 ):
898 # sequence of 2-tuples
899 return [
900 (
901 clone(key, **kw)
902 if hasattr(key, "__clause_element__")
903 else key,
904 clone(value, **kw),
905 )
906 for key, value in element
907 ]
909 def visit_dml_values(self, attrname, parent, element, clone=_clone, **kw):
910 return {
911 (
912 clone(key, **kw) if hasattr(key, "__clause_element__") else key
913 ): clone(value, **kw)
914 for key, value in element.items()
915 }
917 def visit_dml_multi_values(
918 self, attrname, parent, element, clone=_clone, **kw
919 ):
920 # sequence of sequences, each sequence contains a list/dict/tuple
922 def copy(elem):
923 if isinstance(elem, (list, tuple)):
924 return [
925 clone(value, **kw)
926 if hasattr(value, "__clause_element__")
927 else value
928 for value in elem
929 ]
930 elif isinstance(elem, dict):
931 return {
932 (
933 clone(key, **kw)
934 if hasattr(key, "__clause_element__")
935 else key
936 ): (
937 clone(value, **kw)
938 if hasattr(value, "__clause_element__")
939 else value
940 )
941 for key, value in elem.items()
942 }
943 else:
944 # TODO: use abc classes
945 assert False
947 return [
948 [copy(sub_element) for sub_element in sequence]
949 for sequence in element
950 ]
952 def visit_propagate_attrs(
953 self, attrname, parent, element, clone=_clone, **kw
954 ):
955 return element
958_copy_internals = _CopyInternals()
961def _flatten_clauseelement(element):
962 while hasattr(element, "__clause_element__") and not getattr(
963 element, "is_clause_element", False
964 ):
965 element = element.__clause_element__()
967 return element
970class _GetChildren(InternalTraversal):
971 """Generate a _children_traversal internal traversal dispatch for classes
972 with a _traverse_internals collection."""
974 def visit_has_cache_key(self, element, **kw):
975 # the GetChildren traversal refers explicitly to ClauseElement
976 # structures. Within these, a plain HasCacheKey is not a
977 # ClauseElement, so don't include these.
978 return ()
980 def visit_clauseelement(self, element, **kw):
981 return (element,)
983 def visit_clauseelement_list(self, element, **kw):
984 return element
986 def visit_clauseelement_tuple(self, element, **kw):
987 return element
989 def visit_clauseelement_tuples(self, element, **kw):
990 return itertools.chain.from_iterable(element)
992 def visit_fromclause_canonical_column_collection(self, element, **kw):
993 return ()
995 def visit_string_clauseelement_dict(self, element, **kw):
996 return element.values()
998 def visit_fromclause_ordered_set(self, element, **kw):
999 return element
1001 def visit_clauseelement_unordered_set(self, element, **kw):
1002 return element
1004 def visit_setup_join_tuple(self, element, **kw):
1005 for (target, onclause, from_, flags) in element:
1006 if from_ is not None:
1007 yield from_
1009 if not isinstance(target, str):
1010 yield _flatten_clauseelement(target)
1012 if onclause is not None and not isinstance(onclause, str):
1013 yield _flatten_clauseelement(onclause)
1015 def visit_memoized_select_entities(self, element, **kw):
1016 return self.visit_clauseelement_tuple(element, **kw)
1018 def visit_dml_ordered_values(self, element, **kw):
1019 for k, v in element:
1020 if hasattr(k, "__clause_element__"):
1021 yield k
1022 yield v
1024 def visit_dml_values(self, element, **kw):
1025 expr_values = {k for k in element if hasattr(k, "__clause_element__")}
1026 str_values = expr_values.symmetric_difference(element)
1028 for k in sorted(str_values):
1029 yield element[k]
1030 for k in expr_values:
1031 yield k
1032 yield element[k]
1034 def visit_dml_multi_values(self, element, **kw):
1035 return ()
1037 def visit_propagate_attrs(self, element, **kw):
1038 return ()
1041_get_children = _GetChildren()
1044@util.preload_module("sqlalchemy.sql.elements")
1045def _resolve_name_for_compare(element, name, anon_map, **kw):
1046 if isinstance(name, util.preloaded.sql_elements._anonymous_label):
1047 name = name.apply_map(anon_map)
1049 return name
1052class anon_map(dict):
1053 """A map that creates new keys for missing key access.
1055 Produces an incrementing sequence given a series of unique keys.
1057 This is similar to the compiler prefix_anon_map class although simpler.
1059 Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
1060 is otherwise usually used for this type of operation.
1062 """
1064 def __init__(self):
1065 self.index = 0
1067 def __missing__(self, key):
1068 self[key] = val = str(self.index)
1069 self.index += 1
1070 return val
1073class TraversalComparatorStrategy(InternalTraversal, util.MemoizedSlots):
1074 __slots__ = "stack", "cache", "anon_map"
1076 def __init__(self):
1077 self.stack = deque()
1078 self.cache = set()
1080 def _memoized_attr_anon_map(self):
1081 return (anon_map(), anon_map())
1083 def compare(self, obj1, obj2, **kw):
1084 stack = self.stack
1085 cache = self.cache
1087 compare_annotations = kw.get("compare_annotations", False)
1089 stack.append((obj1, obj2))
1091 while stack:
1092 left, right = stack.popleft()
1094 if left is right:
1095 continue
1096 elif left is None or right is None:
1097 # we know they are different so no match
1098 return False
1099 elif (left, right) in cache:
1100 continue
1101 cache.add((left, right))
1103 visit_name = left.__visit_name__
1104 if visit_name != right.__visit_name__:
1105 return False
1107 meth = getattr(self, "compare_%s" % visit_name, None)
1109 if meth:
1110 attributes_compared = meth(left, right, **kw)
1111 if attributes_compared is COMPARE_FAILED:
1112 return False
1113 elif attributes_compared is SKIP_TRAVERSE:
1114 continue
1116 # attributes_compared is returned as a list of attribute
1117 # names that were "handled" by the comparison method above.
1118 # remaining attribute names in the _traverse_internals
1119 # will be compared.
1120 else:
1121 attributes_compared = ()
1123 for (
1124 (left_attrname, left_visit_sym),
1125 (right_attrname, right_visit_sym),
1126 ) in util.zip_longest(
1127 left._traverse_internals,
1128 right._traverse_internals,
1129 fillvalue=(None, None),
1130 ):
1131 if not compare_annotations and (
1132 (left_attrname == "_annotations")
1133 or (right_attrname == "_annotations")
1134 ):
1135 continue
1137 if (
1138 left_attrname != right_attrname
1139 or left_visit_sym is not right_visit_sym
1140 ):
1141 return False
1142 elif left_attrname in attributes_compared:
1143 continue
1145 dispatch = self.dispatch(left_visit_sym)
1146 left_child = operator.attrgetter(left_attrname)(left)
1147 right_child = operator.attrgetter(right_attrname)(right)
1148 if left_child is None:
1149 if right_child is not None:
1150 return False
1151 else:
1152 continue
1154 comparison = dispatch(
1155 left_attrname, left, left_child, right, right_child, **kw
1156 )
1157 if comparison is COMPARE_FAILED:
1158 return False
1160 return True
1162 def compare_inner(self, obj1, obj2, **kw):
1163 comparator = self.__class__()
1164 return comparator.compare(obj1, obj2, **kw)
1166 def visit_has_cache_key(
1167 self, attrname, left_parent, left, right_parent, right, **kw
1168 ):
1169 if left._gen_cache_key(self.anon_map[0], []) != right._gen_cache_key(
1170 self.anon_map[1], []
1171 ):
1172 return COMPARE_FAILED
1174 def visit_propagate_attrs(
1175 self, attrname, left_parent, left, right_parent, right, **kw
1176 ):
1177 return self.compare_inner(
1178 left.get("plugin_subject", None), right.get("plugin_subject", None)
1179 )
1181 def visit_has_cache_key_list(
1182 self, attrname, left_parent, left, right_parent, right, **kw
1183 ):
1184 for l, r in util.zip_longest(left, right, fillvalue=None):
1185 if l._gen_cache_key(self.anon_map[0], []) != r._gen_cache_key(
1186 self.anon_map[1], []
1187 ):
1188 return COMPARE_FAILED
1190 def visit_executable_options(
1191 self, attrname, left_parent, left, right_parent, right, **kw
1192 ):
1193 for l, r in util.zip_longest(left, right, fillvalue=None):
1194 if (
1195 l._gen_cache_key(self.anon_map[0], [])
1196 if l._is_has_cache_key
1197 else l
1198 ) != (
1199 r._gen_cache_key(self.anon_map[1], [])
1200 if r._is_has_cache_key
1201 else r
1202 ):
1203 return COMPARE_FAILED
1205 def visit_clauseelement(
1206 self, attrname, left_parent, left, right_parent, right, **kw
1207 ):
1208 self.stack.append((left, right))
1210 def visit_fromclause_canonical_column_collection(
1211 self, attrname, left_parent, left, right_parent, right, **kw
1212 ):
1213 for lcol, rcol in util.zip_longest(left, right, fillvalue=None):
1214 self.stack.append((lcol, rcol))
1216 def visit_fromclause_derived_column_collection(
1217 self, attrname, left_parent, left, right_parent, right, **kw
1218 ):
1219 pass
1221 def visit_string_clauseelement_dict(
1222 self, attrname, left_parent, left, right_parent, right, **kw
1223 ):
1224 for lstr, rstr in util.zip_longest(
1225 sorted(left), sorted(right), fillvalue=None
1226 ):
1227 if lstr != rstr:
1228 return COMPARE_FAILED
1229 self.stack.append((left[lstr], right[rstr]))
1231 def visit_clauseelement_tuples(
1232 self, attrname, left_parent, left, right_parent, right, **kw
1233 ):
1234 for ltup, rtup in util.zip_longest(left, right, fillvalue=None):
1235 if ltup is None or rtup is None:
1236 return COMPARE_FAILED
1238 for l, r in util.zip_longest(ltup, rtup, fillvalue=None):
1239 self.stack.append((l, r))
1241 def visit_clauseelement_list(
1242 self, attrname, left_parent, left, right_parent, right, **kw
1243 ):
1244 for l, r in util.zip_longest(left, right, fillvalue=None):
1245 self.stack.append((l, r))
1247 def visit_clauseelement_tuple(
1248 self, attrname, left_parent, left, right_parent, right, **kw
1249 ):
1250 for l, r in util.zip_longest(left, right, fillvalue=None):
1251 self.stack.append((l, r))
1253 def _compare_unordered_sequences(self, seq1, seq2, **kw):
1254 if seq1 is None:
1255 return seq2 is None
1257 completed = set()
1258 for clause in seq1:
1259 for other_clause in set(seq2).difference(completed):
1260 if self.compare_inner(clause, other_clause, **kw):
1261 completed.add(other_clause)
1262 break
1263 return len(completed) == len(seq1) == len(seq2)
1265 def visit_clauseelement_unordered_set(
1266 self, attrname, left_parent, left, right_parent, right, **kw
1267 ):
1268 return self._compare_unordered_sequences(left, right, **kw)
1270 def visit_fromclause_ordered_set(
1271 self, attrname, left_parent, left, right_parent, right, **kw
1272 ):
1273 for l, r in util.zip_longest(left, right, fillvalue=None):
1274 self.stack.append((l, r))
1276 def visit_string(
1277 self, attrname, left_parent, left, right_parent, right, **kw
1278 ):
1279 return left == right
1281 def visit_string_list(
1282 self, attrname, left_parent, left, right_parent, right, **kw
1283 ):
1284 return left == right
1286 def visit_anon_name(
1287 self, attrname, left_parent, left, right_parent, right, **kw
1288 ):
1289 return _resolve_name_for_compare(
1290 left_parent, left, self.anon_map[0], **kw
1291 ) == _resolve_name_for_compare(
1292 right_parent, right, self.anon_map[1], **kw
1293 )
1295 def visit_boolean(
1296 self, attrname, left_parent, left, right_parent, right, **kw
1297 ):
1298 return left == right
1300 def visit_operator(
1301 self, attrname, left_parent, left, right_parent, right, **kw
1302 ):
1303 return left is right
1305 def visit_type(
1306 self, attrname, left_parent, left, right_parent, right, **kw
1307 ):
1308 return left._compare_type_affinity(right)
1310 def visit_plain_dict(
1311 self, attrname, left_parent, left, right_parent, right, **kw
1312 ):
1313 return left == right
1315 def visit_dialect_options(
1316 self, attrname, left_parent, left, right_parent, right, **kw
1317 ):
1318 return left == right
1320 def visit_annotations_key(
1321 self, attrname, left_parent, left, right_parent, right, **kw
1322 ):
1323 if left and right:
1324 return (
1325 left_parent._annotations_cache_key
1326 == right_parent._annotations_cache_key
1327 )
1328 else:
1329 return left == right
1331 def visit_with_context_options(
1332 self, attrname, left_parent, left, right_parent, right, **kw
1333 ):
1334 return tuple((fn.__code__, c_key) for fn, c_key in left) == tuple(
1335 (fn.__code__, c_key) for fn, c_key in right
1336 )
1338 def visit_plain_obj(
1339 self, attrname, left_parent, left, right_parent, right, **kw
1340 ):
1341 return left == right
1343 def visit_named_ddl_element(
1344 self, attrname, left_parent, left, right_parent, right, **kw
1345 ):
1346 if left is None:
1347 if right is not None:
1348 return COMPARE_FAILED
1350 return left.name == right.name
1352 def visit_prefix_sequence(
1353 self, attrname, left_parent, left, right_parent, right, **kw
1354 ):
1355 for (l_clause, l_str), (r_clause, r_str) in util.zip_longest(
1356 left, right, fillvalue=(None, None)
1357 ):
1358 if l_str != r_str:
1359 return COMPARE_FAILED
1360 else:
1361 self.stack.append((l_clause, r_clause))
1363 def visit_setup_join_tuple(
1364 self, attrname, left_parent, left, right_parent, right, **kw
1365 ):
1366 # TODO: look at attrname for "legacy_join" and use different structure
1367 for (
1368 (l_target, l_onclause, l_from, l_flags),
1369 (r_target, r_onclause, r_from, r_flags),
1370 ) in util.zip_longest(left, right, fillvalue=(None, None, None, None)):
1371 if l_flags != r_flags:
1372 return COMPARE_FAILED
1373 self.stack.append((l_target, r_target))
1374 self.stack.append((l_onclause, r_onclause))
1375 self.stack.append((l_from, r_from))
1377 def visit_memoized_select_entities(
1378 self, attrname, left_parent, left, right_parent, right, **kw
1379 ):
1380 return self.visit_clauseelement_tuple(
1381 attrname, left_parent, left, right_parent, right, **kw
1382 )
1384 def visit_table_hint_list(
1385 self, attrname, left_parent, left, right_parent, right, **kw
1386 ):
1387 left_keys = sorted(left, key=lambda elem: (elem[0].fullname, elem[1]))
1388 right_keys = sorted(
1389 right, key=lambda elem: (elem[0].fullname, elem[1])
1390 )
1391 for (ltable, ldialect), (rtable, rdialect) in util.zip_longest(
1392 left_keys, right_keys, fillvalue=(None, None)
1393 ):
1394 if ldialect != rdialect:
1395 return COMPARE_FAILED
1396 elif left[(ltable, ldialect)] != right[(rtable, rdialect)]:
1397 return COMPARE_FAILED
1398 else:
1399 self.stack.append((ltable, rtable))
1401 def visit_statement_hint_list(
1402 self, attrname, left_parent, left, right_parent, right, **kw
1403 ):
1404 return left == right
1406 def visit_unknown_structure(
1407 self, attrname, left_parent, left, right_parent, right, **kw
1408 ):
1409 raise NotImplementedError()
1411 def visit_dml_ordered_values(
1412 self, attrname, left_parent, left, right_parent, right, **kw
1413 ):
1414 # sequence of tuple pairs
1416 for (lk, lv), (rk, rv) in util.zip_longest(
1417 left, right, fillvalue=(None, None)
1418 ):
1419 if not self._compare_dml_values_or_ce(lk, rk, **kw):
1420 return COMPARE_FAILED
1422 def _compare_dml_values_or_ce(self, lv, rv, **kw):
1423 lvce = hasattr(lv, "__clause_element__")
1424 rvce = hasattr(rv, "__clause_element__")
1425 if lvce != rvce:
1426 return False
1427 elif lvce and not self.compare_inner(lv, rv, **kw):
1428 return False
1429 elif not lvce and lv != rv:
1430 return False
1431 elif not self.compare_inner(lv, rv, **kw):
1432 return False
1434 return True
1436 def visit_dml_values(
1437 self, attrname, left_parent, left, right_parent, right, **kw
1438 ):
1439 if left is None or right is None or len(left) != len(right):
1440 return COMPARE_FAILED
1442 if isinstance(left, collections_abc.Sequence):
1443 for lv, rv in zip(left, right):
1444 if not self._compare_dml_values_or_ce(lv, rv, **kw):
1445 return COMPARE_FAILED
1446 elif isinstance(right, collections_abc.Sequence):
1447 return COMPARE_FAILED
1448 elif py37:
1449 # dictionaries guaranteed to support insert ordering in
1450 # py37 so that we can compare the keys in order. without
1451 # this, we can't compare SQL expression keys because we don't
1452 # know which key is which
1453 for (lk, lv), (rk, rv) in zip(left.items(), right.items()):
1454 if not self._compare_dml_values_or_ce(lk, rk, **kw):
1455 return COMPARE_FAILED
1456 if not self._compare_dml_values_or_ce(lv, rv, **kw):
1457 return COMPARE_FAILED
1458 else:
1459 for lk in left:
1460 lv = left[lk]
1462 if lk not in right:
1463 return COMPARE_FAILED
1464 rv = right[lk]
1466 if not self._compare_dml_values_or_ce(lv, rv, **kw):
1467 return COMPARE_FAILED
1469 def visit_dml_multi_values(
1470 self, attrname, left_parent, left, right_parent, right, **kw
1471 ):
1472 for lseq, rseq in util.zip_longest(left, right, fillvalue=None):
1473 if lseq is None or rseq is None:
1474 return COMPARE_FAILED
1476 for ld, rd in util.zip_longest(lseq, rseq, fillvalue=None):
1477 if (
1478 self.visit_dml_values(
1479 attrname, left_parent, ld, right_parent, rd, **kw
1480 )
1481 is COMPARE_FAILED
1482 ):
1483 return COMPARE_FAILED
1485 def compare_clauselist(self, left, right, **kw):
1486 if left.operator is right.operator:
1487 if operators.is_associative(left.operator):
1488 if self._compare_unordered_sequences(
1489 left.clauses, right.clauses, **kw
1490 ):
1491 return ["operator", "clauses"]
1492 else:
1493 return COMPARE_FAILED
1494 else:
1495 return ["operator"]
1496 else:
1497 return COMPARE_FAILED
1499 def compare_binary(self, left, right, **kw):
1500 if left.operator == right.operator:
1501 if operators.is_commutative(left.operator):
1502 if (
1503 self.compare_inner(left.left, right.left, **kw)
1504 and self.compare_inner(left.right, right.right, **kw)
1505 ) or (
1506 self.compare_inner(left.left, right.right, **kw)
1507 and self.compare_inner(left.right, right.left, **kw)
1508 ):
1509 return ["operator", "negate", "left", "right"]
1510 else:
1511 return COMPARE_FAILED
1512 else:
1513 return ["operator", "negate"]
1514 else:
1515 return COMPARE_FAILED
1517 def compare_bindparam(self, left, right, **kw):
1518 compare_keys = kw.pop("compare_keys", True)
1519 compare_values = kw.pop("compare_values", True)
1521 if compare_values:
1522 omit = []
1523 else:
1524 # this means, "skip these, we already compared"
1525 omit = ["callable", "value"]
1527 if not compare_keys:
1528 omit.append("key")
1530 return omit
1533class ColIdentityComparatorStrategy(TraversalComparatorStrategy):
1534 def compare_column_element(
1535 self, left, right, use_proxies=True, equivalents=(), **kw
1536 ):
1537 """Compare ColumnElements using proxies and equivalent collections.
1539 This is a comparison strategy specific to the ORM.
1540 """
1542 to_compare = (right,)
1543 if equivalents and right in equivalents:
1544 to_compare = equivalents[right].union(to_compare)
1546 for oth in to_compare:
1547 if use_proxies and left.shares_lineage(oth):
1548 return SKIP_TRAVERSE
1549 elif hash(left) == hash(right):
1550 return SKIP_TRAVERSE
1551 else:
1552 return COMPARE_FAILED
1554 def compare_column(self, left, right, **kw):
1555 return self.compare_column_element(left, right, **kw)
1557 def compare_label(self, left, right, **kw):
1558 return self.compare_column_element(left, right, **kw)
1560 def compare_table(self, left, right, **kw):
1561 # tables compare on identity, since it's not really feasible to
1562 # compare them column by column with the above rules
1563 return SKIP_TRAVERSE if left is right else COMPARE_FAILED