Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
traversals.py1023 linesDownload Raw Back to sql
1# sql/traversals.py
2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: allow-untyped-defs, allow-untyped-calls
8
9from __future__ import annotations
10
11from collections import deque
12import collections.abc as collections_abc
13import itertools
14from itertools import zip_longest
15import operator
16import typing
17from typing import Any
18from typing import Callable
19from typing import Deque
20from typing import Dict
21from typing import Iterable
22from typing import Optional
23from typing import Set
24from typing import Tuple
25from typing import Type
26
27from . import operators
28from .cache_key import HasCacheKey
29from .visitors import _TraverseInternalsType
30from .visitors import anon_map
31from .visitors import ExternallyTraversible
32from .visitors import HasTraversalDispatch
33from .visitors import HasTraverseInternals
34from .. import util
35from ..util import langhelpers
36from ..util.typing import Self
37
38
39SKIP_TRAVERSE = util.symbol("skip_traverse")
40COMPARE_FAILED = False
41COMPARE_SUCCEEDED = True
42
43
44def compare(obj1: Any, obj2: Any, **kw: Any) -> bool:
45    strategy: TraversalComparatorStrategy
46    if kw.get("use_proxies", False):
47        strategy = ColIdentityComparatorStrategy()
48    else:
49        strategy = TraversalComparatorStrategy()
50
51    return strategy.compare(obj1, obj2, **kw)
52
53
54def _preconfigure_traversals(target_hierarchy: Type[Any]) -> None:
55    for cls in util.walk_subclasses(target_hierarchy):
56        if hasattr(cls, "_generate_cache_attrs") and hasattr(
57            cls, "_traverse_internals"
58        ):
59            cls._generate_cache_attrs()
60            _copy_internals.generate_dispatch(
61                cls,
62                cls._traverse_internals,
63                "_generated_copy_internals_traversal",
64            )
65            _get_children.generate_dispatch(
66                cls,
67                cls._traverse_internals,
68                "_generated_get_children_traversal",
69            )
70
71
72class HasShallowCopy(HasTraverseInternals):
73    """attribute-wide operations that are useful for classes that use
74    __slots__ and therefore can't operate on their attributes in a dictionary.
75
76
77    """
78
79    __slots__ = ()
80
81    if typing.TYPE_CHECKING:
82
83        def _generated_shallow_copy_traversal(self, other: Self) -> None: ...
84
85        def _generated_shallow_from_dict_traversal(
86            self, d: Dict[str, Any]
87        ) -> None: ...
88
89        def _generated_shallow_to_dict_traversal(self) -> Dict[str, Any]: ...
90
91    @classmethod
92    def _generate_shallow_copy(
93        cls,
94        internal_dispatch: _TraverseInternalsType,
95        method_name: str,
96    ) -> Callable[[Self, Self], None]:
97        code = "\n".join(
98            f"    other.{attrname} = self.{attrname}"
99            for attrname, _ in internal_dispatch
100        )
101        meth_text = f"def {method_name}(self, other):\n{code}\n"
102        return langhelpers._exec_code_in_env(meth_text, {}, method_name)
103
104    @classmethod
105    def _generate_shallow_to_dict(
106        cls,
107        internal_dispatch: _TraverseInternalsType,
108        method_name: str,
109    ) -> Callable[[Self], Dict[str, Any]]:
110        code = ",\n".join(
111            f"    '{attrname}': self.{attrname}"
112            for attrname, _ in internal_dispatch
113        )
114        meth_text = f"def {method_name}(self):\n    return {{{code}}}\n"
115        return langhelpers._exec_code_in_env(meth_text, {}, method_name)
116
117    @classmethod
118    def _generate_shallow_from_dict(
119        cls,
120        internal_dispatch: _TraverseInternalsType,
121        method_name: str,
122    ) -> Callable[[Self, Dict[str, Any]], None]:
123        code = "\n".join(
124            f"    self.{attrname} = d['{attrname}']"
125            for attrname, _ in internal_dispatch
126        )
127        meth_text = f"def {method_name}(self, d):\n{code}\n"
128        return langhelpers._exec_code_in_env(meth_text, {}, method_name)
129
130    def _shallow_from_dict(self, d: Dict[str, Any]) -> None:
131        cls = self.__class__
132
133        shallow_from_dict: Callable[[HasShallowCopy, Dict[str, Any]], None]
134        try:
135            shallow_from_dict = cls.__dict__[
136                "_generated_shallow_from_dict_traversal"
137            ]
138        except KeyError:
139            shallow_from_dict = self._generate_shallow_from_dict(
140                cls._traverse_internals,
141                "_generated_shallow_from_dict_traversal",
142            )
143
144            cls._generated_shallow_from_dict_traversal = shallow_from_dict  # type: ignore  # noqa: E501
145
146        shallow_from_dict(self, d)
147
148    def _shallow_to_dict(self) -> Dict[str, Any]:
149        cls = self.__class__
150
151        shallow_to_dict: Callable[[HasShallowCopy], Dict[str, Any]]
152
153        try:
154            shallow_to_dict = cls.__dict__[
155                "_generated_shallow_to_dict_traversal"
156            ]
157        except KeyError:
158            shallow_to_dict = self._generate_shallow_to_dict(
159                cls._traverse_internals, "_generated_shallow_to_dict_traversal"
160            )
161
162            cls._generated_shallow_to_dict_traversal = shallow_to_dict  # type: ignore  # noqa: E501
163        return shallow_to_dict(self)
164
165    def _shallow_copy_to(self, other: Self) -> None:
166        cls = self.__class__
167
168        shallow_copy: Callable[[Self, Self], None]
169        try:
170            shallow_copy = cls.__dict__["_generated_shallow_copy_traversal"]
171        except KeyError:
172            shallow_copy = self._generate_shallow_copy(
173                cls._traverse_internals, "_generated_shallow_copy_traversal"
174            )
175
176            cls._generated_shallow_copy_traversal = shallow_copy  # type: ignore  # noqa: E501
177        shallow_copy(self, other)
178
179    def _clone(self, **kw: Any) -> Self:
180        """Create a shallow copy"""
181        c = self.__class__.__new__(self.__class__)
182        self._shallow_copy_to(c)
183        return c
184
185
186class GenerativeOnTraversal(HasShallowCopy):
187    """Supplies Generative behavior but making use of traversals to shallow
188    copy.
189
190    .. seealso::
191
192        :class:`sqlalchemy.sql.base.Generative`
193
194
195    """
196
197    __slots__ = ()
198
199    def _generate(self) -> Self:
200        cls = self.__class__
201        s = cls.__new__(cls)
202        self._shallow_copy_to(s)
203        return s
204
205
206def _clone(element, **kw):
207    return element._clone()
208
209
210class HasCopyInternals(HasTraverseInternals):
211    __slots__ = ()
212
213    def _clone(self, **kw):
214        raise NotImplementedError()
215
216    def _copy_internals(
217        self, *, omit_attrs: Iterable[str] = (), **kw: Any
218    ) -> None:
219        """Reassign internal elements to be clones of themselves.
220
221        Called during a copy-and-traverse operation on newly
222        shallow-copied elements to create a deep copy.
223
224        The given clone function should be used, which may be applying
225        additional transformations to the element (i.e. replacement
226        traversal, cloned traversal, annotations).
227
228        """
229
230        try:
231            traverse_internals = self._traverse_internals
232        except AttributeError:
233            # user-defined classes may not have a _traverse_internals
234            return
235
236        for attrname, obj, meth in _copy_internals.run_generated_dispatch(
237            self, traverse_internals, "_generated_copy_internals_traversal"
238        ):
239            if attrname in omit_attrs:
240                continue
241
242            if obj is not None:
243                result = meth(attrname, self, obj, **kw)
244                if result is not None:
245                    setattr(self, attrname, result)
246
247
248class _CopyInternalsTraversal(HasTraversalDispatch):
249    """Generate a _copy_internals internal traversal dispatch for classes
250    with a _traverse_internals collection."""
251
252    def visit_clauseelement(
253        self, attrname, parent, element, clone=_clone, **kw
254    ):
255        return clone(element, **kw)
256
257    def visit_clauseelement_list(
258        self, attrname, parent, element, clone=_clone, **kw
259    ):
260        return [clone(clause, **kw) for clause in element]
261
262    def visit_clauseelement_tuple(
263        self, attrname, parent, element, clone=_clone, **kw
264    ):
265        return tuple([clone(clause, **kw) for clause in element])
266
267    def visit_executable_options(
268        self, attrname, parent, element, clone=_clone, **kw
269    ):
270        return tuple([clone(clause, **kw) for clause in element])
271
272    def visit_clauseelement_unordered_set(
273        self, attrname, parent, element, clone=_clone, **kw
274    ):
275        return {clone(clause, **kw) for clause in element}
276
277    def visit_clauseelement_tuples(
278        self, attrname, parent, element, clone=_clone, **kw
279    ):
280        return [
281            tuple(clone(tup_elem, **kw) for tup_elem in elem)
282            for elem in element
283        ]
284
285    def visit_string_clauseelement_dict(
286        self, attrname, parent, element, clone=_clone, **kw
287    ):
288        return {key: clone(value, **kw) for key, value in element.items()}
289
290    def visit_setup_join_tuple(
291        self, attrname, parent, element, clone=_clone, **kw
292    ):
293        return tuple(
294            (
295                clone(target, **kw) if target is not None else None,
296                clone(onclause, **kw) if onclause is not None else None,
297                clone(from_, **kw) if from_ is not None else None,
298                flags,
299            )
300            for (target, onclause, from_, flags) in element
301        )
302
303    def visit_memoized_select_entities(self, attrname, parent, element, **kw):
304        return self.visit_clauseelement_tuple(attrname, parent, element, **kw)
305
306    def visit_dml_ordered_values(
307        self, attrname, parent, element, clone=_clone, **kw
308    ):
309        # sequence of 2-tuples
310        return [
311            (
312                (
313                    clone(key, **kw)
314                    if hasattr(key, "__clause_element__")
315                    else key
316                ),
317                clone(value, **kw),
318            )
319            for key, value in element
320        ]
321
322    def visit_dml_values(self, attrname, parent, element, clone=_clone, **kw):
323        return {
324            (
325                clone(key, **kw) if hasattr(key, "__clause_element__") else key
326            ): clone(value, **kw)
327            for key, value in element.items()
328        }
329
330    def visit_dml_multi_values(
331        self, attrname, parent, element, clone=_clone, **kw
332    ):
333        # sequence of sequences, each sequence contains a list/dict/tuple
334
335        def copy(elem):
336            if isinstance(elem, (list, tuple)):
337                return [
338                    (
339                        clone(value, **kw)
340                        if hasattr(value, "__clause_element__")
341                        else value
342                    )
343                    for value in elem
344                ]
345            elif isinstance(elem, dict):
346                return {
347                    (
348                        clone(key, **kw)
349                        if hasattr(key, "__clause_element__")
350                        else key
351                    ): (
352                        clone(value, **kw)
353                        if hasattr(value, "__clause_element__")
354                        else value
355                    )
356                    for key, value in elem.items()
357                }
358            else:
359                # TODO: use abc classes
360                assert False
361
362        return [
363            [copy(sub_element) for sub_element in sequence]
364            for sequence in element
365        ]
366
367    def visit_propagate_attrs(
368        self, attrname, parent, element, clone=_clone, **kw
369    ):
370        return element
371
372
373_copy_internals = _CopyInternalsTraversal()
374
375
376def _flatten_clauseelement(element):
377    while hasattr(element, "__clause_element__") and not getattr(
378        element, "is_clause_element", False
379    ):
380        element = element.__clause_element__()
381
382    return element
383
384
385class _GetChildrenTraversal(HasTraversalDispatch):
386    """Generate a _children_traversal internal traversal dispatch for classes
387    with a _traverse_internals collection."""
388
389    def visit_has_cache_key(self, element, **kw):
390        # the GetChildren traversal refers explicitly to ClauseElement
391        # structures.  Within these, a plain HasCacheKey is not a
392        # ClauseElement, so don't include these.
393        return ()
394
395    def visit_clauseelement(self, element, **kw):
396        return (element,)
397
398    def visit_clauseelement_list(self, element, **kw):
399        return element
400
401    def visit_clauseelement_tuple(self, element, **kw):
402        return element
403
404    def visit_clauseelement_tuples(self, element, **kw):
405        return itertools.chain.from_iterable(element)
406
407    def visit_fromclause_canonical_column_collection(self, element, **kw):
408        return ()
409
410    def visit_string_clauseelement_dict(self, element, **kw):
411        return element.values()
412
413    def visit_fromclause_ordered_set(self, element, **kw):
414        return element
415
416    def visit_clauseelement_unordered_set(self, element, **kw):
417        return element
418
419    def visit_setup_join_tuple(self, element, **kw):
420        for target, onclause, from_, flags in element:
421            if from_ is not None:
422                yield from_
423
424            if not isinstance(target, str):
425                yield _flatten_clauseelement(target)
426
427            if onclause is not None and not isinstance(onclause, str):
428                yield _flatten_clauseelement(onclause)
429
430    def visit_memoized_select_entities(self, element, **kw):
431        return self.visit_clauseelement_tuple(element, **kw)
432
433    def visit_dml_ordered_values(self, element, **kw):
434        for k, v in element:
435            if hasattr(k, "__clause_element__"):
436                yield k
437            yield v
438
439    def visit_dml_values(self, element, **kw):
440        expr_values = {k for k in element if hasattr(k, "__clause_element__")}
441        str_values = expr_values.symmetric_difference(element)
442
443        for k in sorted(str_values):
444            yield element[k]
445        for k in expr_values:
446            yield k
447            yield element[k]
448
449    def visit_dml_multi_values(self, element, **kw):
450        return ()
451
452    def visit_propagate_attrs(self, element, **kw):
453        return ()
454
455
456_get_children = _GetChildrenTraversal()
457
458
459@util.preload_module("sqlalchemy.sql.elements")
460def _resolve_name_for_compare(element, name, anon_map, **kw):
461    if isinstance(name, util.preloaded.sql_elements._anonymous_label):
462        name = name.apply_map(anon_map)
463
464    return name
465
466
467class TraversalComparatorStrategy(HasTraversalDispatch, util.MemoizedSlots):
468    __slots__ = "stack", "cache", "anon_map"
469
470    def __init__(self):
471        self.stack: Deque[
472            Tuple[
473                Optional[ExternallyTraversible],
474                Optional[ExternallyTraversible],
475            ]
476        ] = deque()
477        self.cache = set()
478
479    def _memoized_attr_anon_map(self):
480        return (anon_map(), anon_map())
481
482    def compare(
483        self,
484        obj1: ExternallyTraversible,
485        obj2: ExternallyTraversible,
486        **kw: Any,
487    ) -> bool:
488        stack = self.stack
489        cache = self.cache
490
491        compare_annotations = kw.get("compare_annotations", False)
492
493        stack.append((obj1, obj2))
494
495        while stack:
496            left, right = stack.popleft()
497
498            if left is right:
499                continue
500            elif left is None or right is None:
501                # we know they are different so no match
502                return False
503            elif (left, right) in cache:
504                continue
505            cache.add((left, right))
506
507            visit_name = left.__visit_name__
508            if visit_name != right.__visit_name__:
509                return False
510
511            meth = getattr(self, "compare_%s" % visit_name, None)
512
513            if meth:
514                attributes_compared = meth(left, right, **kw)
515                if attributes_compared is COMPARE_FAILED:
516                    return False
517                elif attributes_compared is SKIP_TRAVERSE:
518                    continue
519
520                # attributes_compared is returned as a list of attribute
521                # names that were "handled" by the comparison method above.
522                # remaining attribute names in the _traverse_internals
523                # will be compared.
524            else:
525                attributes_compared = ()
526
527            for (
528                (left_attrname, left_visit_sym),
529                (right_attrname, right_visit_sym),
530            ) in zip_longest(
531                left._traverse_internals,
532                right._traverse_internals,
533                fillvalue=(None, None),
534            ):
535                if not compare_annotations and (
536                    (left_attrname == "_annotations")
537                    or (right_attrname == "_annotations")
538                ):
539                    continue
540
541                if (
542                    left_attrname != right_attrname
543                    or left_visit_sym is not right_visit_sym
544                ):
545                    return False
546                elif left_attrname in attributes_compared:
547                    continue
548
549                assert left_visit_sym is not None
550                assert left_attrname is not None
551                assert right_attrname is not None
552
553                dispatch = self.dispatch(left_visit_sym)
554                assert dispatch is not None, (
555                    f"{self.__class__} has no dispatch for "
556                    f"'{self._dispatch_lookup[left_visit_sym]}'"
557                )
558                left_child = operator.attrgetter(left_attrname)(left)
559                right_child = operator.attrgetter(right_attrname)(right)
560                if left_child is None:
561                    if right_child is not None:
562                        return False
563                    else:
564                        continue
565
566                comparison = dispatch(
567                    left_attrname, left, left_child, right, right_child, **kw
568                )
569                if comparison is COMPARE_FAILED:
570                    return False
571
572        return True
573
574    def compare_inner(self, obj1, obj2, **kw):
575        comparator = self.__class__()
576        return comparator.compare(obj1, obj2, **kw)
577
578    def visit_has_cache_key(
579        self, attrname, left_parent, left, right_parent, right, **kw
580    ):
581        if left._gen_cache_key(self.anon_map[0], []) != right._gen_cache_key(
582            self.anon_map[1], []
583        ):
584            return COMPARE_FAILED
585
586    def visit_propagate_attrs(
587        self, attrname, left_parent, left, right_parent, right, **kw
588    ):
589        return self.compare_inner(
590            left.get("plugin_subject", None), right.get("plugin_subject", None)
591        )
592
593    def visit_has_cache_key_list(
594        self, attrname, left_parent, left, right_parent, right, **kw
595    ):
596        for l, r in zip_longest(left, right, fillvalue=None):
597            if l is None:
598                if r is not None:
599                    return COMPARE_FAILED
600                else:
601                    continue
602            elif r is None:
603                return COMPARE_FAILED
604
605            if l._gen_cache_key(self.anon_map[0], []) != r._gen_cache_key(
606                self.anon_map[1], []
607            ):
608                return COMPARE_FAILED
609
610    def visit_executable_options(
611        self, attrname, left_parent, left, right_parent, right, **kw
612    ):
613        for l, r in zip_longest(left, right, fillvalue=None):
614            if l is None:
615                if r is not None:
616                    return COMPARE_FAILED
617                else:
618                    continue
619            elif r is None:
620                return COMPARE_FAILED
621
622            if (
623                l._gen_cache_key(self.anon_map[0], [])
624                if l._is_has_cache_key
625                else l
626            ) != (
627                r._gen_cache_key(self.anon_map[1], [])
628                if r._is_has_cache_key
629                else r
630            ):
631                return COMPARE_FAILED
632
633    def visit_clauseelement(
634        self, attrname, left_parent, left, right_parent, right, **kw
635    ):
636        self.stack.append((left, right))
637
638    def visit_fromclause_canonical_column_collection(
639        self, attrname, left_parent, left, right_parent, right, **kw
640    ):
641        for lcol, rcol in zip_longest(left, right, fillvalue=None):
642            self.stack.append((lcol, rcol))
643
644    def visit_fromclause_derived_column_collection(
645        self, attrname, left_parent, left, right_parent, right, **kw
646    ):
647        pass
648
649    def visit_string_clauseelement_dict(
650        self, attrname, left_parent, left, right_parent, right, **kw
651    ):
652        for lstr, rstr in zip_longest(
653            sorted(left), sorted(right), fillvalue=None
654        ):
655            if lstr != rstr:
656                return COMPARE_FAILED
657            self.stack.append((left[lstr], right[rstr]))
658
659    def visit_clauseelement_tuples(
660        self, attrname, left_parent, left, right_parent, right, **kw
661    ):
662        for ltup, rtup in zip_longest(left, right, fillvalue=None):
663            if ltup is None or rtup is None:
664                return COMPARE_FAILED
665
666            for l, r in zip_longest(ltup, rtup, fillvalue=None):
667                self.stack.append((l, r))
668
669    def visit_clauseelement_list(
670        self, attrname, left_parent, left, right_parent, right, **kw
671    ):
672        for l, r in zip_longest(left, right, fillvalue=None):
673            self.stack.append((l, r))
674
675    def visit_clauseelement_tuple(
676        self, attrname, left_parent, left, right_parent, right, **kw
677    ):
678        for l, r in zip_longest(left, right, fillvalue=None):
679            self.stack.append((l, r))
680
681    def _compare_unordered_sequences(self, seq1, seq2, **kw):
682        if seq1 is None:
683            return seq2 is None
684
685        completed: Set[object] = set()
686        for clause in seq1:
687            for other_clause in set(seq2).difference(completed):
688                if self.compare_inner(clause, other_clause, **kw):
689                    completed.add(other_clause)
690                    break
691        return len(completed) == len(seq1) == len(seq2)
692
693    def visit_clauseelement_unordered_set(
694        self, attrname, left_parent, left, right_parent, right, **kw
695    ):
696        return self._compare_unordered_sequences(left, right, **kw)
697
698    def visit_fromclause_ordered_set(
699        self, attrname, left_parent, left, right_parent, right, **kw
700    ):
701        for l, r in zip_longest(left, right, fillvalue=None):
702            self.stack.append((l, r))
703
704    def visit_string(
705        self, attrname, left_parent, left, right_parent, right, **kw
706    ):
707        return left == right
708
709    def visit_string_list(
710        self, attrname, left_parent, left, right_parent, right, **kw
711    ):
712        return left == right
713
714    def visit_string_multi_dict(
715        self, attrname, left_parent, left, right_parent, right, **kw
716    ):
717        for lk, rk in zip_longest(
718            sorted(left.keys()), sorted(right.keys()), fillvalue=(None, None)
719        ):
720            if lk != rk:
721                return COMPARE_FAILED
722
723            lv, rv = left[lk], right[rk]
724
725            lhc = isinstance(left, HasCacheKey)
726            rhc = isinstance(right, HasCacheKey)
727            if lhc and rhc:
728                if lv._gen_cache_key(
729                    self.anon_map[0], []
730                ) != rv._gen_cache_key(self.anon_map[1], []):
731                    return COMPARE_FAILED
732            elif lhc != rhc:
733                return COMPARE_FAILED
734            elif lv != rv:
735                return COMPARE_FAILED
736
737    def visit_multi(
738        self, attrname, left_parent, left, right_parent, right, **kw
739    ):
740        lhc = isinstance(left, HasCacheKey)
741        rhc = isinstance(right, HasCacheKey)
742        if lhc and rhc:
743            if left._gen_cache_key(
744                self.anon_map[0], []
745            ) != right._gen_cache_key(self.anon_map[1], []):
746                return COMPARE_FAILED
747        elif lhc != rhc:
748            return COMPARE_FAILED
749        else:
750            return left == right
751
752    def visit_anon_name(
753        self, attrname, left_parent, left, right_parent, right, **kw
754    ):
755        return _resolve_name_for_compare(
756            left_parent, left, self.anon_map[0], **kw
757        ) == _resolve_name_for_compare(
758            right_parent, right, self.anon_map[1], **kw
759        )
760
761    def visit_boolean(
762        self, attrname, left_parent, left, right_parent, right, **kw
763    ):
764        return left == right
765
766    def visit_operator(
767        self, attrname, left_parent, left, right_parent, right, **kw
768    ):
769        return left == right
770
771    def visit_type(
772        self, attrname, left_parent, left, right_parent, right, **kw
773    ):
774        return left._compare_type_affinity(right)
775
776    def visit_plain_dict(
777        self, attrname, left_parent, left, right_parent, right, **kw
778    ):
779        return left == right
780
781    def visit_dialect_options(
782        self, attrname, left_parent, left, right_parent, right, **kw
783    ):
784        return left == right
785
786    def visit_annotations_key(
787        self, attrname, left_parent, left, right_parent, right, **kw
788    ):
789        if left and right:
790            return (
791                left_parent._annotations_cache_key
792                == right_parent._annotations_cache_key
793            )
794        else:
795            return left == right
796
797    def visit_with_context_options(
798        self, attrname, left_parent, left, right_parent, right, **kw
799    ):
800        return tuple((fn.__code__, c_key) for fn, c_key in left) == tuple(
801            (fn.__code__, c_key) for fn, c_key in right
802        )
803
804    def visit_plain_obj(
805        self, attrname, left_parent, left, right_parent, right, **kw
806    ):
807        return left == right
808
809    def visit_named_ddl_element(
810        self, attrname, left_parent, left, right_parent, right, **kw
811    ):
812        if left is None:
813            if right is not None:
814                return COMPARE_FAILED
815
816        return left.name == right.name
817
818    def visit_prefix_sequence(
819        self, attrname, left_parent, left, right_parent, right, **kw
820    ):
821        for (l_clause, l_str), (r_clause, r_str) in zip_longest(
822            left, right, fillvalue=(None, None)
823        ):
824            if l_str != r_str:
825                return COMPARE_FAILED
826            else:
827                self.stack.append((l_clause, r_clause))
828
829    def visit_setup_join_tuple(
830        self, attrname, left_parent, left, right_parent, right, **kw
831    ):
832        # TODO: look at attrname for "legacy_join" and use different structure
833        for (
834            (l_target, l_onclause, l_from, l_flags),
835            (r_target, r_onclause, r_from, r_flags),
836        ) in zip_longest(left, right, fillvalue=(None, None, None, None)):
837            if l_flags != r_flags:
838                return COMPARE_FAILED
839            self.stack.append((l_target, r_target))
840            self.stack.append((l_onclause, r_onclause))
841            self.stack.append((l_from, r_from))
842
843    def visit_memoized_select_entities(
844        self, attrname, left_parent, left, right_parent, right, **kw
845    ):
846        return self.visit_clauseelement_tuple(
847            attrname, left_parent, left, right_parent, right, **kw
848        )
849
850    def visit_table_hint_list(
851        self, attrname, left_parent, left, right_parent, right, **kw
852    ):
853        left_keys = sorted(left, key=lambda elem: (elem[0].fullname, elem[1]))
854        right_keys = sorted(
855            right, key=lambda elem: (elem[0].fullname, elem[1])
856        )
857        for (ltable, ldialect), (rtable, rdialect) in zip_longest(
858            left_keys, right_keys, fillvalue=(None, None)
859        ):
860            if ldialect != rdialect:
861                return COMPARE_FAILED
862            elif left[(ltable, ldialect)] != right[(rtable, rdialect)]:
863                return COMPARE_FAILED
864            else:
865                self.stack.append((ltable, rtable))
866
867    def visit_statement_hint_list(
868        self, attrname, left_parent, left, right_parent, right, **kw
869    ):
870        return left == right
871
872    def visit_unknown_structure(
873        self, attrname, left_parent, left, right_parent, right, **kw
874    ):
875        raise NotImplementedError()
876
877    def visit_dml_ordered_values(
878        self, attrname, left_parent, left, right_parent, right, **kw
879    ):
880        # sequence of tuple pairs
881
882        for (lk, lv), (rk, rv) in zip_longest(
883            left, right, fillvalue=(None, None)
884        ):
885            if not self._compare_dml_values_or_ce(lk, rk, **kw):
886                return COMPARE_FAILED
887
888    def _compare_dml_values_or_ce(self, lv, rv, **kw):
889        lvce = hasattr(lv, "__clause_element__")
890        rvce = hasattr(rv, "__clause_element__")
891        if lvce != rvce:
892            return False
893        elif lvce and not self.compare_inner(lv, rv, **kw):
894            return False
895        elif not lvce and lv != rv:
896            return False
897        elif not self.compare_inner(lv, rv, **kw):
898            return False
899
900        return True
901
902    def visit_dml_values(
903        self, attrname, left_parent, left, right_parent, right, **kw
904    ):
905        if left is None or right is None or len(left) != len(right):
906            return COMPARE_FAILED
907
908        if isinstance(left, collections_abc.Sequence):
909            for lv, rv in zip(left, right):
910                if not self._compare_dml_values_or_ce(lv, rv, **kw):
911                    return COMPARE_FAILED
912        elif isinstance(right, collections_abc.Sequence):
913            return COMPARE_FAILED
914        else:
915            # dictionaries guaranteed to support insert ordering in
916            # py37 so that we can compare the keys in order.  without
917            # this, we can't compare SQL expression keys because we don't
918            # know which key is which
919            for (lk, lv), (rk, rv) in zip(left.items(), right.items()):
920                if not self._compare_dml_values_or_ce(lk, rk, **kw):
921                    return COMPARE_FAILED
922                if not self._compare_dml_values_or_ce(lv, rv, **kw):
923                    return COMPARE_FAILED
924
925    def visit_dml_multi_values(
926        self, attrname, left_parent, left, right_parent, right, **kw
927    ):
928        for lseq, rseq in zip_longest(left, right, fillvalue=None):
929            if lseq is None or rseq is None:
930                return COMPARE_FAILED
931
932            for ld, rd in zip_longest(lseq, rseq, fillvalue=None):
933                if (
934                    self.visit_dml_values(
935                        attrname, left_parent, ld, right_parent, rd, **kw
936                    )
937                    is COMPARE_FAILED
938                ):
939                    return COMPARE_FAILED
940
941    def compare_expression_clauselist(self, left, right, **kw):
942        if left.operator is right.operator:
943            if operators.is_associative(left.operator):
944                if self._compare_unordered_sequences(
945                    left.clauses, right.clauses, **kw
946                ):
947                    return ["operator", "clauses"]
948                else:
949                    return COMPARE_FAILED
950            else:
951                return ["operator"]
952        else:
953            return COMPARE_FAILED
954
955    def compare_clauselist(self, left, right, **kw):
956        return self.compare_expression_clauselist(left, right, **kw)
957
958    def compare_binary(self, left, right, **kw):
959        if left.operator == right.operator:
960            if operators.is_commutative(left.operator):
961                if (
962                    self.compare_inner(left.left, right.left, **kw)
963                    and self.compare_inner(left.right, right.right, **kw)
964                ) or (
965                    self.compare_inner(left.left, right.right, **kw)
966                    and self.compare_inner(left.right, right.left, **kw)
967                ):
968                    return ["operator", "negate", "left", "right"]
969                else:
970                    return COMPARE_FAILED
971            else:
972                return ["operator", "negate"]
973        else:
974            return COMPARE_FAILED
975
976    def compare_bindparam(self, left, right, **kw):
977        compare_keys = kw.pop("compare_keys", True)
978        compare_values = kw.pop("compare_values", True)
979
980        if compare_values:
981            omit = []
982        else:
983            # this means, "skip these, we already compared"
984            omit = ["callable", "value"]
985
986        if not compare_keys:
987            omit.append("key")
988
989        return omit
990
991
992class ColIdentityComparatorStrategy(TraversalComparatorStrategy):
993    def compare_column_element(
994        self, left, right, use_proxies=True, equivalents=(), **kw
995    ):
996        """Compare ColumnElements using proxies and equivalent collections.
997
998        This is a comparison strategy specific to the ORM.
999        """
1000
1001        to_compare = (right,)
1002        if equivalents and right in equivalents:
1003            to_compare = equivalents[right].union(to_compare)
1004
1005        for oth in to_compare:
1006            if use_proxies and left.shares_lineage(oth):
1007                return SKIP_TRAVERSE
1008            elif hash(left) == hash(right):
1009                return SKIP_TRAVERSE
1010        else:
1011            return COMPARE_FAILED
1012
1013    def compare_column(self, left, right, **kw):
1014        return self.compare_column_element(left, right, **kw)
1015
1016    def compare_label(self, left, right, **kw):
1017        return self.compare_column_element(left, right, **kw)
1018
1019    def compare_table(self, left, right, **kw):
1020        # tables compare on identity, since it's not really feasible to
1021        # compare them column by column with the above rules
1022        return SKIP_TRAVERSE if left is right else COMPARE_FAILED
1023 
codekingpro/portable-devtools · Team Ai