]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed a critical regression caused by :ticket:`3061` where the
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 17 Apr 2015 20:06:04 +0000 (16:06 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 17 Apr 2015 20:06:04 +0000 (16:06 -0400)
NEVER_SET symbol could easily leak into a lazyload query, subsequent
to the flush of a pending object.  This would occur typically
for a many-to-one relationship that does not use a simple
"get" strategy.   The good news is that the fix improves efficiency
vs. 0.9, because we can now skip the SELECT statement entirely
when we detect NEVER_SET symbols present in the parameters; prior to
:ticket:`3061`, we couldn't discern if the None here were set or not.
fixes #3368

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/base.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
test/orm/test_lazy_relations.py

index d13202d71297203237e444e80f704aab6513f1b5..cff3f1b5c949941effe9da3c2dc6e7a965a459d1 100644 (file)
     .. include:: changelog_07.rst
         :start-line: 5
 
+.. changelog::
+    :version: 1.0.1
+
+    .. change::
+        :tags: bug, orm
+        :tickets: 3368
+
+        Fixed a critical regression caused by :ticket:`3061` where the
+        NEVER_SET symbol could easily leak into a lazyload query, subsequent
+        to the flush of a pending object.  This would occur typically
+        for a many-to-one relationship that does not use a simple
+        "get" strategy.   The good news is that the fix improves efficiency
+        vs. 0.9, because we can now skip the SELECT statement entirely
+        when we detect NEVER_SET symbols present in the parameters; prior to
+        :ticket:`3061`, we couldn't discern if the None here were set or not.
+
+
 .. changelog::
     :version: 1.0.0
     :released: April 16, 2015
index c259878f0b8f6ea1170f3456f7b7573fc8cc2e7b..785bd09dd53a743097d6a9f655e444c7fdb89b78 100644 (file)
@@ -181,6 +181,8 @@ NOT_EXTENSION = util.symbol(
 
     """)
 
+_never_set = frozenset([NEVER_SET])
+
 _none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
 
 _SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
index c03e133deeb878e80420ad35c991d5cf6515efc3..5c6618686abcf3e8bc43af3fbe0c9ac82f654a6c 100644 (file)
@@ -585,6 +585,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
 
         if pending and orm_util._none_set.intersection(params.values()):
             return None
+        elif orm_util._never_set.intersection(params.values()):
+            return None
 
         q = q.filter(lazy_clause).params(params)
 
index b3f3bc5fa7db6a818bd00e09f523409352d42b42..b9098c77c4fdf2247c84c7a371a08bfe402000f2 100644 (file)
@@ -13,7 +13,7 @@ from . import attributes
 import re
 
 from .base import instance_str, state_str, state_class_str, attribute_str, \
-    state_attribute_str, object_mapper, object_state, _none_set
+    state_attribute_str, object_mapper, object_state, _none_set, _never_set
 from .base import class_mapper, _class_to_mapper
 from .base import InspectionAttr
 from .path_registry import PathRegistry
index e99e227259311a2fef70dd0fe0147aef8215e477..166ee90cfc3c9d05d7da1f9bb9efafedae74a4a4 100644 (file)
@@ -559,7 +559,7 @@ class GetterStateTest(_fixtures.FixtureTest):
 
     run_inserts = None
 
-    def _u_ad_fixture(self, populate_user):
+    def _u_ad_fixture(self, populate_user, dont_use_get=False):
         users, Address, addresses, User = (
             self.tables.users,
             self.classes.Address,
@@ -567,9 +567,17 @@ class GetterStateTest(_fixtures.FixtureTest):
             self.classes.User)
 
         mapper(User, users, properties={
-            'addresses': relationship(Address, backref='user')
+            'addresses': relationship(Address, back_populates='user')
+        })
+        mapper(Address, addresses, properties={
+            'user': relationship(
+                User,
+                primaryjoin=and_(
+                    users.c.id == addresses.c.user_id, users.c.id != 27)
+                if dont_use_get else None,
+                back_populates='addresses'
+            )
         })
-        mapper(Address, addresses)
 
         sess = create_session()
         a1 = Address(email_address='a1')
@@ -581,6 +589,19 @@ class GetterStateTest(_fixtures.FixtureTest):
             sess.expire_all()
         return User, Address, sess, a1
 
+    def test_no_use_get_params_missing(self):
+        User, Address, sess, a1 = self._u_ad_fixture(False, True)
+
+        def go():
+            eq_(a1.user, None)
+
+        # doesn't emit SQL
+        self.assert_sql_count(
+            testing.db,
+            go,
+            0
+        )
+
     def test_get_empty_passive_return_never_set(self):
         User, Address, sess, a1 = self._u_ad_fixture(False)
         eq_(