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.2.7, created at 2023-06-07 06:35 +0000

1from collections import deque 

2from collections import namedtuple 

3import itertools 

4import operator 

5 

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 

14 

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") 

24 

25 

26def compare(obj1, obj2, **kw): 

27 if kw.get("use_proxies", False): 

28 strategy = ColIdentityComparatorStrategy() 

29 else: 

30 strategy = TraversalComparatorStrategy() 

31 

32 return strategy.compare(obj1, obj2, **kw) 

33 

34 

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 ) 

49 

50 

51class HasCacheKey(object): 

52 """Mixin for objects which can produce a cache key. 

53 

54 .. seealso:: 

55 

56 :class:`.CacheKey` 

57 

58 :ref:`sql_caching` 

59 

60 """ 

61 

62 _cache_key_traversal = NO_CACHE 

63 

64 _is_has_cache_key = True 

65 

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. 

69 

70 Currently applies to the DDLElement hierarchy which does not implement 

71 caching. 

72 

73 """ 

74 

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. 

78 

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. 

83 

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. 

87 

88 .. seealso:: 

89 

90 :ref:`compilerext_caching` - General guideslines for setting the 

91 :attr:`.HasCacheKey.inherit_cache` attribute for third-party or user 

92 defined SQL constructs. 

93 

94 """ 

95 

96 __slots__ = () 

97 

98 @classmethod 

99 def _generate_cache_attrs(cls): 

100 """generate cache key dispatcher for a new class. 

101 

102 This sets the _generated_cache_key_traversal attribute once called 

103 so should only be called once per class. 

104 

105 """ 

106 inherit_cache = cls.__dict__.get("inherit_cache", None) 

107 inherit = bool(inherit_cache) 

108 

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 

117 

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 

154 

155 return _cache_key_traversal_visitor.generate_dispatch( 

156 cls, _cache_key_traversal, "_generated_cache_key_traversal" 

157 ) 

158 

159 @util.preload_module("sqlalchemy.sql.elements") 

160 def _gen_cache_key(self, anon_map, bindparams): 

161 """return an optional cache key. 

162 

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. 

167 

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. 

173 

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. 

177 

178 """ 

179 

180 idself = id(self) 

181 cls = self.__class__ 

182 

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 

190 

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() 

199 

200 if dispatcher is NO_CACHE: 

201 anon_map[NO_CACHE] = True 

202 return None 

203 

204 result = (id_, cls) 

205 

206 # inline of _cache_key_traversal_visitor.run_generated_dispatch() 

207 

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 

214 

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 ) 

231 

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 

254 

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 

279 

280 def _generate_cache_key(self): 

281 """return a cache key. 

282 

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. 

287 

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. 

293 

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. 

303 

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. 

309 

310 """ 

311 

312 bindparams = [] 

313 

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) 

320 

321 @classmethod 

322 def _generate_cache_key_for_object(cls, obj): 

323 bindparams = [] 

324 

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) 

331 

332 

333class MemoizedHasCacheKey(HasCacheKey, HasMemoized): 

334 @HasMemoized.memoized_instancemethod 

335 def _generate_cache_key(self): 

336 return HasCacheKey._generate_cache_key(self) 

337 

338 

339class CacheKey(namedtuple("CacheKey", ["key", "bindparams"])): 

340 """The key used to identify a SQL statement construct in the 

341 SQL compilation cache. 

342 

343 .. seealso:: 

344 

345 :ref:`sql_caching` 

346 

347 """ 

348 

349 def __hash__(self): 

350 """CacheKey itself is not hashable - hash the .key portion""" 

351 

352 return None 

353 

354 def to_offline_string(self, statement_cache, statement, parameters): 

