views:

176

answers:

2

Let's say I have model app1.models.ModelOne defined with save decorated with @commit_on_success. All the exceptions caught in ModelOne.save() are re-raised. Works fine on model_one_instance.save().

However, in app2 I need to make series of insertions to ModelOne and rollback all of them if any of them fails. How do I accomplish this?

Decorating app2.jobs.do_the_inserts with @commit_on_success doesn't work as expected.

A: 

Depending on your DBMS server nested transactions may not be supported at all. Properly implementing nested transactions in a DBMS is actually quite difficult because you end up having to share locks between transactions.

However, what you're describing doesn't sound like nested transactions. I don't know whether django supports XA transactions but what you describe could be achieved with a TP monitor architecture and an XA aware DBMS (which is most of them these days).

If your platform doesn't have support for XA transactions you would have to structure the transactions so a record of how to roll them back is stored somewhere. Something along the lines of the 'Unit of Work' pattern described in Fowler's Patterns of Enterprise Application Architecture might give a good start for the architecture of such a subsystem.

ConcernedOfTunbridgeWells
DBMS is pg 8.3; I am wondering if there is a way to do this via ORM layer.
ohnoes
PG 8.3 supports XA transactions but I don't know if Django supports them. The article at this link goes into transaction management on django http://blogs.gnome.org/jamesh/2008/09/01/transaction-management-in-django/
ConcernedOfTunbridgeWells
+1  A: 

Nested transactions are database-specific, so you're going to lose portability. If you need that, I'd consider doing is changing the simple @commit_on_save for something more flexible:

def save(commit=True):
  if commit:
    db.start_transaction()
    try:
        self.real_save()
        db.commit_transaction()
    except backend.DatabaseError, e:
        db.rollback_transaction()
        raise e
  else:
    self.real_save()

Otherwise, you can run arbitrary SQL commands so you can call db.connection.cursor().execute() with whatever your backend database uses, probably with a check to not do anything for other backends so you can still use sqlite for local testing.

Depending on your app structure, it might also be possible to use savepoints. I've written a few utilities which do something like this:

  • Start transaction
  • Perform mandatory commands
  • Start optional statements:
    • start savepoint
    • execute
    • savepoint commit or rollback
  • More mandatory SQL
  • commit
Chris Adams