PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Un commit Postgres peut-il exister dans une procédure comportant un bloc d'exception ?

La sémantique de la gestion des erreurs dicter que :

Ceci est implémenté à l'aide de sous-transactions, qui sont fondamentalement les mêmes que les savepoints . En d'autres termes, lorsque vous exécutez le code PL/pgSQL suivant :

BEGIN
  PERFORM foo();
EXCEPTION WHEN others THEN
  PERFORM handle_error();
END

...ce qui se passe réellement ressemble à ceci :

BEGIN
  SAVEPOINT a;
  PERFORM foo();
  RELEASE SAVEPOINT a;
EXCEPTION WHEN others THEN
  ROLLBACK TO SAVEPOINT a;
  PERFORM handle_error();
END

Un COMMIT dans le bloc briserait complètement cela; vos modifications seraient rendues permanentes, le point de sauvegarde serait supprimé et le gestionnaire d'exceptions n'aurait plus aucun moyen de revenir en arrière. Par conséquent, les commits ne sont pas autorisés dans ce contexte, et essayer d'exécuter un COMMIT entraînera une erreur "Impossible de valider tant qu'une sous-transaction est active".

C'est pourquoi vous voyez votre procédure passer au gestionnaire d'exceptions au lieu d'exécuter le raise notice 'B' :lorsqu'il atteint le commit , il génère une erreur et le gestionnaire l'attrape.

C'est assez simple à contourner, cependant. BEGIN ... END les blocs peuvent être imbriqués, et seuls les blocs avec EXCEPTION les clauses impliquent la définition de points de sauvegarde, vous pouvez donc simplement encapsuler les commandes avant et après la validation dans leurs propres gestionnaires d'exception :

create or replace procedure x_transaction_try() language plpgsql
as $$
declare
  my_ex_state text;
  my_ex_message text;
  my_ex_detail text;
  my_ex_hint text;
  my_ex_ctx text;
begin
  begin
    raise notice 'A';
  exception when others then
    raise notice 'C';
    GET STACKED DIAGNOSTICS
      my_ex_state   = RETURNED_SQLSTATE,
      my_ex_message = MESSAGE_TEXT,
      my_ex_detail  = PG_EXCEPTION_DETAIL,
      my_ex_hint    = PG_EXCEPTION_HINT,
      my_ex_ctx     = PG_EXCEPTION_CONTEXT
    ;
    raise notice '% % % % %', my_ex_state, my_ex_message, my_ex_detail, my_ex_hint, my_ex_ctx;
  end;

  commit;

  begin
    raise notice 'B';
  exception when others then
    raise notice 'C';
    GET STACKED DIAGNOSTICS
      my_ex_state   = RETURNED_SQLSTATE,
      my_ex_message = MESSAGE_TEXT,
      my_ex_detail  = PG_EXCEPTION_DETAIL,
      my_ex_hint    = PG_EXCEPTION_HINT,
      my_ex_ctx     = PG_EXCEPTION_CONTEXT
    ;
    raise notice '% % % % %', my_ex_state, my_ex_message, my_ex_detail, my_ex_hint, my_ex_ctx;
  end;      
end;
$$;

Malheureusement, cela entraîne de nombreuses duplications dans les gestionnaires d'erreurs, mais je ne vois pas de moyen agréable de l'éviter.