]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- In addition to expired attributes, deferred attributes
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 21 Jun 2008 16:08:04 +0000 (16:08 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 21 Jun 2008 16:08:04 +0000 (16:08 +0000)
also load if their data is present in the result set
[ticket:870]

CHANGES
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/mapper.py
test/orm/mapper.py
test/perf/ormsession.py

diff --git a/CHANGES b/CHANGES
index 87a0151d1b99bad60e64b1a48510a34ba3ee0e4b..992138a31295e47f6e1622dd623222e630074632 100644 (file)
--- 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
 
index 2dbef249702f6ec799b20def2e99677fdfbeb2aa..c6c1860eae3af9e1261aff5f525a2330ee36d1ad 100644 (file)
@@ -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)
index dd54ed986abe2268c3fd7c5779b1f313e953d41f..da471b4d1b2802f8045eaf8bbcb6ff4fc392169b 100644 (file)
@@ -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:
index 9d1a4e5767d3dd7253baca49cc306b3d2e70d941..31920cdf738254ab70f18f6ac094ac1f8ef2d8a5 100644 (file)
@@ -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):
index d8ae187e5660ab62f287677306e25ef1e76412aa..cdffa51a96ae60a55f26600edf3abfb4dc212a79 100644 (file)
@@ -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'))