44.7. Subtransações explícitas #

44.7.1. Gerenciadores de contexto de subtransação

A recuperação de erros causados ​​pelo acesso ao banco de dados conforme descrito em Captura de erros pode levar a uma situação indesejável, em que algumas operações são bem-sucedidas antes de uma delas falhar e, após a recuperação desse erro, os dados são deixados em um estado inconsistente. O PL/Python oferece uma solução para este problema na forma de subtransações explícitas.

44.7.1. Gerenciadores de contexto de subtransação #

Considere a seguinte função que implementa a transferência entre duas contas:

CREATE FUNCTION transfer_funds()
  RETURNS void
AS $$
try:
    plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
    plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
    result = "error transferring funds: %s" % e.args
else:
    result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

Se a segunda instrução UPDATE resultar no lançamento de uma exceção, esta função irá relatar o erro, mas o resultado da primeira instrução UPDATE será efetivado. Em outras palavras, a quantia será debitada da conta do Joe, mas não será creditada na conta da Mary.

Para evitar este tipo de problema, pode-se envolver as chamadas a plpy.execute em uma subtransação explícita. O módulo plpy fornece um objeto auxiliar para gerenciar subtransações explícitas, que são criadas usando a função plpy.subtransaction(). Os objetos criados por esta função implementam a interface do gerenciador de contexto. Usando subtransações explícitas, a função pode ser reescrita como:

CREATE FUNCTION transfer_funds2()
  RETURNS void
AS $$
try:
    with plpy.subtransaction():
        plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
        plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
    result = "error transferring funds: %s" % e.args
else:
    result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

Note que o uso de try/except ainda é necessário. Caso contrário, a exceção se propagaria para o topo da pilha do Python, e faria com que toda a função fosse interrompida com um erro do PostgreSQL, de modo que a tabela operacoes não teria nenhuma linha inserida nela. O gerenciador de contexto de subtransação não intercepta erros, apenas garante que todas as operações de banco de dados executadas dentro de seu escopo sejam efetivadas ou desfeitas atomicamente. Um desfazimento (rollback) do bloco de subtransação ocorre para qualquer tipo de saída de exceção, e não apenas naquelas causadas por erros originados pelo acesso ao banco de dados. Uma exceção regular do Python levantada dentro de um bloco de subtransação explícito, também faz com que a subtransação seja desfeita.