dependency.py 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. # orm/dependency.py
  2. # Copyright (C) 2005-2017 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: http://www.opensource.org/licenses/mit-license.php
  7. """Relationship dependencies.
  8. """
  9. from .. import sql, util, exc as sa_exc
  10. from . import attributes, exc, sync, unitofwork, \
  11. util as mapperutil
  12. from .interfaces import ONETOMANY, MANYTOONE, MANYTOMANY
  13. class DependencyProcessor(object):
  14. def __init__(self, prop):
  15. self.prop = prop
  16. self.cascade = prop.cascade
  17. self.mapper = prop.mapper
  18. self.parent = prop.parent
  19. self.secondary = prop.secondary
  20. self.direction = prop.direction
  21. self.post_update = prop.post_update
  22. self.passive_deletes = prop.passive_deletes
  23. self.passive_updates = prop.passive_updates
  24. self.enable_typechecks = prop.enable_typechecks
  25. if self.passive_deletes:
  26. self._passive_delete_flag = attributes.PASSIVE_NO_INITIALIZE
  27. else:
  28. self._passive_delete_flag = attributes.PASSIVE_OFF
  29. if self.passive_updates:
  30. self._passive_update_flag = attributes.PASSIVE_NO_INITIALIZE
  31. else:
  32. self._passive_update_flag = attributes.PASSIVE_OFF
  33. self.key = prop.key
  34. if not self.prop.synchronize_pairs:
  35. raise sa_exc.ArgumentError(
  36. "Can't build a DependencyProcessor for relationship %s. "
  37. "No target attributes to populate between parent and "
  38. "child are present" %
  39. self.prop)
  40. @classmethod
  41. def from_relationship(cls, prop):
  42. return _direction_to_processor[prop.direction](prop)
  43. def hasparent(self, state):
  44. """return True if the given object instance has a parent,
  45. according to the ``InstrumentedAttribute`` handled by this
  46. ``DependencyProcessor``.
  47. """
  48. return self.parent.class_manager.get_impl(self.key).hasparent(state)
  49. def per_property_preprocessors(self, uow):
  50. """establish actions and dependencies related to a flush.
  51. These actions will operate on all relevant states in
  52. the aggregate.
  53. """
  54. uow.register_preprocessor(self, True)
  55. def per_property_flush_actions(self, uow):
  56. after_save = unitofwork.ProcessAll(uow, self, False, True)
  57. before_delete = unitofwork.ProcessAll(uow, self, True, True)
  58. parent_saves = unitofwork.SaveUpdateAll(
  59. uow,
  60. self.parent.primary_base_mapper
  61. )
  62. child_saves = unitofwork.SaveUpdateAll(
  63. uow,
  64. self.mapper.primary_base_mapper
  65. )
  66. parent_deletes = unitofwork.DeleteAll(
  67. uow,
  68. self.parent.primary_base_mapper
  69. )
  70. child_deletes = unitofwork.DeleteAll(
  71. uow,
  72. self.mapper.primary_base_mapper
  73. )
  74. self.per_property_dependencies(uow,
  75. parent_saves,
  76. child_saves,
  77. parent_deletes,
  78. child_deletes,
  79. after_save,
  80. before_delete
  81. )
  82. def per_state_flush_actions(self, uow, states, isdelete):
  83. """establish actions and dependencies related to a flush.
  84. These actions will operate on all relevant states
  85. individually. This occurs only if there are cycles
  86. in the 'aggregated' version of events.
  87. """
  88. parent_base_mapper = self.parent.primary_base_mapper
  89. child_base_mapper = self.mapper.primary_base_mapper
  90. child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper)
  91. child_deletes = unitofwork.DeleteAll(uow, child_base_mapper)
  92. # locate and disable the aggregate processors
  93. # for this dependency
  94. if isdelete:
  95. before_delete = unitofwork.ProcessAll(uow, self, True, True)
  96. before_delete.disabled = True
  97. else:
  98. after_save = unitofwork.ProcessAll(uow, self, False, True)
  99. after_save.disabled = True
  100. # check if the "child" side is part of the cycle
  101. if child_saves not in uow.cycles:
  102. # based on the current dependencies we use, the saves/
  103. # deletes should always be in the 'cycles' collection
  104. # together. if this changes, we will have to break up
  105. # this method a bit more.
  106. assert child_deletes not in uow.cycles
  107. # child side is not part of the cycle, so we will link per-state
  108. # actions to the aggregate "saves", "deletes" actions
  109. child_actions = [
  110. (child_saves, False), (child_deletes, True)
  111. ]
  112. child_in_cycles = False
  113. else:
  114. child_in_cycles = True
  115. # check if the "parent" side is part of the cycle
  116. if not isdelete:
  117. parent_saves = unitofwork.SaveUpdateAll(
  118. uow,
  119. self.parent.base_mapper)
  120. parent_deletes = before_delete = None
  121. if parent_saves in uow.cycles:
  122. parent_in_cycles = True
  123. else:
  124. parent_deletes = unitofwork.DeleteAll(
  125. uow,
  126. self.parent.base_mapper)
  127. parent_saves = after_save = None
  128. if parent_deletes in uow.cycles:
  129. parent_in_cycles = True
  130. # now create actions /dependencies for each state.
  131. for state in states:
  132. # detect if there's anything changed or loaded
  133. # by a preprocessor on this state/attribute. In the
  134. # case of deletes we may try to load missing items here as well.
  135. sum_ = state.manager[self.key].impl.get_all_pending(
  136. state, state.dict,
  137. self._passive_delete_flag
  138. if isdelete
  139. else attributes.PASSIVE_NO_INITIALIZE)
  140. if not sum_:
  141. continue
  142. if isdelete:
  143. before_delete = unitofwork.ProcessState(uow,
  144. self, True, state)
  145. if parent_in_cycles:
  146. parent_deletes = unitofwork.DeleteState(
  147. uow,
  148. state,
  149. parent_base_mapper)
  150. else:
  151. after_save = unitofwork.ProcessState(uow, self, False, state)
  152. if parent_in_cycles:
  153. parent_saves = unitofwork.SaveUpdateState(
  154. uow,
  155. state,
  156. parent_base_mapper)
  157. if child_in_cycles:
  158. child_actions = []
  159. for child_state, child in sum_:
  160. if child_state not in uow.states:
  161. child_action = (None, None)
  162. else:
  163. (deleted, listonly) = uow.states[child_state]
  164. if deleted:
  165. child_action = (
  166. unitofwork.DeleteState(
  167. uow, child_state,
  168. child_base_mapper),
  169. True)
  170. else:
  171. child_action = (
  172. unitofwork.SaveUpdateState(
  173. uow, child_state,
  174. child_base_mapper),
  175. False)
  176. child_actions.append(child_action)
  177. # establish dependencies between our possibly per-state
  178. # parent action and our possibly per-state child action.
  179. for child_action, childisdelete in child_actions:
  180. self.per_state_dependencies(uow, parent_saves,
  181. parent_deletes,
  182. child_action,
  183. after_save, before_delete,
  184. isdelete, childisdelete)
  185. def presort_deletes(self, uowcommit, states):
  186. return False
  187. def presort_saves(self, uowcommit, states):
  188. return False
  189. def process_deletes(self, uowcommit, states):
  190. pass
  191. def process_saves(self, uowcommit, states):
  192. pass
  193. def prop_has_changes(self, uowcommit, states, isdelete):
  194. if not isdelete or self.passive_deletes:
  195. passive = attributes.PASSIVE_NO_INITIALIZE
  196. elif self.direction is MANYTOONE:
  197. passive = attributes.PASSIVE_NO_FETCH_RELATED
  198. else:
  199. passive = attributes.PASSIVE_OFF
  200. for s in states:
  201. # TODO: add a high speed method
  202. # to InstanceState which returns: attribute
  203. # has a non-None value, or had one
  204. history = uowcommit.get_attribute_history(
  205. s,
  206. self.key,
  207. passive)
  208. if history and not history.empty():
  209. return True
  210. else:
  211. return states and \
  212. not self.prop._is_self_referential and \
  213. self.mapper in uowcommit.mappers
  214. def _verify_canload(self, state):
  215. if self.prop.uselist and state is None:
  216. raise exc.FlushError(
  217. "Can't flush None value found in "
  218. "collection %s" % (self.prop, ))
  219. elif state is not None and \
  220. not self.mapper._canload(
  221. state, allow_subtypes=not self.enable_typechecks):
  222. if self.mapper._canload(state, allow_subtypes=True):
  223. raise exc.FlushError('Attempting to flush an item of type '
  224. '%(x)s as a member of collection '
  225. '"%(y)s". Expected an object of type '
  226. '%(z)s or a polymorphic subclass of '
  227. 'this type. If %(x)s is a subclass of '
  228. '%(z)s, configure mapper "%(zm)s" to '
  229. 'load this subtype polymorphically, or '
  230. 'set enable_typechecks=False to allow '
  231. 'any subtype to be accepted for flush. '
  232. % {
  233. 'x': state.class_,
  234. 'y': self.prop,
  235. 'z': self.mapper.class_,
  236. 'zm': self.mapper,
  237. })
  238. else:
  239. raise exc.FlushError(
  240. 'Attempting to flush an item of type '
  241. '%(x)s as a member of collection '
  242. '"%(y)s". Expected an object of type '
  243. '%(z)s or a polymorphic subclass of '
  244. 'this type.' % {
  245. 'x': state.class_,
  246. 'y': self.prop,
  247. 'z': self.mapper.class_,
  248. })
  249. def _synchronize(self, state, child, associationrow,
  250. clearkeys, uowcommit):
  251. raise NotImplementedError()
  252. def _get_reversed_processed_set(self, uow):
  253. if not self.prop._reverse_property:
  254. return None
  255. process_key = tuple(sorted(
  256. [self.key] +
  257. [p.key for p in self.prop._reverse_property]
  258. ))
  259. return uow.memo(
  260. ('reverse_key', process_key),
  261. set
  262. )
  263. def _post_update(self, state, uowcommit, related, is_m2o_delete=False):
  264. for x in related:
  265. if not is_m2o_delete or x is not None:
  266. uowcommit.issue_post_update(
  267. state,
  268. [r for l, r in self.prop.synchronize_pairs]
  269. )
  270. break
  271. def _pks_changed(self, uowcommit, state):
  272. raise NotImplementedError()
  273. def __repr__(self):
  274. return "%s(%s)" % (self.__class__.__name__, self.prop)
  275. class OneToManyDP(DependencyProcessor):
  276. def per_property_dependencies(self, uow, parent_saves,
  277. child_saves,
  278. parent_deletes,
  279. child_deletes,
  280. after_save,
  281. before_delete,
  282. ):
  283. if self.post_update:
  284. child_post_updates = unitofwork.IssuePostUpdate(
  285. uow,
  286. self.mapper.primary_base_mapper,
  287. False)
  288. child_pre_updates = unitofwork.IssuePostUpdate(
  289. uow,
  290. self.mapper.primary_base_mapper,
  291. True)
  292. uow.dependencies.update([
  293. (child_saves, after_save),
  294. (parent_saves, after_save),
  295. (after_save, child_post_updates),
  296. (before_delete, child_pre_updates),
  297. (child_pre_updates, parent_deletes),
  298. (child_pre_updates, child_deletes),
  299. ])
  300. else:
  301. uow.dependencies.update([
  302. (parent_saves, after_save),
  303. (after_save, child_saves),
  304. (after_save, child_deletes),
  305. (child_saves, parent_deletes),
  306. (child_deletes, parent_deletes),
  307. (before_delete, child_saves),
  308. (before_delete, child_deletes),
  309. ])
  310. def per_state_dependencies(self, uow,
  311. save_parent,
  312. delete_parent,
  313. child_action,
  314. after_save, before_delete,
  315. isdelete, childisdelete):
  316. if self.post_update:
  317. child_post_updates = unitofwork.IssuePostUpdate(
  318. uow,
  319. self.mapper.primary_base_mapper,
  320. False)
  321. child_pre_updates = unitofwork.IssuePostUpdate(
  322. uow,
  323. self.mapper.primary_base_mapper,
  324. True)
  325. # TODO: this whole block is not covered
  326. # by any tests
  327. if not isdelete:
  328. if childisdelete:
  329. uow.dependencies.update([
  330. (child_action, after_save),
  331. (after_save, child_post_updates),
  332. ])
  333. else:
  334. uow.dependencies.update([
  335. (save_parent, after_save),
  336. (child_action, after_save),
  337. (after_save, child_post_updates),
  338. ])
  339. else:
  340. if childisdelete:
  341. uow.dependencies.update([
  342. (before_delete, child_pre_updates),
  343. (child_pre_updates, delete_parent),
  344. ])
  345. else:
  346. uow.dependencies.update([
  347. (before_delete, child_pre_updates),
  348. (child_pre_updates, delete_parent),
  349. ])
  350. elif not isdelete:
  351. uow.dependencies.update([
  352. (save_parent, after_save),
  353. (after_save, child_action),
  354. (save_parent, child_action)
  355. ])
  356. else:
  357. uow.dependencies.update([
  358. (before_delete, child_action),
  359. (child_action, delete_parent)
  360. ])
  361. def presort_deletes(self, uowcommit, states):
  362. # head object is being deleted, and we manage its list of
  363. # child objects the child objects have to have their
  364. # foreign key to the parent set to NULL
  365. should_null_fks = not self.cascade.delete and \
  366. not self.passive_deletes == 'all'
  367. for state in states:
  368. history = uowcommit.get_attribute_history(
  369. state,
  370. self.key,
  371. self._passive_delete_flag)
  372. if history:
  373. for child in history.deleted:
  374. if child is not None and self.hasparent(child) is False:
  375. if self.cascade.delete_orphan:
  376. uowcommit.register_object(child, isdelete=True)
  377. else:
  378. uowcommit.register_object(child)
  379. if should_null_fks:
  380. for child in history.unchanged:
  381. if child is not None:
  382. uowcommit.register_object(
  383. child, operation="delete", prop=self.prop)
  384. def presort_saves(self, uowcommit, states):
  385. children_added = uowcommit.memo(('children_added', self), set)
  386. for state in states:
  387. pks_changed = self._pks_changed(uowcommit, state)
  388. if not pks_changed or self.passive_updates:
  389. passive = attributes.PASSIVE_NO_INITIALIZE
  390. else:
  391. passive = attributes.PASSIVE_OFF
  392. history = uowcommit.get_attribute_history(
  393. state,
  394. self.key,
  395. passive)
  396. if history:
  397. for child in history.added:
  398. if child is not None:
  399. uowcommit.register_object(child, cancel_delete=True,
  400. operation="add",
  401. prop=self.prop)
  402. children_added.update(history.added)
  403. for child in history.deleted:
  404. if not self.cascade.delete_orphan:
  405. uowcommit.register_object(child, isdelete=False,
  406. operation='delete',
  407. prop=self.prop)
  408. elif self.hasparent(child) is False:
  409. uowcommit.register_object(
  410. child, isdelete=True,
  411. operation="delete", prop=self.prop)
  412. for c, m, st_, dct_ in self.mapper.cascade_iterator(
  413. 'delete', child):
  414. uowcommit.register_object(
  415. st_,
  416. isdelete=True)
  417. if pks_changed:
  418. if history:
  419. for child in history.unchanged:
  420. if child is not None:
  421. uowcommit.register_object(
  422. child,
  423. False,
  424. self.passive_updates,
  425. operation="pk change",
  426. prop=self.prop)
  427. def process_deletes(self, uowcommit, states):
  428. # head object is being deleted, and we manage its list of
  429. # child objects the child objects have to have their foreign
  430. # key to the parent set to NULL this phase can be called
  431. # safely for any cascade but is unnecessary if delete cascade
  432. # is on.
  433. if self.post_update or not self.passive_deletes == 'all':
  434. children_added = uowcommit.memo(('children_added', self), set)
  435. for state in states:
  436. history = uowcommit.get_attribute_history(
  437. state,
  438. self.key,
  439. self._passive_delete_flag)
  440. if history:
  441. for child in history.deleted:
  442. if child is not None and \
  443. self.hasparent(child) is False:
  444. self._synchronize(
  445. state,
  446. child,
  447. None, True,
  448. uowcommit, False)
  449. if self.post_update and child:
  450. self._post_update(child, uowcommit, [state])
  451. if self.post_update or not self.cascade.delete:
  452. for child in set(history.unchanged).\
  453. difference(children_added):
  454. if child is not None:
  455. self._synchronize(
  456. state,
  457. child,
  458. None, True,
  459. uowcommit, False)
  460. if self.post_update and child:
  461. self._post_update(child,
  462. uowcommit,
  463. [state])
  464. # technically, we can even remove each child from the
  465. # collection here too. but this would be a somewhat
  466. # inconsistent behavior since it wouldn't happen
  467. # if the old parent wasn't deleted but child was moved.
  468. def process_saves(self, uowcommit, states):
  469. for state in states:
  470. history = uowcommit.get_attribute_history(
  471. state,
  472. self.key,
  473. attributes.PASSIVE_NO_INITIALIZE)
  474. if history:
  475. for child in history.added:
  476. self._synchronize(state, child, None,
  477. False, uowcommit, False)
  478. if child is not None and self.post_update:
  479. self._post_update(child, uowcommit, [state])
  480. for child in history.deleted:
  481. if not self.cascade.delete_orphan and \
  482. not self.hasparent(child):
  483. self._synchronize(state, child, None, True,
  484. uowcommit, False)
  485. if self._pks_changed(uowcommit, state):
  486. for child in history.unchanged:
  487. self._synchronize(state, child, None,
  488. False, uowcommit, True)
  489. def _synchronize(self, state, child,
  490. associationrow, clearkeys, uowcommit,
  491. pks_changed):
  492. source = state
  493. dest = child
  494. self._verify_canload(child)
  495. if dest is None or \
  496. (not self.post_update and uowcommit.is_deleted(dest)):
  497. return
  498. if clearkeys:
  499. sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
  500. else:
  501. sync.populate(source, self.parent, dest, self.mapper,
  502. self.prop.synchronize_pairs, uowcommit,
  503. self.passive_updates and pks_changed)
  504. def _pks_changed(self, uowcommit, state):
  505. return sync.source_modified(
  506. uowcommit,
  507. state,
  508. self.parent,
  509. self.prop.synchronize_pairs)
  510. class ManyToOneDP(DependencyProcessor):
  511. def __init__(self, prop):
  512. DependencyProcessor.__init__(self, prop)
  513. self.mapper._dependency_processors.append(DetectKeySwitch(prop))
  514. def per_property_dependencies(self, uow,
  515. parent_saves,
  516. child_saves,
  517. parent_deletes,
  518. child_deletes,
  519. after_save,
  520. before_delete):
  521. if self.post_update:
  522. parent_post_updates = unitofwork.IssuePostUpdate(
  523. uow,
  524. self.parent.primary_base_mapper,
  525. False)
  526. parent_pre_updates = unitofwork.IssuePostUpdate(
  527. uow,
  528. self.parent.primary_base_mapper,
  529. True)
  530. uow.dependencies.update([
  531. (child_saves, after_save),
  532. (parent_saves, after_save),
  533. (after_save, parent_post_updates),
  534. (after_save, parent_pre_updates),
  535. (before_delete, parent_pre_updates),
  536. (parent_pre_updates, child_deletes),
  537. ])
  538. else:
  539. uow.dependencies.update([
  540. (child_saves, after_save),
  541. (after_save, parent_saves),
  542. (parent_saves, child_deletes),
  543. (parent_deletes, child_deletes)
  544. ])
  545. def per_state_dependencies(self, uow,
  546. save_parent,
  547. delete_parent,
  548. child_action,
  549. after_save, before_delete,
  550. isdelete, childisdelete):
  551. if self.post_update:
  552. if not isdelete:
  553. parent_post_updates = unitofwork.IssuePostUpdate(
  554. uow,
  555. self.parent.primary_base_mapper,
  556. False)
  557. if childisdelete:
  558. uow.dependencies.update([
  559. (after_save, parent_post_updates),
  560. (parent_post_updates, child_action)
  561. ])
  562. else:
  563. uow.dependencies.update([
  564. (save_parent, after_save),
  565. (child_action, after_save),
  566. (after_save, parent_post_updates)
  567. ])
  568. else:
  569. parent_pre_updates = unitofwork.IssuePostUpdate(
  570. uow,
  571. self.parent.primary_base_mapper,
  572. True)
  573. uow.dependencies.update([
  574. (before_delete, parent_pre_updates),
  575. (parent_pre_updates, delete_parent),
  576. (parent_pre_updates, child_action)
  577. ])
  578. elif not isdelete:
  579. if not childisdelete:
  580. uow.dependencies.update([
  581. (child_action, after_save),
  582. (after_save, save_parent),
  583. ])
  584. else:
  585. uow.dependencies.update([
  586. (after_save, save_parent),
  587. ])
  588. else:
  589. if childisdelete:
  590. uow.dependencies.update([
  591. (delete_parent, child_action)
  592. ])
  593. def presort_deletes(self, uowcommit, states):
  594. if self.cascade.delete or self.cascade.delete_orphan:
  595. for state in states:
  596. history = uowcommit.get_attribute_history(
  597. state,
  598. self.key,
  599. self._passive_delete_flag)
  600. if history:
  601. if self.cascade.delete_orphan:
  602. todelete = history.sum()
  603. else:
  604. todelete = history.non_deleted()
  605. for child in todelete:
  606. if child is None:
  607. continue
  608. uowcommit.register_object(
  609. child, isdelete=True,
  610. operation="delete", prop=self.prop)
  611. t = self.mapper.cascade_iterator('delete', child)
  612. for c, m, st_, dct_ in t:
  613. uowcommit.register_object(
  614. st_, isdelete=True)
  615. def presort_saves(self, uowcommit, states):
  616. for state in states:
  617. uowcommit.register_object(state, operation="add", prop=self.prop)
  618. if self.cascade.delete_orphan:
  619. history = uowcommit.get_attribute_history(
  620. state,
  621. self.key,
  622. self._passive_delete_flag)
  623. if history:
  624. for child in history.deleted:
  625. if self.hasparent(child) is False:
  626. uowcommit.register_object(
  627. child, isdelete=True,
  628. operation="delete", prop=self.prop)
  629. t = self.mapper.cascade_iterator('delete', child)
  630. for c, m, st_, dct_ in t:
  631. uowcommit.register_object(st_, isdelete=True)
  632. def process_deletes(self, uowcommit, states):
  633. if self.post_update and \
  634. not self.cascade.delete_orphan and \
  635. not self.passive_deletes == 'all':
  636. # post_update means we have to update our
  637. # row to not reference the child object
  638. # before we can DELETE the row
  639. for state in states:
  640. self._synchronize(state, None, None, True, uowcommit)
  641. if state and self.post_update:
  642. history = uowcommit.get_attribute_history(
  643. state,
  644. self.key,
  645. self._passive_delete_flag)
  646. if history:
  647. self._post_update(
  648. state, uowcommit, history.sum(),
  649. is_m2o_delete=True)
  650. def process_saves(self, uowcommit, states):
  651. for state in states:
  652. history = uowcommit.get_attribute_history(
  653. state,
  654. self.key,
  655. attributes.PASSIVE_NO_INITIALIZE)
  656. if history:
  657. if history.added:
  658. for child in history.added:
  659. self._synchronize(state, child, None, False,
  660. uowcommit, "add")
  661. if self.post_update:
  662. self._post_update(state, uowcommit, history.sum())
  663. def _synchronize(self, state, child, associationrow,
  664. clearkeys, uowcommit, operation=None):
  665. if state is None or \
  666. (not self.post_update and uowcommit.is_deleted(state)):
  667. return
  668. if operation is not None and \
  669. child is not None and \
  670. not uowcommit.session._contains_state(child):
  671. util.warn(
  672. "Object of type %s not in session, %s "
  673. "operation along '%s' won't proceed" %
  674. (mapperutil.state_class_str(child), operation, self.prop))
  675. return
  676. if clearkeys or child is None:
  677. sync.clear(state, self.parent, self.prop.synchronize_pairs)
  678. else:
  679. self._verify_canload(child)
  680. sync.populate(child, self.mapper, state,
  681. self.parent,
  682. self.prop.synchronize_pairs,
  683. uowcommit,
  684. False)
  685. class DetectKeySwitch(DependencyProcessor):
  686. """For many-to-one relationships with no one-to-many backref,
  687. searches for parents through the unit of work when a primary
  688. key has changed and updates them.
  689. Theoretically, this approach could be expanded to support transparent
  690. deletion of objects referenced via many-to-one as well, although
  691. the current attribute system doesn't do enough bookkeeping for this
  692. to be efficient.
  693. """
  694. def per_property_preprocessors(self, uow):
  695. if self.prop._reverse_property:
  696. if self.passive_updates:
  697. return
  698. else:
  699. if False in (prop.passive_updates for
  700. prop in self.prop._reverse_property):
  701. return
  702. uow.register_preprocessor(self, False)
  703. def per_property_flush_actions(self, uow):
  704. parent_saves = unitofwork.SaveUpdateAll(
  705. uow,
  706. self.parent.base_mapper)
  707. after_save = unitofwork.ProcessAll(uow, self, False, False)
  708. uow.dependencies.update([
  709. (parent_saves, after_save)
  710. ])
  711. def per_state_flush_actions(self, uow, states, isdelete):
  712. pass
  713. def presort_deletes(self, uowcommit, states):
  714. pass
  715. def presort_saves(self, uow, states):
  716. if not self.passive_updates:
  717. # for non-passive updates, register in the preprocess stage
  718. # so that mapper save_obj() gets a hold of changes
  719. self._process_key_switches(states, uow)
  720. def prop_has_changes(self, uow, states, isdelete):
  721. if not isdelete and self.passive_updates:
  722. d = self._key_switchers(uow, states)
  723. return bool(d)
  724. return False
  725. def process_deletes(self, uowcommit, states):
  726. assert False
  727. def process_saves(self, uowcommit, states):
  728. # for passive updates, register objects in the process stage
  729. # so that we avoid ManyToOneDP's registering the object without
  730. # the listonly flag in its own preprocess stage (results in UPDATE)
  731. # statements being emitted
  732. assert self.passive_updates
  733. self._process_key_switches(states, uowcommit)
  734. def _key_switchers(self, uow, states):
  735. switched, notswitched = uow.memo(
  736. ('pk_switchers', self),
  737. lambda: (set(), set())
  738. )
  739. allstates = switched.union(notswitched)
  740. for s in states:
  741. if s not in allstates:
  742. if self._pks_changed(uow, s):
  743. switched.add(s)
  744. else:
  745. notswitched.add(s)
  746. return switched
  747. def _process_key_switches(self, deplist, uowcommit):
  748. switchers = self._key_switchers(uowcommit, deplist)
  749. if switchers:
  750. # if primary key values have actually changed somewhere, perform
  751. # a linear search through the UOW in search of a parent.
  752. for state in uowcommit.session.identity_map.all_states():
  753. if not issubclass(state.class_, self.parent.class_):
  754. continue
  755. dict_ = state.dict
  756. related = state.get_impl(self.key).get(
  757. state, dict_, passive=self._passive_update_flag)
  758. if related is not attributes.PASSIVE_NO_RESULT and \
  759. related is not None:
  760. related_state = attributes.instance_state(dict_[self.key])
  761. if related_state in switchers:
  762. uowcommit.register_object(state,
  763. False,
  764. self.passive_updates)
  765. sync.populate(
  766. related_state,
  767. self.mapper, state,
  768. self.parent, self.prop.synchronize_pairs,
  769. uowcommit, self.passive_updates)
  770. def _pks_changed(self, uowcommit, state):
  771. return bool(state.key) and sync.source_modified(
  772. uowcommit, state, self.mapper, self.prop.synchronize_pairs)
  773. class ManyToManyDP(DependencyProcessor):
  774. def per_property_dependencies(self, uow, parent_saves,
  775. child_saves,
  776. parent_deletes,
  777. child_deletes,
  778. after_save,
  779. before_delete
  780. ):
  781. uow.dependencies.update([
  782. (parent_saves, after_save),
  783. (child_saves, after_save),
  784. (after_save, child_deletes),
  785. # a rowswitch on the parent from deleted to saved
  786. # can make this one occur, as the "save" may remove
  787. # an element from the
  788. # "deleted" list before we have a chance to
  789. # process its child rows
  790. (before_delete, parent_saves),
  791. (before_delete, parent_deletes),
  792. (before_delete, child_deletes),
  793. (before_delete, child_saves),
  794. ])
  795. def per_state_dependencies(self, uow,
  796. save_parent,
  797. delete_parent,
  798. child_action,
  799. after_save, before_delete,
  800. isdelete, childisdelete):
  801. if not isdelete:
  802. if childisdelete:
  803. uow.dependencies.update([
  804. (save_parent, after_save),
  805. (after_save, child_action),
  806. ])
  807. else:
  808. uow.dependencies.update([
  809. (save_parent, after_save),
  810. (child_action, after_save),
  811. ])
  812. else:
  813. uow.dependencies.update([
  814. (before_delete, child_action),
  815. (before_delete, delete_parent)
  816. ])
  817. def presort_deletes(self, uowcommit, states):
  818. # TODO: no tests fail if this whole
  819. # thing is removed !!!!
  820. if not self.passive_deletes:
  821. # if no passive deletes, load history on
  822. # the collection, so that prop_has_changes()
  823. # returns True
  824. for state in states:
  825. uowcommit.get_attribute_history(
  826. state,
  827. self.key,
  828. self._passive_delete_flag)
  829. def presort_saves(self, uowcommit, states):
  830. if not self.passive_updates:
  831. # if no passive updates, load history on
  832. # each collection where parent has changed PK,
  833. # so that prop_has_changes() returns True
  834. for state in states:
  835. if self._pks_changed(uowcommit, state):
  836. history = uowcommit.get_attribute_history(
  837. state,
  838. self.key,
  839. attributes.PASSIVE_OFF)
  840. if not self.cascade.delete_orphan:
  841. return
  842. # check for child items removed from the collection
  843. # if delete_orphan check is turned on.
  844. for state in states:
  845. history = uowcommit.get_attribute_history(
  846. state,
  847. self.key,
  848. attributes.PASSIVE_NO_INITIALIZE)
  849. if history:
  850. for child in history.deleted:
  851. if self.hasparent(child) is False:
  852. uowcommit.register_object(
  853. child, isdelete=True,
  854. operation="delete", prop=self.prop)
  855. for c, m, st_, dct_ in self.mapper.cascade_iterator(
  856. 'delete',
  857. child):
  858. uowcommit.register_object(
  859. st_, isdelete=True)
  860. def process_deletes(self, uowcommit, states):
  861. secondary_delete = []
  862. secondary_insert = []
  863. secondary_update = []
  864. processed = self._get_reversed_processed_set(uowcommit)
  865. tmp = set()
  866. for state in states:
  867. # this history should be cached already, as
  868. # we loaded it in preprocess_deletes
  869. history = uowcommit.get_attribute_history(
  870. state,
  871. self.key,
  872. self._passive_delete_flag)
  873. if history:
  874. for child in history.non_added():
  875. if child is None or \
  876. (processed is not None and
  877. (state, child) in processed):
  878. continue
  879. associationrow = {}
  880. if not self._synchronize(
  881. state,
  882. child,
  883. associationrow,
  884. False, uowcommit, "delete"):
  885. continue
  886. secondary_delete.append(associationrow)
  887. tmp.update((c, state) for c in history.non_added())
  888. if processed is not None:
  889. processed.update(tmp)
  890. self._run_crud(uowcommit, secondary_insert,
  891. secondary_update, secondary_delete)
  892. def process_saves(self, uowcommit, states):
  893. secondary_delete = []
  894. secondary_insert = []
  895. secondary_update = []
  896. processed = self._get_reversed_processed_set(uowcommit)
  897. tmp = set()
  898. for state in states:
  899. need_cascade_pks = not self.passive_updates and \
  900. self._pks_changed(uowcommit, state)
  901. if need_cascade_pks:
  902. passive = attributes.PASSIVE_OFF
  903. else:
  904. passive = attributes.PASSIVE_NO_INITIALIZE
  905. history = uowcommit.get_attribute_history(state, self.key,
  906. passive)
  907. if history:
  908. for child in history.added:
  909. if (processed is not None and
  910. (state, child) in processed):
  911. continue
  912. associationrow = {}
  913. if not self._synchronize(state,
  914. child,
  915. associationrow,
  916. False, uowcommit, "add"):
  917. continue
  918. secondary_insert.append(associationrow)
  919. for child in history.deleted:
  920. if (processed is not None and
  921. (state, child) in processed):
  922. continue
  923. associationrow = {}
  924. if not self._synchronize(state,
  925. child,
  926. associationrow,
  927. False, uowcommit, "delete"):
  928. continue
  929. secondary_delete.append(associationrow)
  930. tmp.update((c, state)
  931. for c in history.added + history.deleted)
  932. if need_cascade_pks:
  933. for child in history.unchanged:
  934. associationrow = {}
  935. sync.update(state,
  936. self.parent,
  937. associationrow,
  938. "old_",
  939. self.prop.synchronize_pairs)
  940. sync.update(child,
  941. self.mapper,
  942. associationrow,
  943. "old_",
  944. self.prop.secondary_synchronize_pairs)
  945. secondary_update.append(associationrow)
  946. if processed is not None:
  947. processed.update(tmp)
  948. self._run_crud(uowcommit, secondary_insert,
  949. secondary_update, secondary_delete)
  950. def _run_crud(self, uowcommit, secondary_insert,
  951. secondary_update, secondary_delete):
  952. connection = uowcommit.transaction.connection(self.mapper)
  953. if secondary_delete:
  954. associationrow = secondary_delete[0]
  955. statement = self.secondary.delete(sql.and_(*[
  956. c == sql.bindparam(c.key, type_=c.type)
  957. for c in self.secondary.c
  958. if c.key in associationrow
  959. ]))
  960. result = connection.execute(statement, secondary_delete)
  961. if result.supports_sane_multi_rowcount() and \
  962. result.rowcount != len(secondary_delete):
  963. raise exc.StaleDataError(
  964. "DELETE statement on table '%s' expected to delete "
  965. "%d row(s); Only %d were matched." %
  966. (self.secondary.description, len(secondary_delete),
  967. result.rowcount)
  968. )
  969. if secondary_update:
  970. associationrow = secondary_update[0]
  971. statement = self.secondary.update(sql.and_(*[
  972. c == sql.bindparam("old_" + c.key, type_=c.type)
  973. for c in self.secondary.c
  974. if c.key in associationrow
  975. ]))
  976. result = connection.execute(statement, secondary_update)
  977. if result.supports_sane_multi_rowcount() and \
  978. result.rowcount != len(secondary_update):
  979. raise exc.StaleDataError(
  980. "UPDATE statement on table '%s' expected to update "
  981. "%d row(s); Only %d were matched." %
  982. (self.secondary.description, len(secondary_update),
  983. result.rowcount)
  984. )
  985. if secondary_insert:
  986. statement = self.secondary.insert()
  987. connection.execute(statement, secondary_insert)
  988. def _synchronize(self, state, child, associationrow,
  989. clearkeys, uowcommit, operation):
  990. # this checks for None if uselist=True
  991. self._verify_canload(child)
  992. # but if uselist=False we get here. If child is None,
  993. # no association row can be generated, so return.
  994. if child is None:
  995. return False
  996. if child is not None and not uowcommit.session._contains_state(child):
  997. if not child.deleted:
  998. util.warn(
  999. "Object of type %s not in session, %s "
  1000. "operation along '%s' won't proceed" %
  1001. (mapperutil.state_class_str(child), operation, self.prop))
  1002. return False
  1003. sync.populate_dict(state, self.parent, associationrow,
  1004. self.prop.synchronize_pairs)
  1005. sync.populate_dict(child, self.mapper, associationrow,
  1006. self.prop.secondary_synchronize_pairs)
  1007. return True
  1008. def _pks_changed(self, uowcommit, state):
  1009. return sync.source_modified(
  1010. uowcommit,
  1011. state,
  1012. self.parent,
  1013. self.prop.synchronize_pairs)
  1014. _direction_to_processor = {
  1015. ONETOMANY: OneToManyDP,
  1016. MANYTOONE: ManyToOneDP,
  1017. MANYTOMANY: ManyToManyDP,
  1018. }