quarta-feira, março 23, 2011

Transações de banco de dados postgres no django

O django, por padrão faz commit automaticamente as transações no banco de dados.
Django’s default behavior is to run with an open transaction which it commits automatically when any built-in, data-altering model function is called. For example, if you call model.save() or model.delete(), the change will be committed immediately.
Isso quer dizer que caso haja um erro durante a transação no banco de dados as transações subsequentes também não serão executadas. Este comportamento poder ser útil em alguns casos, mas não todos! Mas o django como sempre lhe dá liberdade para alterar o comportamento padrão, e geralmente de uma maneira muito simples. Este é o caso.

Vamos supor que queremos inserir 100 registros novos no banco de dados Postgres que foram lidos de um excel.
E no 10º registro ocorra em um campo string que exceda o tamanho do campo no banco de dados, como abaixo

* DatabaseError('value too long for type character varying(70)
* DatabaseError('current transaction is aborted, commands ignored until end of transaction block
...
* DatabaseError('current transaction is aborted, commands ignored until end of transaction block]

o primeiro erro é por causa de uma string maior do que a aceita pelo campo no banco de dados.
Os error subsequentes, ocorreram simplesmente porque houve um erro ! oops, confuso?
veja a primeira mensagem de error

  • mensagem 1 - value too long for type character varying(70)
  • mensagem 2 em diante - current transaction is aborted, commands ignored until end of transaction block

a partir da msg de erro 2, o que temos é um erro de transação pendente, porque o commit automático não ocorreu.
O código executado é algo parecido com isso abaixo

for row_index in range(20 ):
    try:
        model_instance.save()            # salvar o modelo
    except Exception, error_str:
                error_list.append(error_str)


No meu caso, eu quero continuar as inserções no banco, mesmo que haja erro em algumas delas.
A saida é fazer um Savepoint Rollback! THANKS DJANGO !
Savepoint rollback
If you are using PostgreSQL 8 or later, you can use savepoints to control the extent of a rollback. Before performing a database operation that could fail, you can set or update the savepoint; that way, if the operation fails, you can roll back the single offending operation, rather than the entire transaction.
O que é dito aqui, é que ao salvar um ponto de rollback, eu posso comitar as transações que ocorreram sem erro, e dar um "rollback" somente na transação que falhou.
O código ficaria assim

for row_index in range(20 ):
    try:
        sid = transaction.savepoint()  # salvar ponto da transação
        model_instance.save()            # salvar o modelo
        transaction.savepoint_commit(sid)  # aplicamos o commit da transação
    except  Exception, error_str:
        error_list.append(error_str)
        transaction.savepoint_rollback(sid) #rollback somente da transação que falhou

leia mais na docs do Django  em :   http://docs.djangoproject.com/en/1.3/topics/db/transactions/