From bd13d4ed2eb686c881530bef0e6009bf8fea1cc9 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 29 Sep 2006 23:46:57 +0000 Subject: [PATCH] - added some dependency logging - moved the ClauseSynchronizer compile from properties to dependency where its used --- lib/sqlalchemy/orm/dependency.py | 58 ++++++++++++++++++++++--------- lib/sqlalchemy/orm/properties.py | 28 ++++----------- lib/sqlalchemy/orm/topological.py | 9 +++-- lib/sqlalchemy/orm/unitofwork.py | 6 ++-- 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 4deb8297f7..9c87eb0b34 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -8,34 +8,36 @@ """bridges the PropertyLoader (i.e. a relation()) and the UOWTransaction together to allow processing of scalar- and list-based dependencies at flush time.""" +import sync from sync import ONETOMANY,MANYTOONE,MANYTOMANY from sqlalchemy import sql, util import session as sessionlib -def create_dependency_processor(key, syncrules, cascade, secondary=None, association=None, is_backref=False, post_update=False): +def create_dependency_processor(prop): types = { ONETOMANY : OneToManyDP, MANYTOONE: ManyToOneDP, MANYTOMANY : ManyToManyDP, } - if association is not None: - return AssociationDP(key, syncrules, cascade, secondary, association, is_backref, post_update) + if prop.association is not None: + return AssociationDP(prop) else: - return types[syncrules.direction](key, syncrules, cascade, secondary, association, is_backref, post_update) + return types[prop.direction](prop) class DependencyProcessor(object): - def __init__(self, key, syncrules, cascade, secondary=None, association=None, is_backref=False, post_update=False): - # TODO: update instance variable names to be more meaningful - self.syncrules = syncrules - self.cascade = cascade - self.mapper = syncrules.child_mapper - self.parent = syncrules.parent_mapper - self.association = association - self.secondary = secondary - self.direction = syncrules.direction - self.is_backref = is_backref - self.post_update = post_update - self.key = key + def __init__(self, prop): + self.prop = prop + self.cascade = prop.cascade + self.mapper = prop.mapper + self.parent = prop.parent + self.association = prop.association + self.secondary = prop.secondary + self.direction = prop.direction + self.is_backref = prop.is_backref + self.post_update = prop.post_update + self.key = prop.key + + self._compile_synchronizers() def register_dependencies(self, uowcommit): """tells a UOWTransaction what mappers are dependent on which, with regards @@ -75,6 +77,23 @@ class DependencyProcessor(object): """called during a flush to synchronize primary key identifier values between a parent/child object, as well as to an associationrow in the case of many-to-many.""" raise NotImplementedError() + + def _compile_synchronizers(self): + """assembles a list of 'synchronization rules', which are instructions on how to populate + the objects on each side of a relationship. This is done when a PropertyLoader is + first initialized. + + The list of rules is used within commits by the _synchronize() method when dependent + objects are processed.""" + parent_tables = util.Set(self.parent.tables + [self.parent.mapped_table]) + target_tables = util.Set(self.mapper.tables + [self.mapper.mapped_table]) + + self.syncrules = sync.ClauseSynchronizer(self.parent, self.mapper, self.direction) + if self.direction == sync.MANYTOMANY: + self.syncrules.compile(self.prop.primaryjoin, parent_tables, [self.secondary], False) + self.syncrules.compile(self.prop.secondaryjoin, target_tables, [self.secondary], True) + else: + self.syncrules.compile(self.prop.primaryjoin, parent_tables, target_tables) def get_object_dependencies(self, obj, uowcommit, passive = True): """returns the list of objects that are dependent on the given object, as according to the relationship @@ -82,6 +101,13 @@ class DependencyProcessor(object): return sessionlib.attribute_manager.get_history(obj, self.key, passive = passive) def _conditional_post_update(self, obj, uowcommit, related): + """execute a post_update call. + + for relations that contain the post_update flag, an additional UPDATE statement may be + associated after an INSERT or before a DELETE in order to resolve circular row dependencies. + This method will check for the post_update flag being set on a particular relationship, and + given a target object and list of one or more related objects, and execute the UPDATE if the + given related object list contains INSERTs or DELETEs.""" if obj is not None and self.post_update: for x in related: if x is not None and (uowcommit.is_deleted(x) or not hasattr(x, '_instance_key')): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index d535ccc764..f76f46036a 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -9,8 +9,8 @@ well as relationships. also defines some MapperOptions that can be used with th properties.""" from sqlalchemy import sql, schema, util, attributes, exceptions, sql_util, logging -import sync import mapper +import sync import session as sessionlib import dependency import util as mapperutil @@ -250,7 +250,7 @@ class PropertyLoader(mapper.MapperProperty): raise exceptions.ArgumentError("Error determining primary and/or secondary join for relationship '%s' between mappers '%s' and '%s'. If the underlying error cannot be corrected, you should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is \"%s\"" % (self.key, self.localparent, self.mapper, str(e))) # if the foreign key wasnt specified and theres no assocaition table, try to figure # out who is dependent on who. we dont need all the foreign keys represented in the join, - # just one of them. + # just one of them. if not len(self.foreignkey) and self.secondaryjoin is None: # else we usually will have a one-to-many where the secondary depends on the primary # but its possible that its reversed @@ -273,8 +273,7 @@ class PropertyLoader(mapper.MapperProperty): self._dependency_processor = self.inherits._dependency_processor if not hasattr(self, '_dependency_processor'): - self._compile_synchronizers() - self._dependency_processor = dependency.create_dependency_processor(self.key, self.syncrules, self.cascade, secondary=self.secondary, association=self.association, is_backref=self.is_backref, post_update=self.post_update) + self._dependency_processor = dependency.create_dependency_processor(self) if self.inherits is not None and not hasattr(self.inherits, '_dependency_processor'): self.inherits._dependency_processor = self._dependency_processor @@ -307,12 +306,15 @@ class PropertyLoader(mapper.MapperProperty): def _set_class_attribute(self, class_, key): """sets attribute behavior on our target class.""" self._register_attribute(class_) + + def _is_self_referential(self): + return self.parent.mapped_table is self.target or self.parent.select_table is self.target def _get_direction(self): """determines our 'direction', i.e. do we represent one to many, many to many, etc.""" if self.secondaryjoin is not None: return sync.MANYTOMANY - elif self.parent.mapped_table is self.target or self.parent.select_table is self.target: + elif self._is_self_referential(): if list(self.foreignkey)[0].primary_key: return sync.MANYTOONE else: @@ -356,22 +358,6 @@ class PropertyLoader(mapper.MapperProperty): if not self.viewonly: self._dependency_processor.register_dependencies(uowcommit) - def _compile_synchronizers(self): - """assembles a list of 'synchronization rules', which are instructions on how to populate - the objects on each side of a relationship. This is done when a PropertyLoader is - first initialized. - - The list of rules is used within commits by the _synchronize() method when dependent - objects are processed.""" - parent_tables = util.Set(self.parent.tables + [self.parent.mapped_table]) - target_tables = util.Set(self.mapper.tables + [self.mapper.mapped_table]) - - self.syncrules = sync.ClauseSynchronizer(self.parent, self.mapper, self.direction) - if self.direction == sync.MANYTOMANY: - self.syncrules.compile(self.primaryjoin, parent_tables, [self.secondary], False) - self.syncrules.compile(self.secondaryjoin, target_tables, [self.secondary], True) - else: - self.syncrules.compile(self.primaryjoin, parent_tables, target_tables) PropertyLoader.logger = logging.class_logger(PropertyLoader) diff --git a/lib/sqlalchemy/orm/topological.py b/lib/sqlalchemy/orm/topological.py index d9ec5cde98..8c481b6f11 100644 --- a/lib/sqlalchemy/orm/topological.py +++ b/lib/sqlalchemy/orm/topological.py @@ -52,9 +52,14 @@ class QueueDependencySorter(object): def __str__(self): return self.safestr() def safestr(self, indent=0): - return (' ' * indent) + "%s (idself=%s)" % (str(self.item), repr(id(self))) + repr(self.cycles) + "\n" + string.join([n.safestr(indent + 1) for n in self.children], '') + return (' ' * indent * 2) + \ + str(self.item) + \ + (self.cycles is not None and (" (cycles: " + repr([x for x in self.cycles]) + ")") or "") + \ + "\n" + \ + string.join([n.safestr(indent + 1) for n in self.children], '') + def describe(self): - return "%s (idself=%s)" % (str(self.item), repr(id(self))) + return "%s" % (str(self.item)) def __repr__(self): return self.describe() def is_dependent(self, child): diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 008236877c..68dfc3fbbb 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -377,9 +377,7 @@ class UOWTransaction(object): mappers = self._get_noninheriting_mappers() head = DependencySorter(self.dependencies, list(mappers)).sort(allow_all_cycles=True) - #print "-------------------------" - #print str(head) - #print "---------------------------" + self.logger.debug("Dependency sort:\n"+ str(head)) task = sort_hier(head) return task @@ -722,7 +720,7 @@ class UOWTask(object): #print "BEGIN CIRC SORT-------" #print "PRE-CIRC:" - #print list(cycles)[0].dump() + #print list(cycles) #[0].dump() # dependency processors that arent part of the cyclical thing # get put here -- 2.47.2