42.9. Subtransacciones explícitas en PL/Tcl #

Recuperarse de errores causados por el acceso a la base de datos como se describe en Section 42.8 puede llevar a una situación no deseada donde algunas operaciones tienen éxito antes de que una de ellas falle, y después de recuperarse de ese error los datos quedan en un estado inconsistente. PL/Tcl ofrece una solución a este problema en forma de subtransacciones explícitas.

Considera una función que implementa una transferencia entre dos cuentas:

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;

Si la segunda sentencia UPDATE resulta en una excepción, esta función registrará el fallo, pero el resultado del primer UPDATE, sin embargo, se confirmará. En otras palabras, los fondos se retirarán de la cuenta de Joe, pero no se transferirán a la cuenta de Mary. Esto sucede porque cada spi_exec es una subtransacción separada, y solo una de esas subtransacciones se revirtió.

Para manejar tales casos, puedes envolver múltiples operaciones de base de datos en una subtransacción explícita, la cual tendrá éxito o se revertirá como un todo. PL/Tcl proporciona un comando subtransaction para gestionar esto. Podemos reescribir nuestra función 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;

Ten en cuenta que el uso de catch sigue siendo necesario para este propósito. De lo contrario, el error se propagaría al nivel superior de la función, impidiendo la inserción deseada en la tabla operations. El comando subtransaction no atrapa errores, solo asegura que todas las operaciones de base de datos ejecutadas dentro de su ámbito se reviertan juntas cuando se reporte un error.

La reversión de una subtransacción explícita ocurre ante cualquier error reportado por el código Tcl contenido, no solo ante errores originados en el acceso a la base de datos. Por lo tanto, una excepción regular de Tcl generada dentro de un comando subtransaction también causará que la subtransacción se revierta. Sin embargo, las salidas que no sean por error del código Tcl contenido (por ejemplo, debido a return) no provocan una reversión.