transaction.rollback(_capture_exception=True)
def bulk_save_objects(
- self, objects, return_defaults=False, update_changed_only=True):
+ self, objects, return_defaults=False, update_changed_only=True,
+ preserve_order=True):
"""Perform a bulk save of the given list of objects.
The bulk save feature allows mapped objects to be used as the
When False, all attributes present are rendered into the SET clause
with the exception of primary key attributes.
+ :param preserve_order: when True, the order of inserts and updates
+ matches exactly the order in which the objects are given. When
+ False, common types of objects are grouped into inserts
+ and updates, to allow for more batching opportunities.
+
+ .. versionadded:: 1.3
+
.. seealso::
:ref:`bulk_operations`
:meth:`.Session.bulk_update_mappings`
"""
+ def key(state):
+ return (state.mapper, state.key is not None)
+
+ obj_states = tuple(attributes.instance_state(obj) for obj in objects)
+ if not preserve_order:
+ obj_states = sorted(obj_states, key=key)
+
for (mapper, isupdate), states in itertools.groupby(
- (attributes.instance_state(obj) for obj in objects),
- lambda state: (state.mapper, state.key is not None)
+ obj_states, key
):
self._bulk_save_mappings(
mapper, states, isupdate, True,
from sqlalchemy import testing
from sqlalchemy.testing import eq_
from sqlalchemy.testing.schema import Table, Column
+from sqlalchemy.testing import mock
from sqlalchemy.testing import fixtures
from sqlalchemy import Integer, String, ForeignKey, FetchedValue
from sqlalchemy.orm import mapper, Session
)
eq_(objects[0].__dict__['id'], 1)
+ def test_bulk_save_mappings_preserve_order(self):
+ User, = self.classes("User", )
+
+ s = Session()
+
+ # commit some object into db
+ user1 = User(name="i1")
+ user2 = User(name="i2")
+ s.add(user1)
+ s.add(user2)
+ s.commit()
+
+ # make some changes
+ user1.name = "u1"
+ user3 = User(name="i3")
+ s.add(user3)
+ user2.name = "u2"
+
+ objects = [user1, user3, user2]
+
+ from sqlalchemy import inspect
+
+ def _bulk_save_mappings(
+ mapper, mappings, isupdate, isstates,
+ return_defaults, update_changed_only, render_nulls):
+ mock_method(list(mappings), isupdate)
+
+ mock_method = mock.Mock()
+ with mock.patch.object(s, '_bulk_save_mappings', _bulk_save_mappings):
+ s.bulk_save_objects(objects)
+ eq_(
+ mock_method.mock_calls,
+ [
+ mock.call([inspect(user1)], True),
+ mock.call([inspect(user3)], False),
+ mock.call([inspect(user2)], True),
+ ]
+ )
+
+ mock_method = mock.Mock()
+ with mock.patch.object(s, '_bulk_save_mappings', _bulk_save_mappings):
+ s.bulk_save_objects(objects, preserve_order=False)
+ eq_(
+ mock_method.mock_calls,
+ [
+ mock.call([inspect(user3)], False),
+ mock.call([inspect(user1), inspect(user2)], True),
+ ]
+ )
+
def test_bulk_save_no_defaults(self):
User, = self.classes("User",)