355 """Generate an "offline string" form of this :class:`.CacheKey` 

356 

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. 

362 

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. 

367 

368 

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] 

374 

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 ) 

382 

383 return repr((sql_str, param_tuple)) 

384 

385 def __eq__(self, other): 

386 return self.key == other.key 

387 

388 @classmethod 

389 def _diff_tuples(cls, left, right): 

390 ck1 = CacheKey(left, []) 

391 ck2 = CacheKey(right, []) 

392 return ck1._diff(ck2) 

393 

394 def _whats_different(self, other): 

395 

396 k1 = self.key 

397 k2 = other.key 

398 

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] 

406 

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 

424 

425 def _diff(self, other): 

426 return ", ".join(self._whats_different(other)) 

427 

428 def __str__(self): 

429 stack = [self.key] 

430 

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_ + ", ") 

455 

456 return "CacheKey(key=%s)" % ("\n".join(output),) 

457 

458 def _generate_param_dict(self): 

459 """used for testing""" 

460 

461 from .compiler import prefix_anon_map 

462 

463 _anon_map = prefix_anon_map() 

464 return {b.key % _anon_map: b.effective_value for b in self.bindparams} 

465 

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 } 

471 

472 return target_element.params(translate) 

473 

474 

475def _clone(element, **kw): 

476 return element._clone() 

477 

478 

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 

482 

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 ) 

490 

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 

497 

498 visit_propagate_attrs = PROPAGATE_ATTRS 

499 

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) 

504 

505 def visit_inspectable(self, attrname, obj, parent, anon_map, bindparams): 

506 return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams)) 

507 

508 def visit_string_list(self, attrname, obj, parent, anon_map, bindparams): 

509 return tuple(obj) 

510 

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 ) 

518 

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 ) 

529 

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 ) 

545 

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 ) 

555 

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 ) 

569 

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 ) 

576 

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 ) 

583 

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 ) 

593 

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 ) 

608 

609 def visit_named_ddl_element( 

610 self, attrname, obj, parent, anon_map, bindparams 

611 ): 

612 return (attrname, obj.name) 

613 

614 def visit_prefix_sequence( 

615 self, attrname, obj, parent, anon_map, bindparams 

616 ): 

617 if not obj: 

618 return () 

619 

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 ) 

629 

630 def visit_setup_join_tuple( 

631 self, attrname, obj, parent, anon_map, bindparams 

632 ): 

633 is_legacy = "legacy" in attrname 

634 

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 ) 

652 

653 def visit_table_hint_list( 

654 self, attrname, obj, parent, anon_map, bindparams 

655 ): 

656 if not obj: 

657 return () 

658 

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 ) 

672 

673 def visit_plain_dict(self, attrname, obj, parent, anon_map, bindparams): 

674 return (attrname, tuple([(key, obj[key]) for key in sorted(obj)])) 

675 

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 ) 

694 

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 ) 

705 

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 ) 

721 

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 ) 

733 

734 def visit_unknown_structure( 

735 self, attrname, obj, parent, anon_map, bindparams 

736 ): 

737 anon_map[NO_CACHE] = True 

738 return () 

739 

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 ) 

755 

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 () 

779 

780 str_values = expr_values.symmetric_difference(obj) 

781 

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 ) 

789 

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 () 

796 

797 

798_cache_key_traversal_visitor = _CacheKey() 

799 

800 

801class HasCopyInternals(object): 

802 def _clone(self, **kw): 

803 raise NotImplementedError() 

804 

805 def _copy_internals(self, omit_attrs=(), **kw): 

806 """Reassign internal elements to be clones of themselves. 

807 

808 Called during a copy-and-traverse operation on newly 

809 shallow-copied elements to create a deep copy. 

810 

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). 

814 

815 """ 

816 

817 try: 

818 traverse_internals = self._traverse_internals 

819 except AttributeError: 

820 # user-defined classes may not have a _traverse_internals 

821 return 

822 

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 

828 

829 if obj is not None: 

830 result = meth(attrname, self, obj, **kw) 

831 if result is not None: 

832 setattr(self, attrname, result) 

833 

834 

835class _CopyInternals(InternalTraversal): 

836 """Generate a _copy_internals internal traversal dispatch for classes 

837 with a _traverse_internals collection.""" 

838 

839 def visit_clauseelement( 

840 self, attrname, parent, element, clone=_clone, **kw 

841 ): 

842 return clone(element, **kw) 

843 

844 def visit_clauseelement_list( 

845 self, attrname, parent, element, clone=_clone, **kw 

846 ): 

847 return [clone(clause, **kw) for clause in element] 

848 

849 def visit_clauseelement_tuple( 

850 self, attrname, parent, element, clone=_clone, **kw 

851 ): 

852 return tuple([clone(clause, **kw) for clause in element]) 

853 

854 def visit_executable_options( 

855 self, attrname, parent, element, clone=_clone, **kw 

856 ): 

857 return tuple([clone(clause, **kw) for clause in element]) 

858 

859 def visit_clauseelement_unordered_set( 

860 self, attrname, parent, element, clone=_clone, **kw 

861 ): 

862 return {clone(clause, **kw) for clause in element} 

863 

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 ] 

871 

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 ) 

878 

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 ) 

891 

892 def visit_memoized_select_entities(self, attrname, parent, element, **kw): 

893 return self.visit_clauseelement_tuple(attrname, parent, element, **kw) 

894 

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 ] 

908 

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 } 

916 

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 

921 

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 

946 

947 return [ 

948 [copy(sub_element) for sub_element in sequence] 

949 for sequence in element 

950 ] 

951 

952 def visit_propagate_attrs( 

953 self, attrname, parent, element, clone=_clone, **kw 

954 ): 

955 return element 

956 

957 

958_copy_internals = _CopyInternals() 

959 

960 

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__() 

966 

967 return element 

968 

969 

970class _GetChildren(InternalTraversal): 

971 """Generate a _children_traversal internal traversal dispatch for classes 

