]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
restore an updated version of cascade backrefs
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Mar 2022 04:01:30 +0000 (00:01 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Mar 2022 04:01:30 +0000 (00:01 -0400)
these sections were removed totally from SQLAlchemy 2.0
as the ``cascade_backrefs`` option was removed.  However,
the current behavior is still one that should be documented,
so restore the two removed sections in a re-worded form
that presents the current behavior as well as some
explaination.

Change-Id: I43fc0bca926a90937d419a1d11c27d18d5d5b4b8

doc/build/orm/backref.rst
doc/build/orm/cascades.rst

index 2e1a1920cf994ccaefaf9520d273a36af4a742ac..3c1f7f8e646c26297425d2850c414d11efe55337 100644 (file)
@@ -187,6 +187,27 @@ it into a form that is interpreted by the receiving :func:`_orm.relationship` as
 arguments to be applied to the new relationship it creates.
 
 
+Cascade behavior for backrefs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's important to note that even though a bi-directional relationship
+may be manipulated from either direction, having the same end effect on the
+object structure produced, there is a significant difference in how the
+:ref:`save-update cascade <cascade_save_update>` behaves for two objects
+where one is attached and the other is unattached to a :class:`_orm.Session`,
+depending on the direction in which the relationships are manipulated.
+
+The ``save-update`` cascade will only take effect **uni-directionally**
+in the direction from a parent object that is already associated with a
+:class:`_orm.Session`, towards an object that is being associated with that
+parent directly via an attribute or collection on that parent.  It won't
+take effect if the parent object is instead assigned to an attribute or
+collection on the child, in which case the unattached parent object should be
+added to the :class:`_orm.Session` explicitly using :meth:`_orm.Session.add`.
+
+For a complete example of how this looks in practice, see the section
+:ref:`backref_cascade`.
+
 
 One Way Backrefs
 ~~~~~~~~~~~~~~~~
index 1f83fee50fde8090829a5f72b6e06bee73bb4ba6..c9e16cbacab8de26812818095b45abd9ea89adf9 100644 (file)
@@ -122,6 +122,106 @@ for granted; it simplifies code by allowing a single call to
 that :class:`.Session` at once.   While it can be disabled, there
 is usually not a need to do so.
 
+.. _backref_cascade:
+
+Behavior of save-update cascade with bi-directional relationships
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``save-update`` cascade takes place **uni-directionally** in the context of
+a bi-directional relationship, i.e. when using
+:ref:`backref / back_populates <relationships_backref>` to create two separate
+:func:`_orm.relationship` objects which refer to each other.
+
+An object that's not associated with a :class:`_orm.Session`, when assigned to
+an attribute or collection on a parent object that is associated with a
+:class:`_orm.Session`, will be automatically added to that same
+:class:`_orm.Session`. However, the same operation in reverse will not have
+this effect; an object that's not associated with a :class:`_orm.Session`, upon
+which a child object that is associated with a :class:`_orm.Session` is
+assigned, will not result in an automatic addition of that parent object to the
+:class:`_orm.Session`.  The overall subject of this behavior is known
+as "cascade backrefs", and represents a change in behavior that was standardized
+as of SQLAlchemy 2.0.
+
+To illustrate, given a mapping of ``Order`` objects which relate
+bi-directionally to a series of ``Item`` objects via relationships
+``Order.items`` and ``Item.order``::
+
+    mapper_registry.map_imperatively(Order, order_table, properties={
+        'items' : relationship(Item, back_populates='order')
+    })
+
+    mapper_registry.map_imperatively(Item, item_table, properties={
+        'order' : relationship(Order, back_populates='items')
+    })
+
+If an ``Order`` is already associated with a :class:`_orm.Session`, and
+an ``Item`` object is then created and appended to the ``Order.items``
+collection of that ``Order``, the ``Item`` will be automatically cascaded
+into that same :class:`_orm.Session`::
+
+    >>> o1 = Order()
+    >>> session.add(o1)
+    >>> o1 in session
+    True
+
+    >>> i1 = Item()
+    >>> o1.items.append(i1)
+    >>> o1 is i1.order
+    True
+    >>> i1 in session
+    True
+
+Above, the bidirectional nature of ``Order.items`` and ``Item.order`` means
+that appending to ``Order.items`` also assigns to ``Item.order``. At the same
+time, the ``save-update`` cascade allowed for the ``Item`` object to be added
+to the same :class:`_orm.Session` which the parent ``Order`` was already
+associated.
+
+However, if the operation above is performed in the **reverse** direction,
+where ``Item.order`` is assigned rather than appending directly to
+``Order.item``, the cascade operation into the :class:`_orm.Session` will
+**not** take place automatically, even though the object assignments
+``Order.items`` and ``Item.order`` will be in the same state as in the
+previous example::
+
+    >>> o1 = Order()
+    >>> session.add(o1)
+    >>> o1 in session
+    True
+
+    >>> i1 = Item()
+    >>> i1.order = o1
+    >>> i1 in order.items
+    True
+    >>> i1 in session
+    False
+
+In the above case, after the ``Item`` object is created and all the desired
+state is set upon it, it should then be added to the :class:`_orm.Session`
+explicitly::
+
+    >>> session.add(i1)
+
+In older versions of SQLAlchemy, the save-update cascade would occur
+bidirectionally in all cases. It was then made optional using an option known
+as ``cascade_backrefs``. Finally, in SQLAlchemy 1.4 the old behavior was
+deprecated and the ``cascade_backrefs`` option was removed in SQLAlchemy 2.0.
+The rationale is that users generally do not find it intuitive that assigning
+to an attribute on an object, illustrated above as the assignment of
+``i1.order = o1``, would alter the persistence state of that object ``i1`` such
+that it's now pending within a :class:`_orm.Session`, and there would
+frequently be subsequent issues where autoflush would prematurely flush the
+object and cause errors, in those cases where the given object was still being
+constructed and wasn't in a ready state to be flushed. The option to select between
+uni-directional and bi-directional behvaiors was also removed, as this option
+created two slightly different ways of working, adding to the overall learning
+curve of the ORM as well as to the documentation and user support burden.
+
+.. seealso::
+
+    :ref:`change_5150` - background on the change in behavior for
+    "cascade backrefs"
 
 .. _cascade_delete: