]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Modified the behavior of :func:`.orm.load_only` such that primary key
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 12 Jun 2014 15:47:50 +0000 (11:47 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 12 Jun 2014 15:48:22 +0000 (11:48 -0400)
columns are always added to the list of columns to be "undeferred";
otherwise, the ORM can't load the row's identity.   Apparently,
one can defer the mapped primary keys and the ORM will fail, that
hasn't been changed.  But as load_only is essentially saying
"defer all but X", it's more critical that PK cols not be part of this
deferral. fixes #3080

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_deferred.py

index cbd5b42192046e871d1698b077600eeee882f785..0b4546a914c3a4973f56e5116dbfd07e89dc9e6c 100644 (file)
 .. changelog::
     :version: 0.9.5
 
+    .. change::
+        :tags: bug, orm
+        :tickets: 3080
+
+        Modified the behavior of :func:`.orm.load_only` such that primary key
+        columns are always added to the list of columns to be "undeferred";
+        otherwise, the ORM can't load the row's identity.   Apparently,
+        one can defer the mapped primary keys and the ORM will fail, that
+        hasn't been changed.  But as load_only is essentially saying
+        "defer all but X", it's more critical that PK cols not be part of this
+        deferral.
+
     .. change::
         :tags: feature, examples
         :pullreq: bitbucket: 21
index c8946a0c07e1b85ab17b002f66669e553cc88a32..0a120cf1e743f1842e3133491b95b005b23646f9 100644 (file)
@@ -208,10 +208,24 @@ class DeferredColumnLoader(LoaderStrategy):
 
     def setup_query(self, context, entity, path, loadopt, adapter,
                                 only_load_props=None, **kwargs):
+
         if (
-                loadopt and self.group and
+            (
+                loadopt and
+                'undefer_pks' in loadopt.local_opts and
+                set(self.columns).intersection(self.parent.primary_key)
+            )
+            or
+            (
+                loadopt and
+                self.group and
                 loadopt.local_opts.get('undefer_group', False) == self.group
-            ) or (only_load_props and self.key in only_load_props):
+            )
+            or
+            (
+                only_load_props and self.key in only_load_props
+            )
+        ):
             self.parent_property._get_strategy_by_cls(ColumnLoader).\
                             setup_query(context, entity,
                                         path, loadopt, adapter, **kwargs)
index 317fc0813c77ec2b23fab96b9741542f48251c20..eccb4f7f0fc014e49f9003bc5bc704fba73f8d7b 100644 (file)
@@ -582,7 +582,8 @@ def load_only(loadopt, *attrs):
                 {"deferred": False, "instrument": True}
             )
     cloned.set_column_strategy("*",
-                    {"deferred": True, "instrument": True})
+                    {"deferred": True, "instrument": True},
+                    {"undefer_pks": True})
     return cloned
 
 @load_only._add_unbound_fn
index d96d420e7e86ba5459d332ea0c98d8f445ccf2f2..1457852d82854c8faef7edb51cdbeb49abbf9aed 100644 (file)
@@ -3,7 +3,7 @@ from sqlalchemy import testing, util
 from sqlalchemy.orm import mapper, deferred, defer, undefer, Load, \
     load_only, undefer_group, create_session, synonym, relationship, Session,\
     joinedload, defaultload
-from sqlalchemy.testing import eq_, AssertsCompiledSQL
+from sqlalchemy.testing import eq_, AssertsCompiledSQL, assert_raises_message
 from test.orm import _fixtures
 from sqlalchemy.orm import strategies
 
@@ -37,6 +37,24 @@ class DeferredTest(AssertsCompiledSQL, _fixtures.FixtureTest):
              "FROM orders WHERE orders.id = :param_1",
              {'param_1':3})])
 
+    def test_defer_primary_key(self):
+        """what happens when we try to defer the primary key?"""
+
+        Order, orders = self.classes.Order, self.tables.orders
+
+
+        mapper(Order, orders, order_by=orders.c.id, properties={
+            'id': deferred(orders.c.id)})
+
+        # right now, it's not that graceful :)
+        q = create_session().query(Order)
+        assert_raises_message(
+            sa.exc.NoSuchColumnError,
+            "Could not locate",
+            q.first
+        )
+
+
     def test_unsaved(self):
         """Deferred loading does not kick in when just PK cols are set."""
 
@@ -431,7 +449,7 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             "LEFT OUTER JOIN orders AS orders_1 ON users.id = orders_1.user_id"
             )
 
-    def test_load_only(self):
+    def test_load_only_no_pk(self):
         orders, Order = self.tables.orders, self.classes.Order
 
         mapper(Order, orders)
@@ -439,9 +457,20 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
         sess = create_session()
         q = sess.query(Order).options(load_only("isopen", "description"))
         self.assert_compile(q,
-            "SELECT orders.description AS orders_description, "
+            "SELECT orders.id AS orders_id, "
+            "orders.description AS orders_description, "
             "orders.isopen AS orders_isopen FROM orders")
 
+    def test_load_only_no_pk_rt(self):
+        orders, Order = self.tables.orders, self.classes.Order
+
+        mapper(Order, orders)
+
+        sess = create_session()
+        q = sess.query(Order).order_by(Order.id).\
+                options(load_only("isopen", "description"))
+        eq_(q.first(), Order(id=1))
+
     def test_load_only_w_deferred(self):
         orders, Order = self.tables.orders, self.classes.Order
 
@@ -456,6 +485,7 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
                 )
         self.assert_compile(q,
             "SELECT orders.description AS orders_description, "
+            "orders.id AS orders_id, "
             "orders.user_id AS orders_user_id, "
             "orders.isopen AS orders_isopen FROM orders")
 
@@ -522,7 +552,8 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
                 )
 
         self.assert_compile(q,
-            "SELECT users.name AS users_name, orders.id AS orders_id, "
+            "SELECT users.id AS users_id, users.name AS users_name, "
+            "orders.id AS orders_id, "
             "addresses.id AS addresses_id, addresses.email_address "
             "AS addresses_email_address FROM users, orders, addresses"
             )
@@ -554,7 +585,7 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
         # hmmmm joinedload seems to be forcing users.id into here...
         self.assert_compile(
             q,
-            "SELECT users.name AS users_name, users.id AS users_id, "
+            "SELECT users.id AS users_id, users.name AS users_name, "
             "addresses_1.id AS addresses_1_id, "
             "addresses_1.email_address AS addresses_1_email_address, "
             "orders_1.id AS orders_1_id FROM users "