From 060c3ce33c02c2bcd78b54e05da8b9196a296b62 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 21 Jun 2008 16:08:04 +0000 Subject: [PATCH] - In addition to expired attributes, deferred attributes also load if their data is present in the result set [ticket:870] --- CHANGES | 7 ++- lib/sqlalchemy/orm/attributes.py | 13 +++++ lib/sqlalchemy/orm/mapper.py | 7 ++- test/orm/mapper.py | 85 +++++++++++++++++++++++++++++++- test/perf/ormsession.py | 3 +- 5 files changed, 108 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 87a0151d1b..992138a312 100644 --- a/CHANGES +++ b/CHANGES @@ -3,9 +3,14 @@ ======= CHANGES ======= +0.5beta2 +======== + - In addition to expired attributes, deferred attributes + also load if their data is present in the result set + [ticket:870] 0.5beta1 -================== +======== - An ongoing document describing the changes from 0.4 to 0.5 is at: http://www.sqlalchemy.org/trac/wiki/05Migration diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 2dbef24970..c6c1860eae 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -883,6 +883,19 @@ class InstanceState(object): or (key in self.manager.mutable_attributes and not self.manager[key].impl.check_mutable_modified(self)) ]) unmodified = property(unmodified) + + def unloaded(self): + """a set of keys which do not have a loaded value. + + This includes expired attributes and any other attribute that + was never populated or modified. + + """ + return util.Set([ + key for key in self.manager.keys() if + key not in self.committed_state and key not in self.dict + ]) + unloaded = property(unloaded) def expire_attributes(self, attribute_names): self.expired_attributes = util.Set(self.expired_attributes) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index dd54ed986a..da471b4d1b 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1473,16 +1473,15 @@ class Mapper(object): else: # populate attributes on non-loading instances which have been expired - # TODO: also support deferred attributes here [ticket:870] # TODO: apply eager loads to un-lazy loaded collections ? - # we might want to create an expanded form of 'state.expired_attributes' which includes deferred/un-lazy loaded - if state.expired_attributes: + if state in context.partials or state.unloaded: + if state in context.partials: isnew = False attrs = context.partials[state] else: isnew = True - attrs = state.expired_attributes.intersection(state.unmodified) + attrs = state.unloaded context.partials[state] = attrs #<-- allow query.instances to commit the subset of attrs if not populate_instance or extension.populate_instance(self, context, row, instance, only_load_props=attrs, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE: diff --git a/test/orm/mapper.py b/test/orm/mapper.py index 9d1a4e5767..31920cdf73 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -4,7 +4,7 @@ import testenv; testenv.configure_for_tests() from testlib import sa, testing from testlib.sa import MetaData, Table, Column, Integer, String, ForeignKey from testlib.sa.orm import mapper, relation, backref, create_session -from testlib.sa.orm import defer, deferred, synonym +from testlib.sa.orm import defer, deferred, synonym, attributes from testlib.testing import eq_ from testlib.compat import set import pickleable @@ -1258,7 +1258,90 @@ class DeferredTest(_fixtures.FixtureTest): self.sql_count_(0, go) eq_(item.description, 'item 4') +class DeferredPopulationTest(_base.MappedTest): + def define_tables(self, metadata): + Table("thing", metadata, + Column("id", Integer, primary_key=True), + Column("name", String)) + + Table("human", metadata, + Column("id", Integer, primary_key=True), + Column("thing_id", Integer, ForeignKey("thing.id")), + Column("name", String)) + + @testing.resolve_artifact_names + def setup_mappers(self): + class Human(_base.BasicEntity): pass + class Thing(_base.BasicEntity): pass + + mapper(Human, human, properties={"thing": relation(Thing)}) + mapper(Thing, thing, properties={"name": deferred(thing.c.name)}) + + @testing.resolve_artifact_names + def insert_data(self): + thing.insert().execute([ + {"id": 1, "name": "Chair"}, + ]) + + human.insert().execute([ + {"id": 1, "thing_id": 1, "name": "Clark Kent"}, + ]) + + def _test(self, thing): + assert "name" in attributes.instance_state(thing).dict + @testing.resolve_artifact_names + def test_no_previous_query(self): + session = create_session() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + @testing.resolve_artifact_names + def test_query_twice_with_clear(self): + session = create_session() + result = session.query(Thing).first() + session.clear() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + @testing.resolve_artifact_names + def test_query_twice_no_clear(self): + session = create_session() + result = session.query(Thing).first() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + @testing.resolve_artifact_names + def test_eagerload_with_clear(self): + session = create_session() + human = session.query(Human).options(sa.orm.eagerload("thing")).first() + session.clear() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + @testing.resolve_artifact_names + def test_eagerload_no_clear(self): + session = create_session() + human = session.query(Human).options(sa.orm.eagerload("thing")).first() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + @testing.resolve_artifact_names + def test_join_with_clear(self): + session = create_session() + result = session.query(Human).add_entity(Thing).join("thing").first() + session.clear() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + @testing.resolve_artifact_names + def test_join_no_clear(self): + session = create_session() + result = session.query(Human).add_entity(Thing).join("thing").first() + thing = session.query(Thing).options(sa.orm.undefer("name")).first() + self._test(thing) + + class CompositeTypesTest(_base.MappedTest): def define_tables(self, metadata): diff --git a/test/perf/ormsession.py b/test/perf/ormsession.py index d8ae187e56..cdffa51a96 100644 --- a/test/perf/ormsession.py +++ b/test/perf/ormsession.py @@ -151,7 +151,8 @@ def run_queries(): # the top 20 items from all purchases q = session.query(Purchase). \ - limit(50).order_by(desc(Purchase.purchase_date)). \ + order_by(desc(Purchase.purchase_date)). \ + limit(50).\ options(eagerload('items'), eagerload('items.subitems'), eagerload('customer')) -- 2.47.3