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.