root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/orm/dependency.py @ 3

リビジョン 3, 26.3 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# orm/dependency.py
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7"""Relationship dependencies.
8
9Bridges the ``PropertyLoader`` (i.e. a ``relation()``) and the
10``UOWTransaction`` together to allow processing of relation()-based
11dependencies at flush time.
12
13"""
14
15from sqlalchemy import sql, util
16import sqlalchemy.exceptions as sa_exc
17from sqlalchemy.orm import attributes, exc, sync
18from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE, MANYTOMANY
19
20
21def create_dependency_processor(prop):
22    types = {
23        ONETOMANY : OneToManyDP,
24        MANYTOONE: ManyToOneDP,
25        MANYTOMANY : ManyToManyDP,
26    }
27    return types[prop.direction](prop)
28
29class DependencyProcessor(object):
30    no_dependencies = False
31
32    def __init__(self, prop):
33        self.prop = prop
34        self.cascade = prop.cascade
35        self.mapper = prop.mapper
36        self.parent = prop.parent
37        self.secondary = prop.secondary
38        self.direction = prop.direction
39        self.post_update = prop.post_update
40        self.passive_deletes = prop.passive_deletes
41        self.passive_updates = prop.passive_updates
42        self.enable_typechecks = prop.enable_typechecks
43        self.key = prop.key
44        self.dependency_marker = MapperStub(self.parent, self.mapper, self.key)
45        if not self.prop.synchronize_pairs:
46            raise sa_exc.ArgumentError("Can't build a DependencyProcessor for relation %s.  "
47                    "No target attributes to populate between parent and child are present" % self.prop)
48
49    def _get_instrumented_attribute(self):
50        """Return the ``InstrumentedAttribute`` handled by this
51        ``DependencyProecssor``.
52       
53        """
54        return self.parent.class_manager.get_impl(self.key)
55
56    def hasparent(self, state):
57        """return True if the given object instance has a parent,
58        according to the ``InstrumentedAttribute`` handled by this ``DependencyProcessor``.
59       
60        """
61        # TODO: use correct API for this
62        return self._get_instrumented_attribute().hasparent(state)
63
64    def register_dependencies(self, uowcommit):
65        """Tell a ``UOWTransaction`` what mappers are dependent on
66        which, with regards to the two or three mappers handled by
67        this ``DependencyProcessor``.
68
69        """
70
71        raise NotImplementedError()
72
73    def register_processors(self, uowcommit):
74        """Tell a ``UOWTransaction`` about this object as a processor,
75        which will be executed after that mapper's objects have been
76        saved or before they've been deleted.  The process operation
77        manages attributes and dependent operations between two mappers.
78       
79        """
80        raise NotImplementedError()
81       
82    def whose_dependent_on_who(self, state1, state2):
83        """Given an object pair assuming `obj2` is a child of `obj1`,
84        return a tuple with the dependent object second, or None if
85        there is no dependency.
86
87        """
88        if state1 is state2:
89            return None
90        elif self.direction == ONETOMANY:
91            return (state1, state2)
92        else:
93            return (state2, state1)
94
95    def process_dependencies(self, task, deplist, uowcommit, delete = False):
96        """This method is called during a flush operation to
97        synchronize data between a parent and child object.
98
99        It is called within the context of the various mappers and
100        sometimes individual objects sorted according to their
101        insert/update/delete order (topological sort).
102
103        """
104        raise NotImplementedError()
105
106    def preprocess_dependencies(self, task, deplist, uowcommit, delete = False):
107        """Used before the flushes' topological sort to traverse
108        through related objects and ensure every instance which will
109        require save/update/delete is properly added to the
110        UOWTransaction.
111
112        """
113        raise NotImplementedError()
114
115    def _verify_canload(self, state):
116        if state is not None and not self.mapper._canload(state, allow_subtypes=not self.enable_typechecks):
117            if self.mapper._canload(state, allow_subtypes=True):
118                raise exc.FlushError("Attempting to flush an item of type %s on collection '%s', "
119                                "which is not the expected type %s.  Configure mapper '%s' to load this "
120                                "subtype polymorphically, or set enable_typechecks=False to allow subtypes.  "
121                                "Mismatched typeloading may cause bi-directional relationships (backrefs) "
122                                "to not function properly." % (state.class_, self.prop, self.mapper.class_, self.mapper))
123            else:
124                raise exc.FlushError("Attempting to flush an item of type %s on collection '%s', "
125                                "whose mapper does not inherit from that of %s." % (state.class_, self.prop, self.mapper.class_))
126           
127    def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
128        """Called during a flush to synchronize primary key identifier
129        values between a parent/child object, as well as to an
130        associationrow in the case of many-to-many.
131       
132        """
133        raise NotImplementedError()
134
135    def _check_reverse_action(self, uowcommit, parent, child, action):
136        """Determine if an action has been performed by the 'reverse' property of this property.
137       
138        this is used to ensure that only one side of a bidirectional relation
139        issues a certain operation for a parent/child pair.
140       
141        """
142        for r in self.prop._reverse_property:
143            if not r.viewonly and (r._dependency_processor, action, parent, child) in uowcommit.attributes:
144                return True
145        return False
146   
147    def _performed_action(self, uowcommit, parent, child, action):
148        """Establish that an action has been performed for a certain parent/child pair.
149       
150        Used only for actions that are sensitive to bidirectional double-action,
151        i.e. manytomany, post_update.
152       
153        """
154        uowcommit.attributes[(self, action, parent, child)] = True
155       
156    def _conditional_post_update(self, state, uowcommit, related):
157        """Execute a post_update call.
158
159        For relations that contain the post_update flag, an additional
160        ``UPDATE`` statement may be associated after an ``INSERT`` or
161        before a ``DELETE`` in order to resolve circular row
162        dependencies.
163
164        This method will check for the post_update flag being set on a
165        particular relationship, and given a target object and list of
166        one or more related objects, and execute the ``UPDATE`` if the
167        given related object list contains ``INSERT``s or ``DELETE``s.
168       
169        """
170        if state is not None and self.post_update:
171            for x in related:
172                if x is not None and not self._check_reverse_action(uowcommit, x, state, "postupdate"):
173                    uowcommit.register_object(state, postupdate=True, post_update_cols=[r for l, r in self.prop.synchronize_pairs])
174                    self._performed_action(uowcommit, x, state, "postupdate")
175                    break
176
177    def _pks_changed(self, uowcommit, state):
178        raise NotImplementedError()
179
180    def __repr__(self):
181        return "%s(%s)" % (self.__class__.__name__, self.prop)
182
183class OneToManyDP(DependencyProcessor):
184    def register_dependencies(self, uowcommit):
185        if self.post_update:
186            uowcommit.register_dependency(self.mapper, self.dependency_marker)
187            uowcommit.register_dependency(self.parent, self.dependency_marker)
188        else:
189            uowcommit.register_dependency(self.parent, self.mapper)
190
191    def register_processors(self, uowcommit):
192        if self.post_update:
193            uowcommit.register_processor(self.dependency_marker, self, self.parent)
194        else:
195            uowcommit.register_processor(self.parent, self, self.parent)
196
197    def process_dependencies(self, task, deplist, uowcommit, delete = False):
198        if delete:
199            # head object is being deleted, and we manage its list of child objects
200            # the child objects have to have their foreign key to the parent set to NULL
201            # this phase can be called safely for any cascade but is unnecessary if delete cascade
202            # is on.
203            if self.post_update or not self.passive_deletes == 'all':
204                for state in deplist:
205                    history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes)
206                    if history:
207                        for child in history.deleted:
208                            if child is not None and self.hasparent(child) is False:
209                                self._synchronize(state, child, None, True, uowcommit)
210                                self._conditional_post_update(child, uowcommit, [state])
211                        if self.post_update or not self.cascade.delete:
212                            for child in history.unchanged:
213                                if child is not None:
214                                    self._synchronize(state, child, None, True, uowcommit)
215                                    self._conditional_post_update(child, uowcommit, [state])
216        else:
217            for state in deplist:
218                history = uowcommit.get_attribute_history(state, self.key, passive=True)
219                if history:
220                    for child in history.added:
221                        self._synchronize(state, child, None, False, uowcommit)
222                        if child is not None:
223                            self._conditional_post_update(child, uowcommit, [state])
224
225                    for child in history.deleted:
226                        if not self.cascade.delete_orphan and not self.hasparent(child):
227                            self._synchronize(state, child, None, True, uowcommit)
228
229                    if self._pks_changed(uowcommit, state):
230                        for child in history.unchanged:
231                            self._synchronize(state, child, None, False, uowcommit)
232
233    def preprocess_dependencies(self, task, deplist, uowcommit, delete = False):
234        if delete:
235            # head object is being deleted, and we manage its list of child objects
236            # the child objects have to have their foreign key to the parent set to NULL
237            if not self.post_update:
238                should_null_fks = not self.cascade.delete and not self.passive_deletes == 'all'
239                for state in deplist:
240                    history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes)
241                    if history:
242                        for child in history.deleted:
243                            if child is not None and self.hasparent(child) is False:
244                                if self.cascade.delete_orphan:
245                                    uowcommit.register_object(child, isdelete=True)
246                                else:
247                                    uowcommit.register_object(child)
248                        if should_null_fks:
249                            for child in history.unchanged:
250                                if child is not None:
251                                    uowcommit.register_object(child)
252        else:
253            for state in deplist:
254                history = uowcommit.get_attribute_history(state, self.key, passive=True)
255                if history:
256                    for child in history.added:
257                        if child is not None:
258                            uowcommit.register_object(child)
259                    for child in history.deleted:
260                        if not self.cascade.delete_orphan:
261                            uowcommit.register_object(child, isdelete=False)
262                        elif self.hasparent(child) is False:
263                            uowcommit.register_object(child, isdelete=True)
264                            for c, m in self.mapper.cascade_iterator('delete', child):
265                                uowcommit.register_object(
266                                    attributes.instance_state(c),
267                                    isdelete=True)
268                if self._pks_changed(uowcommit, state):
269                    if not history:
270                        history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_updates)
271                    if history:
272                        for child in history.unchanged:
273                            if child is not None:
274                                uowcommit.register_object(child)
275
276    def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
277        source = state
278        dest = child
279        if dest is None or (not self.post_update and uowcommit.is_deleted(dest)):
280            return
281        self._verify_canload(child)
282        if clearkeys:
283            sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
284        else:
285            sync.populate(source, self.parent, dest, self.mapper, self.prop.synchronize_pairs)
286
287    def _pks_changed(self, uowcommit, state):
288        return sync.source_modified(uowcommit, state, self.parent, self.prop.synchronize_pairs)
289
290class DetectKeySwitch(DependencyProcessor):
291    """a special DP that works for many-to-one relations, fires off for
292    child items who have changed their referenced key."""
293
294    no_dependencies = True
295
296    def register_dependencies(self, uowcommit):
297        pass
298
299    def register_processors(self, uowcommit):
300        uowcommit.register_processor(self.parent, self, self.mapper)
301
302    def preprocess_dependencies(self, task, deplist, uowcommit, delete=False):
303        # for non-passive updates, register in the preprocess stage
304        # so that mapper save_obj() gets a hold of changes
305        if not delete and not self.passive_updates:
306            self._process_key_switches(deplist, uowcommit)
307
308    def process_dependencies(self, task, deplist, uowcommit, delete=False):
309        # for passive updates, register objects in the process stage
310        # so that we avoid ManyToOneDP's registering the object without
311        # the listonly flag in its own preprocess stage (results in UPDATE)
312        # statements being emitted
313        if not delete and self.passive_updates:
314            self._process_key_switches(deplist, uowcommit)
315
316    def _process_key_switches(self, deplist, uowcommit):
317        switchers = set(s for s in deplist if self._pks_changed(uowcommit, s))
318        if switchers:
319            # yes, we're doing a linear search right now through the UOW.  only
320            # takes effect when primary key values have actually changed.
321            # a possible optimization might be to enhance the "hasparents" capability of
322            # attributes to actually store all parent references, but this introduces
323            # more complicated attribute accounting.
324            for s in [elem for elem in uowcommit.session.identity_map.all_states()
325                if issubclass(elem.class_, self.parent.class_) and
326                    self.key in elem.dict and
327                    elem.dict[self.key] is not None and
328                    attributes.instance_state(elem.dict[self.key]) in switchers
329                ]:
330                uowcommit.register_object(s)
331                sync.populate(attributes.instance_state(s.dict[self.key]), self.mapper, s, self.parent, self.prop.synchronize_pairs)
332
333    def _pks_changed(self, uowcommit, state):
334        return sync.source_modified(uowcommit, state, self.mapper, self.prop.synchronize_pairs)
335
336class ManyToOneDP(DependencyProcessor):
337    def __init__(self, prop):
338        DependencyProcessor.__init__(self, prop)
339        self.mapper._dependency_processors.append(DetectKeySwitch(prop))
340
341    def register_dependencies(self, uowcommit):
342        if self.post_update:
343            uowcommit.register_dependency(self.mapper, self.dependency_marker)
344            uowcommit.register_dependency(self.parent, self.dependency_marker)
345        else:
346            uowcommit.register_dependency(self.mapper, self.parent)
347   
348    def register_processors(self, uowcommit):
349        if self.post_update:
350            uowcommit.register_processor(self.dependency_marker, self, self.parent)
351        else:
352            uowcommit.register_processor(self.mapper, self, self.parent)
353
354    def process_dependencies(self, task, deplist, uowcommit, delete=False):
355        if delete:
356            if self.post_update and not self.cascade.delete_orphan and not self.passive_deletes == 'all':
357                # post_update means we have to update our row to not reference the child object
358                # before we can DELETE the row
359                for state in deplist:
360                    self._synchronize(state, None, None, True, uowcommit)
361                    history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes)
362                    if history:
363                        self._conditional_post_update(state, uowcommit, history.sum())
364        else:
365            for state in deplist:
366                history = uowcommit.get_attribute_history(state, self.key, passive=True)
367                if history:
368                    for child in history.added:
369                        self._synchronize(state, child, None, False, uowcommit)
370                    self._conditional_post_update(state, uowcommit, history.sum())
371
372    def preprocess_dependencies(self, task, deplist, uowcommit, delete=False):
373        if self.post_update:
374            return
375        if delete:
376            if self.cascade.delete or self.cascade.delete_orphan:
377                for state in deplist:
378                    history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes)
379                    if history:
380                        if self.cascade.delete_orphan:
381                            todelete = history.sum()
382                        else:
383                            todelete = history.non_deleted()
384                        for child in todelete:
385                            if child is None:
386                                continue
387                            uowcommit.register_object(child, isdelete=True)
388                            for c, m in self.mapper.cascade_iterator('delete', child):
389                                uowcommit.register_object(
390                                    attributes.instance_state(c), isdelete=True)
391        else:
392            for state in deplist:
393                uowcommit.register_object(state)
394                if self.cascade.delete_orphan:
395                    history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes)
396                    if history:
397                        for child in history.deleted:
398                            if self.hasparent(child) is False:
399                                uowcommit.register_object(child, isdelete=True)
400                                for c, m in self.mapper.cascade_iterator('delete', child):
401                                    uowcommit.register_object(
402                                        attributes.instance_state(c),
403                                        isdelete=True)
404
405
406    def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
407        if state is None or (not self.post_update and uowcommit.is_deleted(state)):
408            return
409
410        if clearkeys or child is None:
411            sync.clear(state, self.parent, self.prop.synchronize_pairs)
412        else:
413            self._verify_canload(child)
414            sync.populate(child, self.mapper, state, self.parent, self.prop.synchronize_pairs)
415
416class ManyToManyDP(DependencyProcessor):
417    def register_dependencies(self, uowcommit):
418        # many-to-many.  create a "Stub" mapper to represent the
419        # "middle table" in the relationship.  This stub mapper doesnt save
420        # or delete any objects, but just marks a dependency on the two
421        # related mappers.  its dependency processor then populates the
422        # association table.
423
424        uowcommit.register_dependency(self.parent, self.dependency_marker)
425        uowcommit.register_dependency(self.mapper, self.dependency_marker)
426
427    def register_processors(self, uowcommit):
428        uowcommit.register_processor(self.dependency_marker, self, self.parent)
429       
430    def process_dependencies(self, task, deplist, uowcommit, delete = False):
431        connection = uowcommit.transaction.connection(self.mapper)
432        secondary_delete = []
433        secondary_insert = []
434        secondary_update = []
435
436        if delete:
437            for state in deplist:
438                history = uowcommit.get_attribute_history(state, self.key, passive=self.passive_deletes)
439                if history:
440                    for child in history.non_added():
441                        if child is None or self._check_reverse_action(uowcommit, child, state, "manytomany"):
442                            continue
443                        associationrow = {}
444                        self._synchronize(state, child, associationrow, False, uowcommit)
445                        secondary_delete.append(associationrow)
446                        self._performed_action(uowcommit, state, child, "manytomany")
447        else:
448            for state in deplist:
449                history = uowcommit.get_attribute_history(state, self.key)
450                if history:
451                    for child in history.added:
452                        if child is None or self._check_reverse_action(uowcommit, child, state, "manytomany"):
453                            continue
454                        associationrow = {}
455                        self._synchronize(state, child, associationrow, False, uowcommit)
456                        self._performed_action(uowcommit, state, child, "manytomany")
457                        secondary_insert.append(associationrow)
458                    for child in history.deleted:
459                        if child is None or self._check_reverse_action(uowcommit, child, state, "manytomany"):
460                            continue
461                        associationrow = {}
462                        self._synchronize(state, child, associationrow, False, uowcommit)
463                        self._performed_action(uowcommit, state, child, "manytomany")
464                        secondary_delete.append(associationrow)
465
466                if not self.passive_updates and self._pks_changed(uowcommit, state):
467                    if not history:
468                        history = uowcommit.get_attribute_history(state, self.key, passive=False)
469                   
470                    for child in history.unchanged:
471                        associationrow = {}
472                        sync.update(state, self.parent, associationrow, "old_", self.prop.synchronize_pairs)
473                        sync.update(child, self.mapper, associationrow, "old_", self.prop.secondary_synchronize_pairs)
474
475                        #self.syncrules.update(associationrow, state, child, "old_")
476                        secondary_update.append(associationrow)
477
478        if secondary_delete:
479            statement = self.secondary.delete(sql.and_(*[
480                                c == sql.bindparam(c.key, type_=c.type) for c in self.secondary.c if c.key in associationrow
481                            ]))
482            result = connection.execute(statement, secondary_delete)
483            if result.supports_sane_multi_rowcount() and result.rowcount != len(secondary_delete):
484                raise exc.ConcurrentModificationError("Deleted rowcount %d does not match number of "
485                            "secondary table rows deleted from table '%s': %d" %
486                            (result.rowcount, self.secondary.description, len(secondary_delete)))
487
488        if secondary_update:
489            statement = self.secondary.update(sql.and_(*[
490                                c == sql.bindparam("old_" + c.key, type_=c.type) for c in self.secondary.c if c.key in associationrow
491                            ]))
492            result = connection.execute(statement, secondary_update)
493            if result.supports_sane_multi_rowcount() and result.rowcount != len(secondary_update):
494                raise exc.ConcurrentModificationError("Updated rowcount %d does not match number of "
495                            "secondary table rows updated from table '%s': %d" %
496                            (result.rowcount, self.secondary.description, len(secondary_update)))
497
498        if secondary_insert:
499            statement = self.secondary.insert()
500            connection.execute(statement, secondary_insert)
501
502    def preprocess_dependencies(self, task, deplist, uowcommit, delete = False):
503        if not delete:
504            for state in deplist:
505                history = uowcommit.get_attribute_history(state, self.key, passive=True)
506                if history:
507                    for child in history.deleted:
508                        if self.cascade.delete_orphan and self.hasparent(child) is False:
509                            uowcommit.register_object(child, isdelete=True)
510                            for c, m in self.mapper.cascade_iterator('delete', child):
511                                uowcommit.register_object(
512                                    attributes.instance_state(c), isdelete=True)
513
514    def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
515        if associationrow is None:
516            return
517        self._verify_canload(child)
518       
519        sync.populate_dict(state, self.parent, associationrow, self.prop.synchronize_pairs)
520        sync.populate_dict(child, self.mapper, associationrow, self.prop.secondary_synchronize_pairs)
521
522    def _pks_changed(self, uowcommit, state):
523        return sync.source_modified(uowcommit, state, self.parent, self.prop.synchronize_pairs)
524
525class MapperStub(object):
526    """Represent a many-to-many dependency within a flush
527    context.
528     
529    The UOWTransaction corresponds dependencies to mappers.   
530    MapperStub takes the place of the "association table"
531    so that a depedendency can be corresponded to it.
532
533    """
534   
535    def __init__(self, parent, mapper, key):
536        self.mapper = mapper
537        self.base_mapper = self
538        self.class_ = mapper.class_
539        self._inheriting_mappers = []
540
541    def polymorphic_iterator(self):
542        return iter((self,))
543
544    def _register_dependencies(self, uowcommit):
545        pass
546
547    def _register_procesors(self, uowcommit):
548        pass
549
550    def _save_obj(self, *args, **kwargs):
551        pass
552
553    def _delete_obj(self, *args, **kwargs):
554        pass
555
556    def primary_mapper(self):
557        return self
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。