ORM_Transactions
Gestion des transactions SQL au sein du framework ORM_CSHX2. Trois helpers globaux — ORM_TransactionBegin, ORM_TransactionCommit, ORM_TransactionRollBack — encapsulent un modèle « transaction owner » qui rend les appels imbriqués sûrs sans intervention de l'appelant.
📋 Description
Une transaction SQL délimite un ensemble d'opérations qui doivent réussir ou échouer en bloc. Soit toutes les modifications sont appliquées (COMMIT), soit aucune ne l'est (ROLLBACK). C'est la garantie d'atomicité du quatuor ACID.
ORM_CSHX2 expose trois procédures globales pour piloter une transaction :
| Procédure | Rôle |
|---|---|
ORM_TransactionBegin() | Ouvre une transaction sur la connexion active. |
ORM_TransactionCommit() | Valide la transaction (les modifications deviennent permanentes). |
ORM_TransactionRollBack() | Annule la transaction (les modifications sont perdues). |
Ces trois procédures s'appuient sur un singleton qui mémorise l'état de la transaction au niveau du processus. Un seul niveau de transaction est actif à un instant donné — les appels imbriqués sont gérés par le pattern transaction owner détaillé dans la section suivante.
MySQL · MariaDB — utilise SQLTransaction(sqlDébut / sqlFin / sqlAnnule).
PostgreSQL — utilise SQLTransaction pour le BEGIN, puis exécute COMMIT / ROLLBACK en SQL natif. Un SET LOCAL lock_timeout est appliqué automatiquement à l'ouverture (sauf configuration contraire) pour éviter les attentes infinies sur les verrous.
🎯 Modèle "transaction owner" & transactions en cascade
Le modèle transaction owner est la pierre angulaire du système. Il répond à un problème courant en programmation modulaire : quand une procédure A qui ouvre une transaction appelle une procédure B qui veut elle-même ouvrir une transaction, que se passe-t-il ?
Dans ORM_CSHX2, la règle est simple :
• Le premier appelant à ouvrir une transaction en devient le propriétaire (« owner »). Son nom est mémorisé en interne.
• Les appels imbriqués à ORM_TransactionBegin sont silencieusement ignorés — ils ne déclenchent pas de nouvelle transaction (les bases SQL ne supportent pas les transactions imbriquées de toute façon).
• Seul le propriétaire peut clôturer la transaction via ORM_TransactionCommit ou ORM_TransactionRollBack. Les appels par d'autres procédures à ces clôtures sont également silencieusement ignorés.
Conséquence pratique : chaque procédure peut être écrite comme si elle gérait sa propre transaction. Si elle est appelée seule, la transaction est bien ouverte et fermée par elle. Si elle est appelée à l'intérieur d'une transaction parente, ses appels à Begin/Commit/Rollback sont neutralisés et c'est l'appelant racine qui pilote.
Exemple de cascade
Imaginons trois procédures imbriquées :
L'ouverture est faite une seule fois en réalité (premier appel de A). La fermeture est faite une seule fois en réalité (dernier appel de A, qui est l'owner). Tous les appels intermédiaires sont neutralisés sans alerter le programme.
Quand ORM_TransactionCommit() ou ORM_TransactionRollBack() est appelé par une procédure qui n'est pas l'owner de la transaction active, l'appel est silencieusement ignoré :
• bProcessing reste à Vrai
• nErrorCode reste à 0
• Aucune erreur n'est émise
C'est volontaire et nécessaire pour que le pattern owner fonctionne en cascade. Mais ça peut piéger : si une procédure imbriquée croit avoir clos la transaction et l'appelant continue à modifier la base, ces modifications seront incluses dans le COMMIT (ou perdues dans le ROLLBACK) que fera l'owner racine plus tard.
Pour éviter toute surprise, une procédure qui appelle ORM_TransactionBegin() doit toujours appeler ORM_TransactionCommit() ou ORM_TransactionRollBack() elle-même, dans tous les chemins de retour. Si la transaction est déjà active, ces appels sont des no-op gratuits ; sinon, ils ferment la transaction proprement.
🔑 Méthodes
ORM_TransactionBegin
Ouvre une transaction sur la connexion active si aucune n'est déjà en cours. Si une transaction est déjà active, l'appel est silencieusement ignoré (no-op) — voir §02 Modèle owner.
Retour : triplet standard ORM (bProcessing, nErrorCode, sErrorMessage).
ORM_TransactionCommit
Valide la transaction en cours (COMMIT). Si l'appelant n'est pas l'owner, l'appel est silencieusement ignoré.
Retour : triplet standard ORM (bProcessing, nErrorCode, sErrorMessage).
ORM_TransactionRollBack
Annule la transaction en cours (ROLLBACK). Si l'appelant n'est pas l'owner, l'appel est silencieusement ignoré.
Contrairement aux deux autres helpers, ORM_TransactionRollBack() ne retourne aucune valeur. L'appel correct est :
ORM_TransactionRollBack()
Et non :
(bProcessing, nErrorCode, sErrorMessage) = ORM_TransactionRollBack() // ❌
Cette signature reflète la réalité opérationnelle : un ROLLBACK arrive le plus souvent en aval d'une erreur, et il n'y a généralement rien d'utile à faire si lui-même échoue.
⚙️ Comportements observables
PostgreSQL — verrou avec timeout automatique
Sur PostgreSQL, ORM_TransactionBegin() applique automatiquement un SET LOCAL lock_timeout au démarrage de la transaction. Cela évite qu'une opération bloquée sur un verrou attende indéfiniment : passé le délai, l'opération échoue avec une erreur claire plutôt que de figer le poste.
Le délai par défaut est défini par la constante interne du framework. Il peut être désactivé ponctuellement via la propriété m_bNO_WAIT du singleton de transaction (cas spécifiques où l'attente est souhaitée).
Audit — traçabilité de la procédure démarreuse
Lors de l'ouverture d'une transaction, ORM_TransactionBegin() auto-détecte le nom de la procédure appelante et le mémorise au niveau de la session. Ce nom est ensuite écrit dans les colonnes framework SQL_PROCEDURE_INSERT et SQL_PROCEDURE_UPDATE par les méthodes mth_Enregistrer et apparentées, fournissant une traçabilité complète : quelle procédure métier a démarré l'unité de travail qui a inséré ou modifié cet enregistrement ?
Mode verbose — traces des transactions
Lorsque la session ORM_CSHX2 est en mode verbose, chaque ouverture, validation ou annulation génère une trace lisible :
Ces traces facilitent le débogage des cascades de transactions complexes en montrant clairement qui ouvre, qui valide et qui annule.
⚠️ Gestion des erreurs
Codes d'erreur retournés par les helpers ORM_TransactionBegin et ORM_TransactionCommit. ORM_TransactionRollBack étant void, ses erreurs ne sont pas remontées à l'appelant.
| Code | Constante | Condition |
|---|---|---|
-30099 | ERR_TXN_LOCK_TIMEOUT_SQL | PostgreSQL uniquement — échec du SET LOCAL lock_timeout appliqué après le BEGIN. La transaction est annulée, l'état revient à « pas de transaction active ». |
-30098 | ERR_TXN_INIT_SQL | Le BEGIN SQL a échoué (problème de connexion, droits insuffisants, etc.). |
-30097 | ERR_TXN_COMMIT_SQL | PostgreSQL uniquement — la requête COMMIT a échoué. |
-30096 | ERR_TXN_COMMIT_ECHEC | MySQL/MariaDB — l'appel SQLTransaction(sqlFin) a échoué. |
-30093 | ERR_TXN_INACTIVE | ORM_TransactionCommit() appelé alors qu'aucune transaction n'est active. Indique généralement un déséquilibre Begin/Commit côté appelant. |
Quand un appel imbriqué à Begin, Commit ou Rollback est ignoré au titre du pattern owner, aucune erreur n'est remontée : bProcessing = Vrai, nErrorCode = 0. Le code ERR_TXN_INACTIVE ne couvre que le cas où aucune transaction n'est active du tout au moment du Commit — pas le cas du caller ≠ owner.
💡 Exemples
Mode 1 — transaction simple
Mode 2 — transactions en cascade (procédures imbriquées)
Chaque procédure est écrite comme si elle gérait sa propre transaction. Lors de l'imbrication, seul le démarreur racine pilote effectivement.
La procédure EnregistrerLignes peut être appelée depuis n'importe où — un bouton, un script de migration, une autre procédure orchestratrice. Son code est identique dans tous les cas. C'est l'environnement d'appel qui détermine si Begin/Commit/Rollback ont un effet réel ou non, sans aucun branchement conditionnel à écrire.
Mode 3 — méthodes qui gèrent leur propre transaction
Certaines méthodes du framework — comme mth_EffacerSelonID — ouvrent et ferment leur propre transaction si aucune n'est active à l'appel. Si l'appelant veut grouper plusieurs opérations dans la même unité de travail, il doit ouvrir lui-même la transaction parente :
Mode 4 — verrou pessimiste sur SELECT puis modification atomique
🔗 Méthodes qui gèrent leur propre transaction
Les méthodes suivantes du framework ouvrent une transaction locale si aucune n'est active à l'appel, puis la valident ou l'annulent automatiquement. Si une transaction parente est déjà active, elles ne touchent pas à la transaction et c'est l'appelant qui pilote.
| Méthode | Effet |
|---|---|
| mth_EffacerSelonID | Suppression d'un enregistrement et de ses dépendances. Transaction locale ouverte si aucune transaction parente n'est active. |
Pour les opérations isolées (un seul appel), aucune action côté appelant n'est nécessaire. Pour grouper plusieurs opérations dans une seule unité de travail, ouvrir explicitement une transaction parente avec ORM_TransactionBegin() avant les appels.