]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add caveat re: with_expression and already-loaded object
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 2 Sep 2020 18:47:03 +0000 (14:47 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 2 Sep 2020 18:57:37 +0000 (14:57 -0400)
Adds a test for the populate_existing() behavior as well.

Fixes: #5553
Change-Id: Ib0db6227c3fec7d0065f2a7caa36b3fd94ef14fd
(cherry picked from commit b050169600019ef249a8b315e7435c752623c900)

doc/build/orm/mapped_sql_expr.rst
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_deferred.py

index f7ee2020ec10b907ff992871e41172ae2930509a..56c7d8f45ca10193801e66523594551f998571eb 100644 (file)
@@ -289,7 +289,20 @@ The :func:`.query_expression` mapping has these caveats:
 
 * On an object where :func:`.query_expression` were not used to populate
   the attribute, the attribute on an object instance will have the value
-  ``None``.
+  ``None``, unless the :paramref:`_orm.query_expression.default_expr`
+  parameter is set to an alternate SQL expression.
+
+* The query_expression value **does not populate on an object that is
+  already loaded**.  That is, this will **not work**::
+
+    obj = session.query(A).first()
+
+    obj = session.query(A).options(with_expression(A.expr, some_expr)).first()
+
+  To ensure the attribute is re-loaded, use :meth:`_orm.Query.populate_existing`::
+
+    obj = session.query(A).populate_existing().options(
+        with_expression(A.expr, some_expr)).first()
 
 * The query_expression value **does not refresh when the object is
   expired**.  Once the object is expired, either via :meth:`.Session.expire`
index cba0e7e80f586f31e36296712d5f45878e84bf34..d5404b550f14b63b785756eb75ace8bd91b0fadf 100644 (file)
@@ -1704,6 +1704,12 @@ def with_expression(loadopt, key, expression):
 
     :param expr: SQL expression to be applied to the attribute.
 
+    .. note:: the target attribute is populated only if the target object
+       is **not currently loaded** in the current :class:`_orm.Session`
+       unless the :meth:`_orm.Query.populate_existing` method is used.
+       Please refer to :ref:`mapper_querytime_expression` for complete
+       usage details.
+
     .. seealso::
 
         :ref:`mapper_querytime_expression`
index b4f848a9685954072dc4150bd58d2614cb51e363..28d771234ef9e30664ea2183cb7f580e474d0a26 100644 (file)
@@ -1742,6 +1742,38 @@ class WithExpressionTest(fixtures.DeclarativeMappedTest):
             q.all(), [A(bs=[B(b_expr=25)]), A(bs=[B(b_expr=38), B(b_expr=10)])]
         )
 
+    def test_no_refresh_unless_populate_existing(self):
+        A = self.classes.A
+
+        s = Session()
+        a1 = s.query(A).first()
+
+        def go():
+            eq_(a1.my_expr, None)
+
+        self.assert_sql_count(testing.db, go, 0)
+
+        a1 = s.query(A).options(with_expression(A.my_expr, A.x + A.y)).first()
+
+        eq_(a1.my_expr, None)
+
+        a1 = (
+            s.query(A)
+            .populate_existing()
+            .options(with_expression(A.my_expr, A.x + A.y))
+            .first()
+        )
+
+        eq_(a1.my_expr, 3)
+
+        a1 = s.query(A).first()
+
+        eq_(a1.my_expr, 3)
+
+        s.expire(a1)
+
+        eq_(a1.my_expr, None)
+
     def test_no_sql_not_set_up(self):
         A = self.classes.A