From 019866eb894ce7cb9350482e868ba381c95b5ac6 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 8 Sep 2005 00:16:25 +0000 Subject: [PATCH] much uncertainty with dependency sorting there is ! --- lib/sqlalchemy/mapper.py | 92 ++++++++++++++++++++++++++++++----- lib/sqlalchemy/objectstore.py | 37 +++++++++----- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index 66b9f147a7..a4eb5f5cee 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -36,8 +36,8 @@ def relation_loader(mapper, secondary = None, primaryjoin = None, secondaryjoin else: return EagerLoader(mapper, secondary, primaryjoin, secondaryjoin, **options) -def relation_mapper(class_, selectable, secondary = None, primaryjoin = None, secondaryjoin = None, table = None, properties = None, lazy = True, uselist = True, **options): - return relation_loader(mapper(class_, selectable, table = table, properties = properties, **options), secondary, primaryjoin, secondaryjoin, lazy = lazy, uselist = uselist, **options) +def relation_mapper(class_, selectable, secondary = None, primaryjoin = None, secondaryjoin = None, table = None, properties = None, lazy = True, uselist = True, direction = None, **options): + return relation_loader(mapper(class_, selectable, table = table, properties = properties, **options), secondary, primaryjoin, secondaryjoin, lazy = lazy, uselist = uselist, direction = direction, **options) _mappers = {} def mapper(*args, **params): @@ -55,6 +55,15 @@ def eagerload(name): def lazyload(name): return EagerLazySwitcher(name, toeager = False) +def object_mapper(object): + try: + return _mappers[object._mapper] + except AttributeError: + try: + return _mappers[object.__class__._mapper] + except AttributeError: + raise "Object " + object.__class__.__name__ + "/" + repr(id(object)) + " has no mapper specified" + class Mapper(object): def __init__(self, class_, selectable, table = None, scope = "thread", properties = None, echo = None, **kwargs): self.class_ = class_ @@ -117,14 +126,16 @@ class Mapper(object): self.init() def hash_key(self): - return mapper_hash_key( - self.class_, - self.selectable, - self.table, - self.properties, - self.scope, - self.echo - ) + if not hasattr(self, 'hashkey'): + self.hashkey = mapper_hash_key( + self.class_, + self.selectable, + self.table, + self.properties, + self.scope, + self.echo + ) + return self.hashkey def set_property(self, key, prop): self.props[key] = prop @@ -132,6 +143,7 @@ class Mapper(object): def init(self): [prop.init(key, self) for key, prop in self.props.iteritems()] + self.class_._mapper = self.hash_key() def instances(self, cursor): result = util.HistoryArraySet() @@ -268,6 +280,10 @@ class Mapper(object): for prop in self.props.values(): prop.save(obj, traverse) + def register_dependencies(self, obj, uow): + for prop in self.props.values(): + prop.register_dependencies(obj, uow) + def transaction(self, f): return self.table.engine.multi_transaction(self.tables, f) @@ -306,6 +322,7 @@ class Mapper(object): exists = objectstore.has_key(identitykey) if not exists: instance = self.class_() + instance._mapper = self.hash_key() for column in self.selectable.primary_keys: if row[column.label] is None: return None @@ -364,6 +381,9 @@ class MapperProperty: def delete(self, object): """called when the instance is being deleted""" pass + + def register_dependencies(self, obj, uow): + pass class ColumnProperty(MapperProperty): """describes an object attribute that corresponds to a table column.""" @@ -393,21 +413,26 @@ class ColumnProperty(MapperProperty): class PropertyLoader(MapperProperty): """describes an object property that holds a list of items that correspond to a related database table.""" - def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, uselist = True): + def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, uselist = True, direction = None): self.uselist = uselist self.mapper = mapper self.target = self.mapper.selectable self.secondary = secondary self.primaryjoin = primaryjoin self.secondaryjoin = secondaryjoin + self.direction = direction self._hash_key = "%s(%s, %s, %s, %s, uselist=%s)" % (self.__class__.__name__, hash_key(mapper), hash_key(secondary), hash_key(primaryjoin), hash_key(secondaryjoin), repr(self.uselist)) - + if self.direction is not None and self.direction != 'left' and self.direction != 'right': + raise "direction propery must be 'left', 'right' or None" + def hash_key(self): return self._hash_key def init(self, key, parent): self.key = key self.parent = parent + + # if join conditions were not specified, figure them out based on primary keys if self.secondary is not None: if self.secondaryjoin is None: self.secondaryjoin = self.match_primaries(self.target, self.secondary) @@ -416,10 +441,36 @@ class PropertyLoader(MapperProperty): else: if self.primaryjoin is None: self.primaryjoin = self.match_primaries(parent.selectable, self.target) + + # 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. + if self.foreignkey is None 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 + w = PropertyLoader.FindDependent() + w.accept_visitor(self.primaryjoin) + if w.dependent is None: + raise "cant determine primary foreign key in the join relationship....specify foreignkey=" + else: + self.foreignkey = w.dependent if not hasattr(parent.class_, key): setattr(parent.class_, key, SmartProperty(key).property(usehistory = True, uselist = self.uselist)) + class FindDependent(sql.ClauseVisitor): + def visit_binary(self, binary): + if binary.operator == '=': + if binary.left.primary_key: + if self.dependent == binary.left: + raise "bidirectional dependency not supported...specify foreignkey" + self.dependent = binary.right + elif binary.right.primary_key: + if self.dependent == binary.right: + raise "bidirectional dependency not supported...specify foreignkey" + self.dependent = binary.left + + def match_primaries(self, primary, secondary): pk = primary.primary_keys if len(pk) == 1: @@ -427,6 +478,23 @@ class PropertyLoader(MapperProperty): else: return sql.and_([pk == secondary.c[pk.name] for pk in primary.primary_keys]) + def register_dependencies(self, obj, uow): + if self.uselist: + childlist = objectstore.uow().register_list_attribute(obj, self.key) + else: + childlist = objectstore.uow().register_attribute(obj, self.key) + + if self.secondarytable is not None: + # TODO: put a "row" as a dependency into the UOW somehow + pass + elif self.foreignkey.table == self.target: + for child in childlist.added_items(): + uow.register_dependency(obj, child) + elif self.foreignkey.table == self.secondary: + for child in childlist.added_items(): + uow.register_dependency(child, obj) + + def save(self, obj, traverse): # saves child objects diff --git a/lib/sqlalchemy/objectstore.py b/lib/sqlalchemy/objectstore.py index 3d5e105dfc..950cbac34f 100644 --- a/lib/sqlalchemy/objectstore.py +++ b/lib/sqlalchemy/objectstore.py @@ -109,8 +109,8 @@ def has_key(key): class UnitOfWork(object): def __init__(self): + self.new = util.HashSet() self.dirty = util.HashSet() - self.clean = util.HashSet() self.deleted = util.HashSet() self.attribute_history = weakref.WeakKeyDictionary() @@ -168,28 +168,22 @@ class UnitOfWork(object): return childlist def register_clean(self, obj): - self.clean.append(obj) try: del self.dirty[obj] except KeyError: pass def register_new(self, obj): - pass + self.new.append(obj) def register_dirty(self, obj): self.dirty.append(obj) - try: - del self.clean[obj] - except KeyError: - pass def is_dirty(self, obj): if not self.dirty.contains(obj): # if we know nothing about this object, register it as dirty (or new ?) if not self.clean.contains(obj): - # TODO: should this be register_new ? - self.register_dirty(obj) + self.register_new(obj) return True return False else: @@ -197,10 +191,29 @@ class UnitOfWork(object): def register_deleted(self, obj): pass - + def commit(self): - for item in self.dirty: - self.clean.append(item) + import sqlalchemy.mapper as mapper + + self.dependencies = {} + + for obj in self.new: + mapper = mapper.object_mapper(obj) + mapper.register_dependencies(item, self) + for obj in self.dirty: + mapper = mapper.object_mapper(obj) + mapper.register_dependencies(obj, self) +# for item in self.deleted: +# mapper = mapper.object_mapper(item) +# sort save instructions +# execute save instructions +# hmmmmm, as we save items, we have to populate the dependencies too +# then the save comes down to them and they are populated self.dirty.clear() +# self.deleted.clear() + + def register_dependency(self, obj, dependency): + pass + uow = util.ScopedRegistry(lambda: UnitOfWork(), "thread") \ No newline at end of file -- 2.47.2