La récupération d'erreurs causées par l'accès à la base de données, comme décrite dans Section 46.7.2, peut amener à une situation indésirable où certaines opérations réussissent avant qu'une d'entre elles échoue et, après récupération de cette erreur, les données sont laissées dans un état incohérent. PL/Python propose une solution à ce problème sous la forme de sous-transactions explicites.
Prenez en considération une fonction qui implémente un transfert entre deux comptes :
CREATE FUNCTION transfert_fonds() RETURNS void AS $$ try: plpy.execute("UPDATE comptes SET balance = balance - 100 WHERE nom = 'joe'") plpy.execute("UPDATE comptes SET balance = balance + 100 WHERE nom = 'mary'") except plpy.SPIError as e: result = "erreur lors du transfert de fond : %s" % e.args else: result = "fonds transféré correctement" plan = plpy.prepare("INSERT INTO operations (resultat) VALUES ($1)", ["text"]) plpy.execute(plan, [result]) $$ LANGUAGE plpythonu;
Si la deuxième instruction UPDATE
se termine avec
la levée d'une exception, cette fonction renverra l'erreur mais le
résultat du premier UPDATE
sera validé malgré
tout. Autrement dit, les fonds auront été débités du compte de Joe
mais ils n'auront pas été crédités sur le compte de Mary.
Pour éviter ce type de problèmes, vous pouvez intégrer vos appels à
plpy.execute
dans une sous-transaction explicite.
Le module plpy
fournit un objet d'aide à la gestion
des sous-transactions explicites qui sont créées avec la fonction
plpy.subtransaction()
. Les objets créés par cette
fonction implémentent l'
interface de gestion du contexte. Nous pouvons réécrire notre
fonction en utilisant les sous-transactions explicites :
CREATE FUNCTION transfert_fonds2() RETURNS void AS $$ try: with plpy.subtransaction(): plpy.execute("UPDATE comptes SET balance = balance - 100 WHERE nom = 'joe'") plpy.execute("UPDATE comptes SET balance = balance + 100 WHERE nom = 'mary'") except plpy.SPIError as e: result = "erreur lors du transfert de fond : %s" % e.args else: result = "fonds transféré correctement" plan = plpy.prepare("INSERT INTO operations (resultat) VALUES ($1)", ["text"]) plpy.execute(plan, [result]) $$ LANGUAGE plpythonu;
Notez que l'utilisation de try
/catch
est toujours
requis. Sinon, l'exception se propagerait en haut de la pile Python
et causerait l'annulation de la fonction entière avec une erreur
PostgreSQL, pour que la table
operations
ne contienne aucune des lignes insérées.
Le gestionnaire de contexte des sous-transactions ne récupère pas les
erreurs, il assure seulement que toutes les opérations de bases de
données exécutées dans son cadre seront validées ou annulées de
façon atomique. Une annulation d'un bloc de sous-transaction survient
à la sortie de tout type d'exception, pas seulement celles causées
par des erreurs venant de l'accès à la base de données. Une exception
standard Python levée dans un bloc de sous-transaction explicite
causerait aussi l'annulation de la sous-transaction.
Pour les gestionnaires de contexte, la syntaxe utilisant le mot clé
with
, est disponible par défaut avec Python 2.6. Pour
la compatibilité avec les anciennes versions de Python, vous pouvez
appeler les fonctions __enter__
et
__exit__
des gestionnaires de sous-transactions en
utilisant les alias enter
et exit
.
La fonction exemple de transfert des fonds pourrait être écrite
ainsi :
CREATE FUNCTION transfert_fonds_ancien() RETURNS void AS $$ try: subxact = plpy.subtransaction() subxact.enter() try: plpy.execute("UPDATE comptes SET balance = balance - 100 WHERE nom = 'joe'") plpy.execute("UPDATE comptes SET balance = balance + 100 WHERE nom = 'mary'") except: import sys subxact.exit(*sys.exc_info()) raise else: subxact.exit(None, None, None) except plpy.SPIError as e: result = "erreur lors du transfert de fond : %s" % e.args else: result = "fonds transféré correctement" plan = plpy.prepare("INSERT INTO operations (resultat) VALUES ($1)", ["text"]) plpy.execute(plan, [result]) $$ LANGUAGE plpythonu;