42.9. Subtransações explícitas em PL/Tcl #

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

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

CREATE FUNCTION transfer_funds() RETURNS void AS $$
    if [catch {
        spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
        spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
    } errormsg] {
        set result [format "error transferring funds: %s" $errormsg]
    } else {
        set result "funds transferred successfully"
    }
    spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
$$ LANGUAGE pltcl;

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. Isto acontece porque cada spi_exec é uma subtransação separada, e apenas uma dessas subtransações foi desfeita.

Para lidar com estes casos, pode-se agrupar várias operações de banco de dados em uma subtransação explícita, que será bem-sucedida ou desfeita como um todo. PL/Tcl fornece o comando subtransaction para gerenciar esta situação. Pode-se, então, reescrever a função como:

CREATE FUNCTION transfer_funds2() RETURNS void AS $$
    if [catch {
        subtransaction {
            spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
            spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
        }
    } errormsg] {
        set result [format "error transferring funds: %s" $errormsg]
    } else {
        set result "funds transferred successfully"
    }
    spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
$$ LANGUAGE pltcl;

Note que o uso de catch ainda é necessário para este propósito. Caso contrário, o erro se propagaria para o nível superior da função, impedindo a inserção desejada na tabela operations. O comando subtransaction não captura erros, apenas garante que todas as operações do banco de dados executadas dentro de seu escopo serão desfeitas juntas quando for lançado um erro.

A reversão de uma subtransação explícita ocorre em qualquer erro relatado pelo código Tcl presente, e não apenas em erros originados no acesso ao banco de dados. Assim, uma exceção Tcl regular levantada em um comando da subtransaction também fará com que a subtransação seja desfeita. Entretanto, saídas sem erro do código Tcl presente (por exemplo, devido a return), não causam uma reversão.