Recuperarse de errores causados por el acceso a la base de datos como se describe en Section 44.6.2 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/Python 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 $$
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;
Si la segunda sentencia UPDATE produce una
excepción, esta función informará del error, pero
el resultado de la primera UPDATE se
confirmará (commit) a pesar de todo. En otras palabras, los fondos se
retirarán de la cuenta de Joe, pero no se transferirán a la
cuenta de Mary.
Para evitar tales problemas, puedes envolver tus llamadas a
plpy.execute en una subtransacción explícita.
El módulo plpy proporciona un objeto auxiliar para
gestionar subtransacciones explícitas que se crea con la función
plpy.subtransaction(). Los objetos creados por
esta función implementan la
interfaz de gestor de contexto. Usando subtransacciones explícitas
podemos reescribir nuestra función 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;
Ten en cuenta que el uso de try/except sigue siendo
necesario. De lo contrario, la excepción se propagaría a la parte superior de la
pila de Python y causaría que toda la función aborte con un error de
PostgreSQL, por lo que en la tabla
operations no se insertaría ninguna fila.
El gestor de contexto de subtransacción no captura errores, solo asegura
que todas las operaciones de la base de datos ejecutadas dentro de su ámbito
se confirmen (commit) o se reviertan (rollback) de forma atómica.
La reversión del bloque de subtransacción ocurre ante cualquier tipo de
salida por excepción, no solo las causadas por errores originados en el
acceso a la base de datos. Una excepción común de Python lanzada dentro de un
bloque de subtransacción explícito también causaría que la subtransacción
se revierta.