972 with a _traverse_internals collection.""" 

973 

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 () 

979 

980 def visit_clauseelement(self, element, **kw): 

981 return (element,) 

982 

983 def visit_clauseelement_list(self, element, **kw): 

984 return element 

985 

986 def visit_clauseelement_tuple(self, element, **kw): 

987 return element 

988 

989 def visit_clauseelement_tuples(self, element, **kw): 

990 return itertools.chain.from_iterable(element) 

991 

992 def visit_fromclause_canonical_column_collection(self, element, **kw): 

993 return () 

994 

995 def visit_string_clauseelement_dict(self, element, **kw): 

996 return element.values() 

997 

998 def visit_fromclause_ordered_set(self, element, **kw): 

999 return element 

1000 

1001 def visit_clauseelement_unordered_set(self, element, **kw): 

1002 return element 

1003 

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_ 

1008 

1009 if not isinstance(target, str): 

1010 yield _flatten_clauseelement(target) 

1011 

1012 if onclause is not None and not isinstance(onclause, str): 

1013 yield _flatten_clauseelement(onclause) 

1014 

1015 def visit_memoized_select_entities(self, element, **kw): 

1016 return self.visit_clauseelement_tuple(element, **kw) 

1017 

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 

1023 

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) 

1027 

1028 for k in sorted(str_values): 

1029 yield element[k] 

1030 for k in expr_values: 

1031 yield k 

1032 yield element[k] 

1033 

1034 def visit_dml_multi_values(self, element, **kw): 

1035 return () 

1036 

1037 def visit_propagate_attrs(self, element, **kw): 

1038 return () 

1039 

1040 

1041_get_children = _GetChildren() 

1042 

1043 

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) 

1048 

1049 return name 

1050 

1051 

1052class anon_map(dict): 

1053 """A map that creates new keys for missing key access. 

1054 

1055 Produces an incrementing sequence given a series of unique keys. 

1056 

1057 This is similar to the compiler prefix_anon_map class although simpler. 

1058 

1059 Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which 

1060 is otherwise usually used for this type of operation. 

1061 

