PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 13.1 » Programmation serveur » PL/Python -- Langage de procédures Python » Sous-transactions explicites

45.8. Sous-transactions explicites

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.

45.8.1. Gestionnaires de contexte de sous-transaction

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.

45.8.2. Anciennes versions de Python

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;