La récupération d'erreurs causées par l'accès à la base de données, comme décrite dans Section 42.7.2, « Récupérer les erreurs », 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.