summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-06-21 16:08:04 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-06-21 16:08:04 +0000
commit060c3ce33c02c2bcd78b54e05da8b9196a296b62 (patch)
tree76d2a479536efcd709bffa6c4264b66e2fbba625
parent8af372585a7da02507036219118d17b2b58e2aad (diff)
downloadsqlalchemy-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--CHANGES7
-rw-r--r--lib/sqlalchemy/orm/attributes.py13
-rw-r--r--lib/sqlalchemy/orm/mapper.py7
-rw-r--r--test/orm/mapper.py85
-rw-r--r--test/perf/ormsession.py3
5 files changed, 108 insertions, 7 deletions
diff --git a/CHANGES b/CHANGES
index 87a0151d1..992138a31 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 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'))