traversals.py 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574
  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. from collections import deque
  8. from collections import namedtuple
  9. import itertools
  10. import operator
  11. from . import operators
  12. from .visitors import ExtendedInternalTraversal
  13. from .visitors import InternalTraversal
  14. from .. import util
  15. from ..inspection import inspect
  16. from ..util import collections_abc
  17. from ..util import HasMemoized
  18. from ..util import py37
  19. SKIP_TRAVERSE = util.symbol("skip_traverse")
  20. COMPARE_FAILED = False
  21. COMPARE_SUCCEEDED = True
  22. NO_CACHE = util.symbol("no_cache")
  23. CACHE_IN_PLACE = util.symbol("cache_in_place")
  24. CALL_GEN_CACHE_KEY = util.symbol("call_gen_cache_key")
  25. STATIC_CACHE_KEY = util.symbol("static_cache_key")
  26. PROPAGATE_ATTRS = util.symbol("propagate_attrs")
  27. ANON_NAME = util.symbol("anon_name")
  28. def compare(obj1, obj2, **kw):
  29. if kw.get("use_proxies", False):
  30. strategy = ColIdentityComparatorStrategy()
  31. else:
  32. strategy = TraversalComparatorStrategy()
  33. return strategy.compare(obj1, obj2, **kw)
  34. def _preconfigure_traversals(target_hierarchy):
  35. for cls in util.walk_subclasses(target_hierarchy):
  36. if hasattr(cls, "_traverse_internals"):
  37. cls._generate_cache_attrs()
  38. _copy_internals.generate_dispatch(
  39. cls,
  40. cls._traverse_internals,
  41. "_generated_copy_internals_traversal",
  42. )
  43. _get_children.generate_dispatch(
  44. cls,
  45. cls._traverse_internals,
  46. "_generated_get_children_traversal",
  47. )
  48. class HasCacheKey(object):
  49. """Mixin for objects which can produce a cache key.
  50. .. seealso::
  51. :class:`.CacheKey`
  52. :ref:`sql_caching`
  53. """
  54. _cache_key_traversal = NO_CACHE
  55. _is_has_cache_key = True
  56. _hierarchy_supports_caching = True
  57. """private attribute which may be set to False to prevent the
  58. inherit_cache warning from being emitted for a hierarchy of subclasses.
  59. Currently applies to the DDLElement hierarchy which does not implement
  60. caching.
  61. """
  62. inherit_cache = None
  63. """Indicate if this :class:`.HasCacheKey` instance should make use of the
  64. cache key generation scheme used by its immediate superclass.
  65. The attribute defaults to ``None``, which indicates that a construct has
  66. not yet taken into account whether or not its appropriate for it to
  67. participate in caching; this is functionally equivalent to setting the
  68. value to ``False``, except that a warning is also emitted.
  69. This flag can be set to ``True`` on a particular class, if the SQL that
  70. corresponds to the object does not change based on attributes which
  71. are local to this class, and not its superclass.
  72. .. seealso::
  73. :ref:`compilerext_caching` - General guideslines for setting the
  74. :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
  75. defined SQL constructs.
  76. """
  77. __slots__ = ()
  78. @classmethod
  79. def _generate_cache_attrs(cls):
  80. """generate cache key dispatcher for a new class.
  81. This sets the _generated_cache_key_traversal attribute once called
  82. so should only be called once per class.
  83. """
  84. inherit_cache = cls.__dict__.get("inherit_cache", None)
  85. inherit = bool(inherit_cache)
  86. if inherit:
  87. _cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
  88. if _cache_key_traversal is None:
  89. try:
  90. _cache_key_traversal = cls._traverse_internals
  91. except AttributeError:
  92. cls._generated_cache_key_traversal = NO_CACHE
  93. return NO_CACHE
  94. # TODO: wouldn't we instead get this from our superclass?
  95. # also, our superclass may not have this yet, but in any case,
  96. # we'd generate for the superclass that has it. this is a little
  97. # more complicated, so for the moment this is a little less
  98. # efficient on startup but simpler.
  99. return _cache_key_traversal_visitor.generate_dispatch(
  100. cls, _cache_key_traversal, "_generated_cache_key_traversal"
  101. )
  102. else:
  103. _cache_key_traversal = cls.__dict__.get(
  104. "_cache_key_traversal", None
  105. )
  106. if _cache_key_traversal is None:
  107. _cache_key_traversal = cls.__dict__.get(
  108. "_traverse_internals", None
  109. )
  110. if _cache_key_traversal is None:
  111. cls._generated_cache_key_traversal = NO_CACHE
  112. if (
  113. inherit_cache is None
  114. and cls._hierarchy_supports_caching
  115. ):
  116. util.warn(
  117. "Class %s will not make use of SQL compilation "
  118. "caching as it does not set the 'inherit_cache' "
  119. "attribute to ``True``. This can have "
  120. "significant performance implications including "
  121. "some performance degradations in comparison to "
  122. "prior SQLAlchemy versions. Set this attribute "
  123. "to True if this object can make use of the cache "
  124. "key generated by the superclass. Alternatively, "
  125. "this attribute may be set to False which will "
  126. "disable this warning." % (cls.__name__),
  127. code="cprf",
  128. )
  129. return NO_CACHE
  130. return _cache_key_traversal_visitor.generate_dispatch(
  131. cls, _cache_key_traversal, "_generated_cache_key_traversal"
  132. )
  133. @util.preload_module("sqlalchemy.sql.elements")
  134. def _gen_cache_key(self, anon_map, bindparams):
  135. """return an optional cache key.
  136. The cache key is a tuple which can contain any series of
  137. objects that are hashable and also identifies
  138. this object uniquely within the presence of a larger SQL expression
  139. or statement, for the purposes of caching the resulting query.
  140. The cache key should be based on the SQL compiled structure that would
  141. ultimately be produced. That is, two structures that are composed in
  142. exactly the same way should produce the same cache key; any difference
  143. in the structures that would affect the SQL string or the type handlers
  144. should result in a different cache key.
  145. If a structure cannot produce a useful cache key, the NO_CACHE
  146. symbol should be added to the anon_map and the method should
  147. return None.
  148. """
  149. idself = id(self)
  150. cls = self.__class__
  151. if idself in anon_map:
  152. return (anon_map[idself], cls)
  153. else:
  154. # inline of
  155. # id_ = anon_map[idself]
  156. anon_map[idself] = id_ = str(anon_map.index)
  157. anon_map.index += 1
  158. try:
  159. dispatcher = cls.__dict__["_generated_cache_key_traversal"]
  160. except KeyError:
  161. # most of the dispatchers are generated up front
  162. # in sqlalchemy/sql/__init__.py ->
  163. # traversals.py-> _preconfigure_traversals().
  164. # this block will generate any remaining dispatchers.
  165. dispatcher = cls._generate_cache_attrs()
  166. if dispatcher is NO_CACHE:
  167. anon_map[NO_CACHE] = True
  168. return None
  169. result = (id_, cls)
  170. # inline of _cache_key_traversal_visitor.run_generated_dispatch()
  171. for attrname, obj, meth in dispatcher(
  172. self, _cache_key_traversal_visitor
  173. ):
  174. if obj is not None:
  175. # TODO: see if C code can help here as Python lacks an
  176. # efficient switch construct
  177. if meth is STATIC_CACHE_KEY:
  178. sck = obj._static_cache_key
  179. if sck is NO_CACHE:
  180. anon_map[NO_CACHE] = True
  181. return None
  182. result += (attrname, sck)
  183. elif meth is ANON_NAME:
  184. elements = util.preloaded.sql_elements
  185. if isinstance(obj, elements._anonymous_label):
  186. obj = obj.apply_map(anon_map)
  187. result += (attrname, obj)
  188. elif meth is CALL_GEN_CACHE_KEY:
  189. result += (
  190. attrname,
  191. obj._gen_cache_key(anon_map, bindparams),
  192. )
  193. # remaining cache functions are against
  194. # Python tuples, dicts, lists, etc. so we can skip
  195. # if they are empty
  196. elif obj:
  197. if meth is CACHE_IN_PLACE:
  198. result += (attrname, obj)
  199. elif meth is PROPAGATE_ATTRS:
  200. result += (
  201. attrname,
  202. obj["compile_state_plugin"],
  203. obj["plugin_subject"]._gen_cache_key(
  204. anon_map, bindparams
  205. )
  206. if obj["plugin_subject"]
  207. else None,
  208. )
  209. elif meth is InternalTraversal.dp_annotations_key:
  210. # obj is here is the _annotations dict. Table uses
  211. # a memoized version of it. however in other cases,
  212. # we generate it given anon_map as we may be from a
  213. # Join, Aliased, etc.
  214. # see #8790
  215. if self._gen_static_annotations_cache_key: # type: ignore # noqa: E501
  216. result += self._annotations_cache_key # type: ignore # noqa: E501
  217. else:
  218. result += self._gen_annotations_cache_key(anon_map) # type: ignore # noqa: E501
  219. elif (
  220. meth is InternalTraversal.dp_clauseelement_list
  221. or meth is InternalTraversal.dp_clauseelement_tuple
  222. or meth
  223. is InternalTraversal.dp_memoized_select_entities
  224. ):
  225. result += (
  226. attrname,
  227. tuple(
  228. [
  229. elem._gen_cache_key(anon_map, bindparams)
  230. for elem in obj
  231. ]
  232. ),
  233. )
  234. else:
  235. result += meth(
  236. attrname, obj, self, anon_map, bindparams
  237. )
  238. return result
  239. def _generate_cache_key(self):
  240. """return a cache key.
  241. The cache key is a tuple which can contain any series of
  242. objects that are hashable and also identifies
  243. this object uniquely within the presence of a larger SQL expression
  244. or statement, for the purposes of caching the resulting query.
  245. The cache key should be based on the SQL compiled structure that would
  246. ultimately be produced. That is, two structures that are composed in
  247. exactly the same way should produce the same cache key; any difference
  248. in the structures that would affect the SQL string or the type handlers
  249. should result in a different cache key.
  250. The cache key returned by this method is an instance of
  251. :class:`.CacheKey`, which consists of a tuple representing the
  252. cache key, as well as a list of :class:`.BindParameter` objects
  253. which are extracted from the expression. While two expressions
  254. that produce identical cache key tuples will themselves generate
  255. identical SQL strings, the list of :class:`.BindParameter` objects
  256. indicates the bound values which may have different values in
  257. each one; these bound parameters must be consulted in order to
  258. execute the statement with the correct parameters.
  259. a :class:`_expression.ClauseElement` structure that does not implement
  260. a :meth:`._gen_cache_key` method and does not implement a
  261. :attr:`.traverse_internals` attribute will not be cacheable; when
  262. such an element is embedded into a larger structure, this method
  263. will return None, indicating no cache key is available.
  264. """
  265. bindparams = []
  266. _anon_map = anon_map()
  267. key = self._gen_cache_key(_anon_map, bindparams)
  268. if NO_CACHE in _anon_map:
  269. return None
  270. else:
  271. return CacheKey(key, bindparams)
  272. @classmethod
  273. def _generate_cache_key_for_object(cls, obj):
  274. bindparams = []
  275. _anon_map = anon_map()
  276. key = obj._gen_cache_key(_anon_map, bindparams)
  277. if NO_CACHE in _anon_map:
  278. return None
  279. else:
  280. return CacheKey(key, bindparams)
  281. class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
  282. @HasMemoized.memoized_instancemethod
  283. def _generate_cache_key(self):
  284. return HasCacheKey._generate_cache_key(self)
  285. class CacheKey(namedtuple("CacheKey", ["key", "bindparams"])):
  286. """The key used to identify a SQL statement construct in the
  287. SQL compilation cache.
  288. .. seealso::
  289. :ref:`sql_caching`
  290. """
  291. def __hash__(self):
  292. """CacheKey itself is not hashable - hash the .key portion"""
  293. return None
  294. def to_offline_string(self, statement_cache, statement, parameters):
  295. """Generate an "offline string" form of this :class:`.CacheKey`
  296. The "offline string" is basically the string SQL for the
  297. statement plus a repr of the bound parameter values in series.
  298. Whereas the :class:`.CacheKey` object is dependent on in-memory
  299. identities in order to work as a cache key, the "offline" version
  300. is suitable for a cache that will work for other processes as well.
  301. The given ``statement_cache`` is a dictionary-like object where the
  302. string form of the statement itself will be cached. This dictionary
  303. should be in a longer lived scope in order to reduce the time spent
  304. stringifying statements.
  305. """
  306. if self.key not in statement_cache:
  307. statement_cache[self.key] = sql_str = str(statement)
  308. else:
  309. sql_str = statement_cache[self.key]
  310. if not self.bindparams:
  311. param_tuple = tuple(parameters[key] for key in sorted(parameters))
  312. else:
  313. param_tuple = tuple(
  314. parameters.get(bindparam.key, bindparam.value)
  315. for bindparam in self.bindparams
  316. )
  317. return repr((sql_str, param_tuple))
  318. def __eq__(self, other):
  319. return bool(self.key == other.key)
  320. def __ne__(self, other):
  321. return not (self.key == other.key)
  322. @classmethod
  323. def _diff_tuples(cls, left, right):
  324. ck1 = CacheKey(left, [])
  325. ck2 = CacheKey(right, [])
  326. return ck1._diff(ck2)
  327. def _whats_different(self, other):
  328. k1 = self.key
  329. k2 = other.key
  330. stack = []
  331. pickup_index = 0
  332. while True:
  333. s1, s2 = k1, k2
  334. for idx in stack:
  335. s1 = s1[idx]
  336. s2 = s2[idx]
  337. for idx, (e1, e2) in enumerate(util.zip_longest(s1, s2)):
  338. if idx < pickup_index:
  339. continue
  340. if e1 != e2:
  341. if isinstance(e1, tuple) and isinstance(e2, tuple):
  342. stack.append(idx)
  343. break
  344. else:
  345. yield "key%s[%d]: %s != %s" % (
  346. "".join("[%d]" % id_ for id_ in stack),
  347. idx,
  348. e1,
  349. e2,
  350. )
  351. else:
  352. pickup_index = stack.pop(-1)
  353. break
  354. def _diff(self, other):
  355. return ", ".join(self._whats_different(other))
  356. def __str__(self):
  357. stack = [self.key]
  358. output = []
  359. sentinel = object()
  360. indent = -1
  361. while stack:
  362. elem = stack.pop(0)
  363. if elem is sentinel:
  364. output.append((" " * (indent * 2)) + "),")
  365. indent -= 1
  366. elif isinstance(elem, tuple):
  367. if not elem:
  368. output.append((" " * ((indent + 1) * 2)) + "()")
  369. else:
  370. indent += 1
  371. stack = list(elem) + [sentinel] + stack
  372. output.append((" " * (indent * 2)) + "(")
  373. else:
  374. if isinstance(elem, HasCacheKey):
  375. repr_ = "<%s object at %s>" % (
  376. type(elem).__name__,
  377. hex(id(elem)),
  378. )
  379. else:
  380. repr_ = repr(elem)
  381. output.append((" " * (indent * 2)) + " " + repr_ + ", ")
  382. return "CacheKey(key=%s)" % ("\n".join(output),)
  383. def _generate_param_dict(self):
  384. """used for testing"""
  385. from .compiler import prefix_anon_map
  386. _anon_map = prefix_anon_map()
  387. return {b.key % _anon_map: b.effective_value for b in self.bindparams}
  388. def _apply_params_to_element(self, original_cache_key, target_element):
  389. translate = {
  390. k.key: v.value
  391. for k, v in zip(original_cache_key.bindparams, self.bindparams)
  392. }
  393. return target_element.params(translate)
  394. def _clone(element, **kw):
  395. return element._clone()
  396. class _CacheKey(ExtendedInternalTraversal):
  397. # very common elements are inlined into the main _get_cache_key() method
  398. # to produce a dramatic savings in Python function call overhead
  399. visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
  400. visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
  401. visit_annotations_key = InternalTraversal.dp_annotations_key
  402. visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
  403. visit_memoized_select_entities = (
  404. InternalTraversal.dp_memoized_select_entities
  405. )
  406. visit_string = (
  407. visit_boolean
  408. ) = visit_operator = visit_plain_obj = CACHE_IN_PLACE
  409. visit_statement_hint_list = CACHE_IN_PLACE
  410. visit_type = STATIC_CACHE_KEY
  411. visit_anon_name = ANON_NAME
  412. visit_propagate_attrs = PROPAGATE_ATTRS
  413. def visit_with_context_options(
  414. self, attrname, obj, parent, anon_map, bindparams
  415. ):
  416. return tuple((fn.__code__, c_key) for fn, c_key in obj)
  417. def visit_inspectable(self, attrname, obj, parent, anon_map, bindparams):
  418. return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
  419. def visit_string_list(self, attrname, obj, parent, anon_map, bindparams):
  420. return tuple(obj)
  421. def visit_multi(self, attrname, obj, parent, anon_map, bindparams):
  422. return (
  423. attrname,
  424. obj._gen_cache_key(anon_map, bindparams)
  425. if isinstance(obj, HasCacheKey)
  426. else obj,
  427. )
  428. def visit_multi_list(self, attrname, obj, parent, anon_map, bindparams):
  429. return (
  430. attrname,
  431. tuple(
  432. elem._gen_cache_key(anon_map, bindparams)
  433. if isinstance(elem, HasCacheKey)
  434. else elem
  435. for elem in obj
  436. ),
  437. )
  438. def visit_has_cache_key_tuples(
  439. self, attrname, obj, parent, anon_map, bindparams
  440. ):
  441. if not obj:
  442. return ()
  443. return (
  444. attrname,
  445. tuple(
  446. tuple(
  447. elem._gen_cache_key(anon_map, bindparams)
  448. for elem in tup_elem
  449. )
  450. for tup_elem in obj
  451. ),
  452. )
  453. def visit_has_cache_key_list(
  454. self, attrname, obj, parent, anon_map, bindparams
  455. ):
  456. if not obj:
  457. return ()
  458. return (
  459. attrname,
  460. tuple(elem._gen_cache_key(anon_map, bindparams) for elem in obj),
  461. )
  462. def visit_executable_options(
  463. self, attrname, obj, parent, anon_map, bindparams
  464. ):
  465. if not obj:
  466. return ()
  467. return (
  468. attrname,
  469. tuple(
  470. elem._gen_cache_key(anon_map, bindparams)
  471. for elem in obj
  472. if elem._is_has_cache_key
  473. ),
  474. )
  475. def visit_inspectable_list(
  476. self, attrname, obj, parent, anon_map, bindparams
  477. ):
  478. return self.visit_has_cache_key_list(
  479. attrname, [inspect(o) for o in obj], parent, anon_map, bindparams
  480. )
  481. def visit_clauseelement_tuples(
  482. self, attrname, obj, parent, anon_map, bindparams
  483. ):
  484. return self.visit_has_cache_key_tuples(
  485. attrname, obj, parent, anon_map, bindparams
  486. )
  487. def visit_fromclause_ordered_set(
  488. self, attrname, obj, parent, anon_map, bindparams
  489. ):
  490. if not obj:
  491. return ()
  492. return (
  493. attrname,
  494. tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
  495. )
  496. def visit_clauseelement_unordered_set(
  497. self, attrname, obj, parent, anon_map, bindparams
  498. ):
  499. if not obj:
  500. return ()
  501. cache_keys = [
  502. elem._gen_cache_key(anon_map, bindparams) for elem in obj
  503. ]
  504. return (
  505. attrname,
  506. tuple(
  507. sorted(cache_keys)
  508. ), # cache keys all start with (id_, class)
  509. )
  510. def visit_named_ddl_element(
  511. self, attrname, obj, parent, anon_map, bindparams
  512. ):
  513. return (attrname, obj.name)
  514. def visit_prefix_sequence(
  515. self, attrname, obj, parent, anon_map, bindparams
  516. ):
  517. if not obj:
  518. return ()
  519. return (
  520. attrname,
  521. tuple(
  522. [
  523. (clause._gen_cache_key(anon_map, bindparams), strval)
  524. for clause, strval in obj
  525. ]
  526. ),
  527. )
  528. def visit_setup_join_tuple(
  529. self, attrname, obj, parent, anon_map, bindparams
  530. ):
  531. is_legacy = "legacy" in attrname
  532. return tuple(
  533. (
  534. target
  535. if is_legacy and isinstance(target, str)
  536. else target._gen_cache_key(anon_map, bindparams),
  537. onclause
  538. if is_legacy and isinstance(onclause, str)
  539. else onclause._gen_cache_key(anon_map, bindparams)
  540. if onclause is not None
  541. else None,
  542. from_._gen_cache_key(anon_map, bindparams)
  543. if from_ is not None
  544. else None,
  545. tuple([(key, flags[key]) for key in sorted(flags)]),
  546. )
  547. for (target, onclause, from_, flags) in obj
  548. )
  549. def visit_table_hint_list(
  550. self, attrname, obj, parent, anon_map, bindparams
  551. ):
  552. if not obj:
  553. return ()
  554. return (
  555. attrname,
  556. tuple(
  557. [
  558. (
  559. clause._gen_cache_key(anon_map, bindparams),
  560. dialect_name,
  561. text,
  562. )
  563. for (clause, dialect_name), text in obj.items()
  564. ]
  565. ),
  566. )
  567. def visit_plain_dict(self, attrname, obj, parent, anon_map, bindparams):
  568. return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
  569. def visit_dialect_options(
  570. self, attrname, obj, parent, anon_map, bindparams
  571. ):
  572. return (
  573. attrname,
  574. tuple(
  575. (
  576. dialect_name,
  577. tuple(
  578. [
  579. (key, obj[dialect_name][key])
  580. for key in sorted(obj[dialect_name])
  581. ]
  582. ),
  583. )
  584. for dialect_name in sorted(obj)
  585. ),
  586. )
  587. def visit_string_clauseelement_dict(
  588. self, attrname, obj, parent, anon_map, bindparams
  589. ):
  590. return (
  591. attrname,
  592. tuple(
  593. (key, obj[key]._gen_cache_key(anon_map, bindparams))
  594. for key in sorted(obj)
  595. ),
  596. )
  597. def visit_string_multi_dict(
  598. self, attrname, obj, parent, anon_map, bindparams
  599. ):
  600. return (
  601. attrname,
  602. tuple(
  603. (
  604. key,
  605. value._gen_cache_key(anon_map, bindparams)
  606. if isinstance(value, HasCacheKey)
  607. else value,
  608. )
  609. for key, value in [(key, obj[key]) for key in sorted(obj)]
  610. ),
  611. )
  612. def visit_fromclause_canonical_column_collection(
  613. self, attrname, obj, parent, anon_map, bindparams
  614. ):
  615. # inlining into the internals of ColumnCollection
  616. return (
  617. attrname,
  618. tuple(
  619. col._gen_cache_key(anon_map, bindparams)
  620. for k, col in obj._collection
  621. ),
  622. )
  623. def visit_unknown_structure(
  624. self, attrname, obj, parent, anon_map, bindparams
  625. ):
  626. anon_map[NO_CACHE] = True
  627. return ()
  628. def visit_dml_ordered_values(
  629. self, attrname, obj, parent, anon_map, bindparams
  630. ):
  631. return (
  632. attrname,
  633. tuple(
  634. (
  635. key._gen_cache_key(anon_map, bindparams)
  636. if hasattr(key, "__clause_element__")
  637. else key,
  638. value._gen_cache_key(anon_map, bindparams),
  639. )
  640. for key, value in obj
  641. ),
  642. )
  643. def visit_dml_values(self, attrname, obj, parent, anon_map, bindparams):
  644. if py37:
  645. # in py37 we can assume two dictionaries created in the same
  646. # insert ordering will retain that sorting
  647. return (
  648. attrname,
  649. tuple(
  650. (
  651. k._gen_cache_key(anon_map, bindparams)
  652. if hasattr(k, "__clause_element__")
  653. else k,
  654. obj[k]._gen_cache_key(anon_map, bindparams),
  655. )
  656. for k in obj
  657. ),
  658. )
  659. else:
  660. expr_values = {k for k in obj if hasattr(k, "__clause_element__")}
  661. if expr_values:
  662. # expr values can't be sorted deterministically right now,
  663. # so no cache
  664. anon_map[NO_CACHE] = True
  665. return ()
  666. str_values = expr_values.symmetric_difference(obj)
  667. return (
  668. attrname,
  669. tuple(
  670. (k, obj[k]._gen_cache_key(anon_map, bindparams))
  671. for k in sorted(str_values)
  672. ),
  673. )
  674. def visit_dml_multi_values(
  675. self, attrname, obj, parent, anon_map, bindparams
  676. ):
  677. # multivalues are simply not cacheable right now
  678. anon_map[NO_CACHE] = True
  679. return ()
  680. _cache_key_traversal_visitor = _CacheKey()
  681. class HasCopyInternals(object):
  682. def _clone(self, **kw):
  683. raise NotImplementedError()
  684. def _copy_internals(self, omit_attrs=(), **kw):
  685. """Reassign internal elements to be clones of themselves.
  686. Called during a copy-and-traverse operation on newly
  687. shallow-copied elements to create a deep copy.
  688. The given clone function should be used, which may be applying
  689. additional transformations to the element (i.e. replacement
  690. traversal, cloned traversal, annotations).
  691. """
  692. try:
  693. traverse_internals = self._traverse_internals
  694. except AttributeError:
  695. # user-defined classes may not have a _traverse_internals
  696. return
  697. for attrname, obj, meth in _copy_internals.run_generated_dispatch(
  698. self, traverse_internals, "_generated_copy_internals_traversal"
  699. ):
  700. if attrname in omit_attrs:
  701. continue
  702. if obj is not None:
  703. result = meth(attrname, self, obj, **kw)
  704. if result is not None:
  705. setattr(self, attrname, result)
  706. class _CopyInternals(InternalTraversal):
  707. """Generate a _copy_internals internal traversal dispatch for classes
  708. with a _traverse_internals collection."""
  709. def visit_clauseelement(
  710. self, attrname, parent, element, clone=_clone, **kw
  711. ):
  712. return clone(element, **kw)
  713. def visit_clauseelement_list(
  714. self, attrname, parent, element, clone=_clone, **kw
  715. ):
  716. return [clone(clause, **kw) for clause in element]
  717. def visit_clauseelement_tuple(
  718. self, attrname, parent, element, clone=_clone, **kw
  719. ):
  720. return tuple([clone(clause, **kw) for clause in element])
  721. def visit_executable_options(
  722. self, attrname, parent, element, clone=_clone, **kw
  723. ):
  724. return tuple([clone(clause, **kw) for clause in element])
  725. def visit_clauseelement_unordered_set(
  726. self, attrname, parent, element, clone=_clone, **kw
  727. ):
  728. return {clone(clause, **kw) for clause in element}
  729. def visit_clauseelement_tuples(
  730. self, attrname, parent, element, clone=_clone, **kw
  731. ):
  732. return [
  733. tuple(clone(tup_elem, **kw) for tup_elem in elem)
  734. for elem in element
  735. ]
  736. def visit_string_clauseelement_dict(
  737. self, attrname, parent, element, clone=_clone, **kw
  738. ):
  739. return dict(
  740. (key, clone(value, **kw)) for key, value in element.items()
  741. )
  742. def visit_setup_join_tuple(
  743. self, attrname, parent, element, clone=_clone, **kw
  744. ):
  745. return tuple(
  746. (
  747. clone(target, **kw) if target is not None else None,
  748. clone(onclause, **kw) if onclause is not None else None,
  749. clone(from_, **kw) if from_ is not None else None,
  750. flags,
  751. )
  752. for (target, onclause, from_, flags) in element
  753. )
  754. def visit_memoized_select_entities(self, attrname, parent, element, **kw):
  755. return self.visit_clauseelement_tuple(attrname, parent, element, **kw)
  756. def visit_dml_ordered_values(
  757. self, attrname, parent, element, clone=_clone, **kw
  758. ):
  759. # sequence of 2-tuples
  760. return [
  761. (
  762. clone(key, **kw)
  763. if hasattr(key, "__clause_element__")
  764. else key,
  765. clone(value, **kw),
  766. )
  767. for key, value in element
  768. ]
  769. def visit_dml_values(self, attrname, parent, element, clone=_clone, **kw):
  770. return {
  771. (
  772. clone(key, **kw) if hasattr(key, "__clause_element__") else key
  773. ): clone(value, **kw)
  774. for key, value in element.items()
  775. }
  776. def visit_dml_multi_values(
  777. self, attrname, parent, element, clone=_clone, **kw
  778. ):
  779. # sequence of sequences, each sequence contains a list/dict/tuple
  780. def copy(elem):
  781. if isinstance(elem, (list, tuple)):
  782. return [
  783. clone(value, **kw)
  784. if hasattr(value, "__clause_element__")
  785. else value
  786. for value in elem
  787. ]
  788. elif isinstance(elem, dict):
  789. return {
  790. (
  791. clone(key, **kw)
  792. if hasattr(key, "__clause_element__")
  793. else key
  794. ): (
  795. clone(value, **kw)
  796. if hasattr(value, "__clause_element__")
  797. else value
  798. )
  799. for key, value in elem.items()
  800. }
  801. else:
  802. # TODO: use abc classes
  803. assert False
  804. return [
  805. [copy(sub_element) for sub_element in sequence]
  806. for sequence in element
  807. ]
  808. def visit_propagate_attrs(
  809. self, attrname, parent, element, clone=_clone, **kw
  810. ):
  811. return element
  812. _copy_internals = _CopyInternals()
  813. def _flatten_clauseelement(element):
  814. while hasattr(element, "__clause_element__") and not getattr(
  815. element, "is_clause_element", False
  816. ):
  817. element = element.__clause_element__()
  818. return element
  819. class _GetChildren(InternalTraversal):
  820. """Generate a _children_traversal internal traversal dispatch for classes
  821. with a _traverse_internals collection."""
  822. def visit_has_cache_key(self, element, **kw):
  823. # the GetChildren traversal refers explicitly to ClauseElement
  824. # structures. Within these, a plain HasCacheKey is not a
  825. # ClauseElement, so don't include these.
  826. return ()
  827. def visit_clauseelement(self, element, **kw):
  828. return (element,)
  829. def visit_clauseelement_list(self, element, **kw):
  830. return element
  831. def visit_clauseelement_tuple(self, element, **kw):
  832. return element
  833. def visit_clauseelement_tuples(self, element, **kw):
  834. return itertools.chain.from_iterable(element)
  835. def visit_fromclause_canonical_column_collection(self, element, **kw):
  836. return ()
  837. def visit_string_clauseelement_dict(self, element, **kw):
  838. return element.values()
  839. def visit_fromclause_ordered_set(self, element, **kw):
  840. return element
  841. def visit_clauseelement_unordered_set(self, element, **kw):
  842. return element
  843. def visit_setup_join_tuple(self, element, **kw):
  844. for (target, onclause, from_, flags) in element:
  845. if from_ is not None:
  846. yield from_
  847. if not isinstance(target, str):
  848. yield _flatten_clauseelement(target)
  849. if onclause is not None and not isinstance(onclause, str):
  850. yield _flatten_clauseelement(onclause)
  851. def visit_memoized_select_entities(self, element, **kw):
  852. return self.visit_clauseelement_tuple(element, **kw)
  853. def visit_dml_ordered_values(self, element, **kw):
  854. for k, v in element:
  855. if hasattr(k, "__clause_element__"):
  856. yield k
  857. yield v
  858. def visit_dml_values(self, element, **kw):
  859. expr_values = {k for k in element if hasattr(k, "__clause_element__")}
  860. str_values = expr_values.symmetric_difference(element)
  861. for k in sorted(str_values):
  862. yield element[k]
  863. for k in expr_values:
  864. yield k
  865. yield element[k]
  866. def visit_dml_multi_values(self, element, **kw):
  867. return ()
  868. def visit_propagate_attrs(self, element, **kw):
  869. return ()
  870. _get_children = _GetChildren()
  871. @util.preload_module("sqlalchemy.sql.elements")
  872. def _resolve_name_for_compare(element, name, anon_map, **kw):
  873. if isinstance(name, util.preloaded.sql_elements._anonymous_label):
  874. name = name.apply_map(anon_map)
  875. return name
  876. class anon_map(dict):
  877. """A map that creates new keys for missing key access.
  878. Produces an incrementing sequence given a series of unique keys.
  879. This is similar to the compiler prefix_anon_map class although simpler.
  880. Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
  881. is otherwise usually used for this type of operation.
  882. """
  883. def __init__(self):
  884. self.index = 0
  885. def __missing__(self, key):
  886. self[key] = val = str(self.index)
  887. self.index += 1
  888. return val
  889. class TraversalComparatorStrategy(InternalTraversal, util.MemoizedSlots):
  890. __slots__ = "stack", "cache", "anon_map"
  891. def __init__(self):
  892. self.stack = deque()
  893. self.cache = set()
  894. def _memoized_attr_anon_map(self):
  895. return (anon_map(), anon_map())
  896. def compare(self, obj1, obj2, **kw):
  897. stack = self.stack
  898. cache = self.cache
  899. compare_annotations = kw.get("compare_annotations", False)
  900. stack.append((obj1, obj2))
  901. while stack:
  902. left, right = stack.popleft()
  903. if left is right:
  904. continue
  905. elif left is None or right is None:
  906. # we know they are different so no match
  907. return False
  908. elif (left, right) in cache:
  909. continue
  910. cache.add((left, right))
  911. visit_name = left.__visit_name__
  912. if visit_name != right.__visit_name__:
  913. return False
  914. meth = getattr(self, "compare_%s" % visit_name, None)
  915. if meth:
  916. attributes_compared = meth(left, right, **kw)
  917. if attributes_compared is COMPARE_FAILED:
  918. return False
  919. elif attributes_compared is SKIP_TRAVERSE:
  920. continue
  921. # attributes_compared is returned as a list of attribute
  922. # names that were "handled" by the comparison method above.
  923. # remaining attribute names in the _traverse_internals
  924. # will be compared.
  925. else:
  926. attributes_compared = ()
  927. for (
  928. (left_attrname, left_visit_sym),
  929. (right_attrname, right_visit_sym),
  930. ) in util.zip_longest(
  931. left._traverse_internals,
  932. right._traverse_internals,
  933. fillvalue=(None, None),
  934. ):
  935. if not compare_annotations and (
  936. (left_attrname == "_annotations")
  937. or (right_attrname == "_annotations")
  938. ):
  939. continue
  940. if (
  941. left_attrname != right_attrname
  942. or left_visit_sym is not right_visit_sym
  943. ):
  944. return False
  945. elif left_attrname in attributes_compared:
  946. continue
  947. dispatch = self.dispatch(left_visit_sym)
  948. left_child = operator.attrgetter(left_attrname)(left)
  949. right_child = operator.attrgetter(right_attrname)(right)
  950. if left_child is None:
  951. if right_child is not None:
  952. return False
  953. else:
  954. continue
  955. elif right_child is None:
  956. return False
  957. comparison = dispatch(
  958. left_attrname, left, left_child, right, right_child, **kw
  959. )
  960. if comparison is COMPARE_FAILED:
  961. return False
  962. return True
  963. def compare_inner(self, obj1, obj2, **kw):
  964. comparator = self.__class__()
  965. return comparator.compare(obj1, obj2, **kw)
  966. def visit_has_cache_key(
  967. self, attrname, left_parent, left, right_parent, right, **kw
  968. ):
  969. if left._gen_cache_key(self.anon_map[0], []) != right._gen_cache_key(
  970. self.anon_map[1], []
  971. ):
  972. return COMPARE_FAILED
  973. def visit_propagate_attrs(
  974. self, attrname, left_parent, left, right_parent, right, **kw
  975. ):
  976. return self.compare_inner(
  977. left.get("plugin_subject", None), right.get("plugin_subject", None)
  978. )
  979. def visit_has_cache_key_list(
  980. self, attrname, left_parent, left, right_parent, right, **kw
  981. ):
  982. for l, r in util.zip_longest(left, right, fillvalue=None):
  983. if l._gen_cache_key(self.anon_map[0], []) != r._gen_cache_key(
  984. self.anon_map[1], []
  985. ):
  986. return COMPARE_FAILED
  987. def visit_executable_options(
  988. self, attrname, left_parent, left, right_parent, right, **kw
  989. ):
  990. for l, r in util.zip_longest(left, right, fillvalue=None):
  991. if (
  992. l._gen_cache_key(self.anon_map[0], [])
  993. if l._is_has_cache_key
  994. else l
  995. ) != (
  996. r._gen_cache_key(self.anon_map[1], [])
  997. if r._is_has_cache_key
  998. else r
  999. ):
  1000. return COMPARE_FAILED
  1001. def visit_clauseelement(
  1002. self, attrname, left_parent, left, right_parent, right, **kw
  1003. ):
  1004. self.stack.append((left, right))
  1005. def visit_fromclause_canonical_column_collection(
  1006. self, attrname, left_parent, left, right_parent, right, **kw
  1007. ):
  1008. for lcol, rcol in util.zip_longest(left, right, fillvalue=None):
  1009. self.stack.append((lcol, rcol))
  1010. def visit_fromclause_derived_column_collection(
  1011. self, attrname, left_parent, left, right_parent, right, **kw
  1012. ):
  1013. pass
  1014. def visit_string_clauseelement_dict(
  1015. self, attrname, left_parent, left, right_parent, right, **kw
  1016. ):
  1017. for lstr, rstr in util.zip_longest(
  1018. sorted(left), sorted(right), fillvalue=None
  1019. ):
  1020. if lstr != rstr:
  1021. return COMPARE_FAILED
  1022. self.stack.append((left[lstr], right[rstr]))
  1023. def visit_clauseelement_tuples(
  1024. self, attrname, left_parent, left, right_parent, right, **kw
  1025. ):
  1026. for ltup, rtup in util.zip_longest(left, right, fillvalue=None):
  1027. if ltup is None or rtup is None:
  1028. return COMPARE_FAILED
  1029. for l, r in util.zip_longest(ltup, rtup, fillvalue=None):
  1030. self.stack.append((l, r))
  1031. def visit_clauseelement_list(
  1032. self, attrname, left_parent, left, right_parent, right, **kw
  1033. ):
  1034. for l, r in util.zip_longest(left, right, fillvalue=None):
  1035. self.stack.append((l, r))
  1036. def visit_clauseelement_tuple(
  1037. self, attrname, left_parent, left, right_parent, right, **kw
  1038. ):
  1039. for l, r in util.zip_longest(left, right, fillvalue=None):
  1040. self.stack.append((l, r))
  1041. def _compare_unordered_sequences(self, seq1, seq2, **kw):
  1042. if seq1 is None:
  1043. return seq2 is None
  1044. completed = set()
  1045. for clause in seq1:
  1046. for other_clause in set(seq2).difference(completed):
  1047. if self.compare_inner(clause, other_clause, **kw):
  1048. completed.add(other_clause)
  1049. break
  1050. return len(completed) == len(seq1) == len(seq2)
  1051. def visit_clauseelement_unordered_set(
  1052. self, attrname, left_parent, left, right_parent, right, **kw
  1053. ):
  1054. return self._compare_unordered_sequences(left, right, **kw)
  1055. def visit_fromclause_ordered_set(
  1056. self, attrname, left_parent, left, right_parent, right, **kw
  1057. ):
  1058. for l, r in util.zip_longest(left, right, fillvalue=None):
  1059. self.stack.append((l, r))
  1060. def visit_string(
  1061. self, attrname, left_parent, left, right_parent, right, **kw
  1062. ):
  1063. return left == right
  1064. def visit_string_list(
  1065. self, attrname, left_parent, left, right_parent, right, **kw
  1066. ):
  1067. return left == right
  1068. def visit_anon_name(
  1069. self, attrname, left_parent, left, right_parent, right, **kw
  1070. ):
  1071. return _resolve_name_for_compare(
  1072. left_parent, left, self.anon_map[0], **kw
  1073. ) == _resolve_name_for_compare(
  1074. right_parent, right, self.anon_map[1], **kw
  1075. )
  1076. def visit_boolean(
  1077. self, attrname, left_parent, left, right_parent, right, **kw
  1078. ):
  1079. return left == right
  1080. def visit_operator(
  1081. self, attrname, left_parent, left, right_parent, right, **kw
  1082. ):
  1083. return left == right
  1084. def visit_type(
  1085. self, attrname, left_parent, left, right_parent, right, **kw
  1086. ):
  1087. return left._compare_type_affinity(right)
  1088. def visit_plain_dict(
  1089. self, attrname, left_parent, left, right_parent, right, **kw
  1090. ):
  1091. return left == right
  1092. def visit_dialect_options(
  1093. self, attrname, left_parent, left, right_parent, right, **kw
  1094. ):
  1095. return left == right
  1096. def visit_annotations_key(
  1097. self, attrname, left_parent, left, right_parent, right, **kw
  1098. ):
  1099. if left and right:
  1100. return (
  1101. left_parent._annotations_cache_key
  1102. == right_parent._annotations_cache_key
  1103. )
  1104. else:
  1105. return left == right
  1106. def visit_with_context_options(
  1107. self, attrname, left_parent, left, right_parent, right, **kw
  1108. ):
  1109. return tuple((fn.__code__, c_key) for fn, c_key in left) == tuple(
  1110. (fn.__code__, c_key) for fn, c_key in right
  1111. )
  1112. def visit_plain_obj(
  1113. self, attrname, left_parent, left, right_parent, right, **kw
  1114. ):
  1115. return left == right
  1116. def visit_named_ddl_element(
  1117. self, attrname, left_parent, left, right_parent, right, **kw
  1118. ):
  1119. if left is None:
  1120. if right is not None:
  1121. return COMPARE_FAILED
  1122. return left.name == right.name
  1123. def visit_prefix_sequence(
  1124. self, attrname, left_parent, left, right_parent, right, **kw
  1125. ):
  1126. for (l_clause, l_str), (r_clause, r_str) in util.zip_longest(
  1127. left, right, fillvalue=(None, None)
  1128. ):
  1129. if l_str != r_str:
  1130. return COMPARE_FAILED
  1131. else:
  1132. self.stack.append((l_clause, r_clause))
  1133. def visit_setup_join_tuple(
  1134. self, attrname, left_parent, left, right_parent, right, **kw
  1135. ):
  1136. # TODO: look at attrname for "legacy_join" and use different structure
  1137. for (
  1138. (l_target, l_onclause, l_from, l_flags),
  1139. (r_target, r_onclause, r_from, r_flags),
  1140. ) in util.zip_longest(left, right, fillvalue=(None, None, None, None)):
  1141. if l_flags != r_flags:
  1142. return COMPARE_FAILED
  1143. self.stack.append((l_target, r_target))
  1144. self.stack.append((l_onclause, r_onclause))
  1145. self.stack.append((l_from, r_from))
  1146. def visit_memoized_select_entities(
  1147. self, attrname, left_parent, left, right_parent, right, **kw
  1148. ):
  1149. return self.visit_clauseelement_tuple(
  1150. attrname, left_parent, left, right_parent, right, **kw
  1151. )
  1152. def visit_table_hint_list(
  1153. self, attrname, left_parent, left, right_parent, right, **kw
  1154. ):
  1155. left_keys = sorted(left, key=lambda elem: (elem[0].fullname, elem[1]))
  1156. right_keys = sorted(
  1157. right, key=lambda elem: (elem[0].fullname, elem[1])
  1158. )
  1159. for (ltable, ldialect), (rtable, rdialect) in util.zip_longest(
  1160. left_keys, right_keys, fillvalue=(None, None)
  1161. ):
  1162. if ldialect != rdialect:
  1163. return COMPARE_FAILED
  1164. elif left[(ltable, ldialect)] != right[(rtable, rdialect)]:
  1165. return COMPARE_FAILED
  1166. else:
  1167. self.stack.append((ltable, rtable))
  1168. def visit_statement_hint_list(
  1169. self, attrname, left_parent, left, right_parent, right, **kw
  1170. ):
  1171. return left == right
  1172. def visit_unknown_structure(
  1173. self, attrname, left_parent, left, right_parent, right, **kw
  1174. ):
  1175. raise NotImplementedError()
  1176. def visit_dml_ordered_values(
  1177. self, attrname, left_parent, left, right_parent, right, **kw
  1178. ):
  1179. # sequence of tuple pairs
  1180. for (lk, lv), (rk, rv) in util.zip_longest(
  1181. left, right, fillvalue=(None, None)
  1182. ):
  1183. if not self._compare_dml_values_or_ce(lk, rk, **kw):
  1184. return COMPARE_FAILED
  1185. def _compare_dml_values_or_ce(self, lv, rv, **kw):
  1186. lvce = hasattr(lv, "__clause_element__")
  1187. rvce = hasattr(rv, "__clause_element__")
  1188. if lvce != rvce:
  1189. return False
  1190. elif lvce and not self.compare_inner(lv, rv, **kw):
  1191. return False
  1192. elif not lvce and lv != rv:
  1193. return False
  1194. elif not self.compare_inner(lv, rv, **kw):
  1195. return False
  1196. return True
  1197. def visit_dml_values(
  1198. self, attrname, left_parent, left, right_parent, right, **kw
  1199. ):
  1200. if left is None or right is None or len(left) != len(right):
  1201. return COMPARE_FAILED
  1202. if isinstance(left, collections_abc.Sequence):
  1203. for lv, rv in zip(left, right):
  1204. if not self._compare_dml_values_or_ce(lv, rv, **kw):
  1205. return COMPARE_FAILED
  1206. elif isinstance(right, collections_abc.Sequence):
  1207. return COMPARE_FAILED
  1208. elif py37:
  1209. # dictionaries guaranteed to support insert ordering in
  1210. # py37 so that we can compare the keys in order. without
  1211. # this, we can't compare SQL expression keys because we don't
  1212. # know which key is which
  1213. for (lk, lv), (rk, rv) in zip(left.items(), right.items()):
  1214. if not self._compare_dml_values_or_ce(lk, rk, **kw):
  1215. return COMPARE_FAILED
  1216. if not self._compare_dml_values_or_ce(lv, rv, **kw):
  1217. return COMPARE_FAILED
  1218. else:
  1219. for lk in left:
  1220. lv = left[lk]
  1221. if lk not in right:
  1222. return COMPARE_FAILED
  1223. rv = right[lk]
  1224. if not self._compare_dml_values_or_ce(lv, rv, **kw):
  1225. return COMPARE_FAILED
  1226. def visit_dml_multi_values(
  1227. self, attrname, left_parent, left, right_parent, right, **kw
  1228. ):
  1229. for lseq, rseq in util.zip_longest(left, right, fillvalue=None):
  1230. if lseq is None or rseq is None:
  1231. return COMPARE_FAILED
  1232. for ld, rd in util.zip_longest(lseq, rseq, fillvalue=None):
  1233. if (
  1234. self.visit_dml_values(
  1235. attrname, left_parent, ld, right_parent, rd, **kw
  1236. )
  1237. is COMPARE_FAILED
  1238. ):
  1239. return COMPARE_FAILED
  1240. def compare_clauselist(self, left, right, **kw):
  1241. if left.operator is right.operator:
  1242. if operators.is_associative(left.operator):
  1243. if self._compare_unordered_sequences(
  1244. left.clauses, right.clauses, **kw
  1245. ):
  1246. return ["operator", "clauses"]
  1247. else:
  1248. return COMPARE_FAILED
  1249. else:
  1250. return ["operator"]
  1251. else:
  1252. return COMPARE_FAILED
  1253. def compare_binary(self, left, right, **kw):
  1254. if left.operator == right.operator:
  1255. if operators.is_commutative(left.operator):
  1256. if (
  1257. self.compare_inner(left.left, right.left, **kw)
  1258. and self.compare_inner(left.right, right.right, **kw)
  1259. ) or (
  1260. self.compare_inner(left.left, right.right, **kw)
  1261. and self.compare_inner(left.right, right.left, **kw)
  1262. ):
  1263. return ["operator", "negate", "left", "right"]
  1264. else:
  1265. return COMPARE_FAILED
  1266. else:
  1267. return ["operator", "negate"]
  1268. else:
  1269. return COMPARE_FAILED
  1270. def compare_bindparam(self, left, right, **kw):
  1271. compare_keys = kw.pop("compare_keys", True)
  1272. compare_values = kw.pop("compare_values", True)
  1273. if compare_values:
  1274. omit = []
  1275. else:
  1276. # this means, "skip these, we already compared"
  1277. omit = ["callable", "value"]
  1278. if not compare_keys:
  1279. omit.append("key")
  1280. return omit
  1281. class ColIdentityComparatorStrategy(TraversalComparatorStrategy):
  1282. def compare_column_element(
  1283. self, left, right, use_proxies=True, equivalents=(), **kw
  1284. ):
  1285. """Compare ColumnElements using proxies and equivalent collections.
  1286. This is a comparison strategy specific to the ORM.
  1287. """
  1288. to_compare = (right,)
  1289. if equivalents and right in equivalents:
  1290. to_compare = equivalents[right].union(to_compare)
  1291. for oth in to_compare:
  1292. if use_proxies and left.shares_lineage(oth):
  1293. return SKIP_TRAVERSE
  1294. elif hash(left) == hash(right):
  1295. return SKIP_TRAVERSE
  1296. else:
  1297. return COMPARE_FAILED
  1298. def compare_column(self, left, right, **kw):
  1299. return self.compare_column_element(left, right, **kw)
  1300. def compare_label(self, left, right, **kw):
  1301. return self.compare_column_element(left, right, **kw)
  1302. def compare_table(self, left, right, **kw):
  1303. # tables compare on identity, since it's not really feasible to
  1304. # compare them column by column with the above rules
  1305. return SKIP_TRAVERSE if left is right else COMPARE_FAILED