La récupération d'erreurs causées par l'accès à la base de données, comme décrite dans Section 45.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, 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, 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.
Si vous utilisez une version plus ancienne de Python, il est toujours
possible d'utiliser les sous-transactions explicites, bien que cela
ne sera pas transparent. 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, 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;
Bien que les gestionnaires de contexte sont implémentés dans Python
2.5, pour utiliser la syntaxe with
dans cette
version vous aurez besoin d'utiliser une requête
future. Dû aux détails d'implémentation, vous ne pouvez pas
utiliser les requêtes futures dans des fonctions PL/Python.