PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Può esistere un commit Postgres in una procedura che ha un blocco di eccezioni?

La semantica della gestione degli errori dettalo:

Questo viene implementato utilizzando sottotransazioni, che sono sostanzialmente le stesse di savepoints . In altre parole, quando esegui il seguente codice PL/pgSQL:

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

...quello che sta effettivamente accadendo è qualcosa del genere:

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

Un COMMIT all'interno del blocco lo spezzerebbe completamente; le tue modifiche verrebbero rese permanenti, il punto di salvataggio verrebbe scartato e il gestore delle eccezioni verrebbe lasciato senza modo per tornare indietro. Di conseguenza, i commit non sono consentiti in questo contesto e il tentativo di eseguire un COMMIT risulterà in un errore "impossibile eseguire il commit mentre una sottotransazione è attiva".

Ecco perché vedi la tua procedura passare al gestore delle eccezioni invece di eseguire raise notice 'B' :quando raggiunge il commit , genera un errore e il gestore lo rileva.

Questo è abbastanza semplice da aggirare, però. BEGIN ... END i blocchi possono essere nidificati e solo i blocchi con EXCEPTION le clausole implicano l'impostazione di punti di salvataggio, quindi puoi semplicemente racchiudere i comandi prima e dopo il commit nei loro gestori di eccezioni:

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;
$$;

Sfortunatamente, porta a molte duplicazioni nei gestori degli errori, ma non riesco a pensare a un modo carino per evitarlo.