From aa839293175dae0dc2987891961b924b97940cac Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 29 Sep 2006 20:41:37 +0000 Subject: [PATCH] [ticket:309] --- lib/sqlalchemy/orm/mapper.py | 35 ++++++++++++++++++-------------- lib/sqlalchemy/orm/properties.py | 10 +++++++-- test/orm/mapper.py | 26 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 29688df21e..24225bd6d3 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -517,8 +517,10 @@ class Mapper(object): self.add_property(key, value) def add_property(self, key, prop): - """adds an indiviual MapperProperty to this mapper. If the mapper has not been compiled yet, - just adds the property to the initial properties dictionary sent to the constructor. if this Mapper + """add an indiviual MapperProperty to this mapper. + + If the mapper has not been compiled yet, just adds the property to the initial + properties dictionary sent to the constructor. if this Mapper has already been compiled, then the given MapperProperty is compiled immediately.""" self.properties[key] = prop if self.__is_compiled: @@ -547,13 +549,14 @@ class Mapper(object): else: return None - def _compile_property(self, key, prop, init=True, skipmissing=False): - """adds an additional property to this mapper. this is the same as if it were - specified within the 'properties' argument to the constructor. if the named - property already exists, this will replace it. Useful for - circular relationships, or overriding the parameters of auto-generated properties - such as backreferences.""" - + def _compile_property(self, key, prop, init=True, skipmissing=False, localparent=None): + """add a MapperProperty to this or another Mapper, including configuration of the property. + + The properties' parent attribute will be set, and the property will also be + copied amongst the mappers which inherit from this one. + + if the given prop is a Column or list of Columns, a ColumnProperty will be created. + """ self.__log("_compile_property(%s, %s)" % (key, prop.__class__.__name__)) if not isinstance(prop, MapperProperty): @@ -561,9 +564,10 @@ class Mapper(object): if prop is None: raise exceptions.ArgumentError("'%s' is not an instance of MapperProperty or Column" % repr(prop)) - self.__props[key] = prop + effectiveparent = localparent or self + effectiveparent.__props[key] = prop prop.set_parent(self) - + if isinstance(prop, ColumnProperty): col = self.select_table.corresponding_column(prop.columns[0], keys_ok=True, raiseerr=False) if col is None: @@ -574,11 +578,11 @@ class Mapper(object): proplist.append(prop) if init: - prop.init(key, self) + prop.init(key, effectiveparent) - for mapper in self._inheriting_mappers: + for mapper in effectiveparent._inheriting_mappers: prop.adapt_to_inherited(key, mapper) - + def __str__(self): return "Mapper|" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") @@ -1034,6 +1038,7 @@ class Mapper(object): identitykey = self._row_identity_key(row) if session.has_key(identitykey): instance = session._get(identitykey) + self.__log_debug("_instance(): using existing instance %s identity %s" % (mapperutil.instance_str(instance), str(identitykey))) isnew = False if version_check and self.version_id_col is not None and self._getattrbycolumn(instance, self.version_id_col) != row[self.version_id_col]: raise exceptions.ConcurrentModificationError("Instance '%s' version of %s does not match %s" % (instance, self._getattrbycolumn(instance, self.version_id_col), row[self.version_id_col])) @@ -1070,7 +1075,7 @@ class Mapper(object): instance = self.extension.create_instance(self, session, row, imap, self.class_) if instance is EXT_PASS: instance = self._create_instance(session) - self.__log_debug("new instance %s identity %s" % (mapperutil.instance_str(instance), str(identitykey))) + self.__log_debug("_instance(): created new instance %s identity %s" % (mapperutil.instance_str(instance), str(identitykey))) imap[identitykey] = instance isnew = True else: diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 6e9f9daff5..58debab025 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -377,6 +377,7 @@ PropertyLoader.logger = logging.class_logger(PropertyLoader) class LazyLoader(PropertyLoader): def do_init_subclass(self): + print "LAZYLOADER %d DOINITSUBCLASS" % id(self) (self.lazywhere, self.lazybinds, self.lazyreverse) = create_lazy_clause(self.parent.unjoined_table, self.primaryjoin, self.secondaryjoin, self.foreignkey) # determine if our "lazywhere" clause is the same as the mapper's # get() clause. then we can just use mapper.get() @@ -496,6 +497,7 @@ def create_lazy_clause(table, primaryjoin, secondaryjoin, foreignkey): lazywhere.accept_visitor(li) if secondaryjoin is not None: lazywhere = sql.and_(lazywhere, secondaryjoin) + LazyLoader.logger.debug("create_lazy_clause " + str(lazywhere)) return (lazywhere, binds, reverse) @@ -808,8 +810,12 @@ class EagerLazyOption(GenericOption): oldprop = mapper.props[key] newprop = class_.__new__(class_) newprop.__dict__.update(oldprop.__dict__) - newprop.do_init_subclass() - mapper._compile_property(key, newprop) + #newprop.do_init_subclass() + p = newprop + while p.inherits is not None: + p = p.inherits + real_parent_mapper = p.parent + real_parent_mapper._compile_property(key, newprop, localparent=mapper) class DeferredOption(GenericOption): def __init__(self, key, defer=False, **kwargs): diff --git a/test/orm/mapper.py b/test/orm/mapper.py index 12059d28e2..c22319ec89 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -502,6 +502,32 @@ class InheritanceTest(MapperSuperTest): sess.clear() au = sess.query(usermapper).get_by(user_name='jack') self.assert_(au.email_address == 'jack@gmail.com') + + def testlazyoption(self): + """test that a lazy options gets created against its correct mapper when + using options with inheriting mappers""" + class _Order(object): + pass + class _User(object): + pass + class AddressUser(_User): + pass + ordermapper = mapper(_Order, orders) + usermapper = mapper(_User, users, + properties = { + 'orders' : relation(ordermapper, lazy=True) + }) + amapper = mapper(AddressUser, addresses, inherits = usermapper) + + sess = create_session() + + def go(): + l = sess.query(AddressUser).options(lazyload('orders')).select() + # this would fail because the "orders" lazyloader gets created against AddressUsers selectable + # and not _User's. + assert len(l[0].orders) == 3 + self.assert_sql_count(db, go, 2) + class DeferredTest(MapperSuperTest): -- 2.47.2