Skip to content

Commit

Permalink
Used commit_on_success_unless_managed to make ORM operations atomic.
Browse files Browse the repository at this point in the history
  • Loading branch information
aaugustin committed Mar 11, 2013
1 parent 86fd920 commit 4dbd1b2
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 77 deletions.
82 changes: 32 additions & 50 deletions django/db/models/deletion.py
Expand Up @@ -50,24 +50,6 @@ def DO_NOTHING(collector, field, sub_objs, using):
pass pass




def force_managed(func):
@wraps(func)
def decorated(self, *args, **kwargs):
if transaction.get_autocommit(using=self.using):
transaction.enter_transaction_management(using=self.using, forced=True)
forced_managed = True
else:
forced_managed = False
try:
func(self, *args, **kwargs)
if forced_managed:
transaction.commit(using=self.using)
finally:
if forced_managed:
transaction.leave_transaction_management(using=self.using)
return decorated


class Collector(object): class Collector(object):
def __init__(self, using): def __init__(self, using):
self.using = using self.using = using
Expand Down Expand Up @@ -260,7 +242,6 @@ def sort(self):
self.data = SortedDict([(model, self.data[model]) self.data = SortedDict([(model, self.data[model])
for model in sorted_models]) for model in sorted_models])


@force_managed
def delete(self): def delete(self):
# sort instance collections # sort instance collections
for model, instances in self.data.items(): for model, instances in self.data.items():
Expand All @@ -271,40 +252,41 @@ def delete(self):
# end of a transaction. # end of a transaction.
self.sort() self.sort()


# send pre_delete signals with transaction.commit_on_success_unless_managed(using=self.using):
for model, obj in self.instances_with_model(): # send pre_delete signals
if not model._meta.auto_created: for model, obj in self.instances_with_model():
signals.pre_delete.send( if not model._meta.auto_created:
sender=model, instance=obj, using=self.using signals.pre_delete.send(
)

# fast deletes
for qs in self.fast_deletes:
qs._raw_delete(using=self.using)

# update fields
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
query = sql.UpdateQuery(model)
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
query.update_batch([obj.pk for obj in instances],
{field.name: value}, self.using)

# reverse instance collections
for instances in six.itervalues(self.data):
instances.reverse()

# delete instances
for model, instances in six.iteritems(self.data):
query = sql.DeleteQuery(model)
pk_list = [obj.pk for obj in instances]
query.delete_batch(pk_list, self.using)

if not model._meta.auto_created:
for obj in instances:
signals.post_delete.send(
sender=model, instance=obj, using=self.using sender=model, instance=obj, using=self.using
) )


# fast deletes
for qs in self.fast_deletes:
qs._raw_delete(using=self.using)

# update fields
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
query = sql.UpdateQuery(model)
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
query.update_batch([obj.pk for obj in instances],
{field.name: value}, self.using)

# reverse instance collections
for instances in six.itervalues(self.data):
instances.reverse()

# delete instances
for model, instances in six.iteritems(self.data):
query = sql.DeleteQuery(model)
pk_list = [obj.pk for obj in instances]
query.delete_batch(pk_list, self.using)

if not model._meta.auto_created:
for obj in instances:
signals.post_delete.send(
sender=model, instance=obj, using=self.using
)

# update collected instances # update collected instances
for model, instances_for_fieldvalues in six.iteritems(self.field_updates): for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
for (field, value), instances in six.iteritems(instances_for_fieldvalues): for (field, value), instances in six.iteritems(instances_for_fieldvalues):
Expand Down
24 changes: 2 additions & 22 deletions django/db/models/query.py
Expand Up @@ -442,12 +442,7 @@ def bulk_create(self, objs, batch_size=None):
self._for_write = True self._for_write = True
connection = connections[self.db] connection = connections[self.db]
fields = self.model._meta.local_fields fields = self.model._meta.local_fields
if transaction.get_autocommit(using=self.db): with transaction.commit_on_success_unless_managed(using=self.db):
transaction.enter_transaction_management(using=self.db, forced=True)
forced_managed = True
else:
forced_managed = False
try:
if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
and self.model._meta.has_auto_field): and self.model._meta.has_auto_field):
self._batched_insert(objs, fields, batch_size) self._batched_insert(objs, fields, batch_size)
Expand All @@ -458,11 +453,6 @@ def bulk_create(self, objs, batch_size=None):
if objs_without_pk: if objs_without_pk:
fields= [f for f in fields if not isinstance(f, AutoField)] fields= [f for f in fields if not isinstance(f, AutoField)]
self._batched_insert(objs_without_pk, fields, batch_size) self._batched_insert(objs_without_pk, fields, batch_size)
if forced_managed:
transaction.commit(using=self.db)
finally:
if forced_managed:
transaction.leave_transaction_management(using=self.db)


return objs return objs


Expand Down Expand Up @@ -579,18 +569,8 @@ def update(self, **kwargs):
self._for_write = True self._for_write = True
query = self.query.clone(sql.UpdateQuery) query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs) query.add_update_values(kwargs)
if transaction.get_autocommit(using=self.db): with transaction.commit_on_success_unless_managed(using=self.db):
transaction.enter_transaction_management(using=self.db, forced=True)
forced_managed = True
else:
forced_managed = False
try:
rows = query.get_compiler(self.db).execute_sql(None) rows = query.get_compiler(self.db).execute_sql(None)
if forced_managed:
transaction.commit(using=self.db)
finally:
if forced_managed:
transaction.leave_transaction_management(using=self.db)
self._result_cache = None self._result_cache = None
return rows return rows
update.alters_data = True update.alters_data = True
Expand Down
9 changes: 4 additions & 5 deletions docs/topics/db/transactions.txt
Expand Up @@ -16,11 +16,10 @@ Django's default behavior is to run in autocommit mode. Each query is
immediately committed to the database. :ref:`See below for details immediately committed to the database. :ref:`See below for details
<autocommit-details>`. <autocommit-details>`.


.. Django uses transactions or savepoints automatically to guarantee the
Django uses transactions or savepoints automatically to guarantee the integrity of ORM operations that require multiple queries, especially
integrity of ORM operations that require multiple queries, especially :ref:`delete() <topics-db-queries-delete>` and :ref:`update()
:ref:`delete() <topics-db-queries-delete>` and :ref:`update() <topics-db-queries-update>` queries.
<topics-db-queries-update>` queries.


.. versionchanged:: 1.6 .. versionchanged:: 1.6
Previous version of Django featured :ref:`a more complicated default Previous version of Django featured :ref:`a more complicated default
Expand Down

0 comments on commit 4dbd1b2

Please sign in to comment.