diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-06-21 16:08:04 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-06-21 16:08:04 +0000 |
commit | 060c3ce33c02c2bcd78b54e05da8b9196a296b62 (patch) | |
tree | 76d2a479536efcd709bffa6c4264b66e2fbba625 | |
parent | 8af372585a7da02507036219118d17b2b58e2aad (diff) | |
download | sqlalchemy-060c3ce33c02c2bcd78b54e05da8b9196a296b62.tar.gz |
- In addition to expired attributes, deferred attributes
also load if their data is present in the result set
[ticket:870]
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 13 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 7 | ||||
-rw-r--r-- | test/orm/mapper.py | 85 | ||||
-rw-r--r-- | test/perf/ormsession.py | 3 |
5 files changed, 108 insertions, 7 deletions
@@ -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 2dbef2497..c6c1860ea 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 dd54ed986..da471b4d1 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 9d1a4e576..31920cdf7 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 d8ae187e5..cdffa51a9 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')) |