1062 """ 

1063 

1064 def __init__(self): 

1065 self.index = 0 

1066 

1067 def __missing__(self, key): 

1068 self[key] = val = str(self.index) 

1069 self.index += 1 

1070 return val 

1071 

1072 

1073class TraversalComparatorStrategy(InternalTraversal, util.MemoizedSlots): 

1074 __slots__ = "stack", "cache", "anon_map" 

1075 

1076 def __init__(self): 

1077 self.stack = deque() 

1078 self.cache = set() 

1079 

1080 def _memoized_attr_anon_map(self): 

1081 return (anon_map(), anon_map()) 

1082 

1083 def compare(self, obj1, obj2, **kw): 

1084 stack = self.stack 

1085 cache = self.cache 

1086 

1087 compare_annotations = kw.get("compare_annotations", False) 

1088 

1089 stack.append((obj1, obj2)) 

1090 

1091 while stack: 

1092 left, right = stack.popleft() 

1093 

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)) 

1102 

1103 visit_name = left.__visit_name__ 

1104 if visit_name != right.__visit_name__: 

1105 return False 

1106 

1107 meth = getattr(self, "compare_%s" % visit_name, None) 

1108 

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 

1115 

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 = () 

1122 

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 

1136 

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 

1144 

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 

1153 

1154 comparison = dispatch( 

1155 left_attrname, left, left_child, right, right_child, **kw 

1156 ) 

1157 if comparison is COMPARE_FAILED: 

1158 return False 

1159 

1160 return True 

1161 

1162 def compare_inner(self, obj1, obj2, **kw): 

1163 comparator = self.__class__() 

1164 return comparator.compare(obj1, obj2, **kw) 

1165 

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 

1173 

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 ) 

1180 

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 

1189 

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 

1204 

1205 def visit_clauseelement( 

1206 self, attrname, left_parent, left, right_parent, right, **kw 

1207 ): 

1208 self.stack.append((left, right)) 

1209 

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)) 

1215 

1216 def visit_fromclause_derived_column_collection( 

1217 self, attrname, left_parent, left, right_parent, right, **kw 

1218 ): 

1219 pass 

1220 

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])) 

1230 

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 

1237 

1238 for l, r in util.zip_longest(ltup, rtup, fillvalue=None): 

1239 self.stack.append((l, r)) 

1240 

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)) 

1246 

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)) 

1252 

1253 def _compare_unordered_sequences(self, seq1, seq2, **kw): 

1254 if seq1 is None: 

1255 return seq2 is None 

1256 

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) 

1264 

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) 

1269 

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)) 

1275 

1276 def visit_string( 

1277 self, attrname, left_parent, left, right_parent, right, **kw 

1278 ): 

1279 return left == right 

1280 

1281 def visit_string_list( 

1282 self, attrname, left_parent, left, right_parent, right, **kw 

1283 ): 

1284 return left == right 

1285 

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 ) 

1294 

1295 def visit_boolean( 

1296 self, attrname, left_parent, left, right_parent, right, **kw 

1297 ): 

1298 return left == right 

1299 

1300 def visit_operator( 

1301 self, attrname, left_parent, left, right_parent, right, **kw 

1302 ): 

1303 return left == right 

1304 

1305 def visit_type( 

1306 self, attrname, left_parent, left, right_parent, right, **kw 

1307 ): 

1308 return left._compare_type_affinity(right) 

1309 

1310 def visit_plain_dict( 

1311 self, attrname, left_parent, left, right_parent, right, **kw 

1312 ): 

1313 return left == right 

1314 

1315 def visit_dialect_options( 

1316 self, attrname, left_parent, left, right_parent, right, **kw 

1317 ): 

1318 return left == right 

1319 

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 

1330 

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 ) 

1337 

1338 def visit_plain_obj( 

1339 self, attrname, left_parent, left, right_parent, right, **kw 

1340 ): 

1341 return left == right 

1342 

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 

1349 

1350 return left.name == right.name 

1351 

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)) 

1362 

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)) 

1376 

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 ) 

1383 

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)) 

1400 

1401 def visit_statement_hint_list( 

1402 self, attrname, left_parent, left, right_parent, right, **kw 

1403 ): 

1404 return left == right 

1405 

1406 def visit_unknown_structure( 

1407 self, attrname, left_parent, left, right_parent, right, **kw 

1408 ): 

1409 raise NotImplementedError() 

1410 

1411 def visit_dml_ordered_values( 

1412 self, attrname, left_parent, left, right_parent, right, **kw 

1413 ): 

1414 # sequence of tuple pairs 

1415 

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 

1421 

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 

1433 

1434 return True 

1435 

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 

1441 

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] 

1461 

1462 if lk not in right: 

1463 return COMPARE_FAILED 

1464 rv = right[lk] 

1465 

1466 if not self._compare_dml_values_or_ce(lv, rv, **kw): 

1467 return COMPARE_FAILED 

1468 

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 

1475 

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 

1484 

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 

1498 

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 

1516 

1517 def compare_bindparam(self, left, right, **kw): 

1518 compare_keys = kw.pop("compare_keys", True) 

1519 compare_values = kw.pop("compare_values", True) 

1520 

1521 if compare_values: 

1522 omit = [] 

1523 else: 

1524 # this means, "skip these, we already compared" 

1525 omit = ["callable", "value"] 

1526 

1527 if not compare_keys: 

1528 omit.append("key") 

1529 

1530 return omit 

1531 

1532 

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. 

1538 

1539 This is a comparison strategy specific to the ORM. 

1540 """ 

1541 

1542 to_compare = (right,) 

1543 if equivalents and right in equivalents: 

1544 to_compare = equivalents[right].union(to_compare) 

1545 

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 

1553 

1554 def compare_column(self, left, right, **kw): 

1555 return self.compare_column_element(left, right, **kw) 

1556 

1557 def compare_label(self, left, right, **kw): 

1558 return self.compare_column_element(left, right, **kw) 

1559 

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