PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Um commit do Postgres pode existir em um procedimento que tenha um bloco de exceção?


A semântica do tratamento de erros ditar que:

Isso é implementado usando subtransações, que são basicamente as mesmas que savepoints . Em outras palavras, quando você executa o seguinte código PL/pgSQL:
BEGIN
  PERFORM foo();
EXCEPTION WHEN others THEN
  PERFORM handle_error();
END

... o que está realmente acontecendo é algo assim:
BEGIN
  SAVEPOINT a;
  PERFORM foo();
  RELEASE SAVEPOINT a;
EXCEPTION WHEN others THEN
  ROLLBACK TO SAVEPOINT a;
  PERFORM handle_error();
END

Um COMMIT dentro do bloco quebraria isso completamente; suas alterações se tornariam permanentes, o ponto de salvamento seria descartado e o manipulador de exceção não teria como reverter. Como resultado, commits não são permitidos neste contexto, e tentar executar um COMMIT resultará em um erro "não é possível confirmar enquanto uma subtransação estiver ativa".

É por isso que você vê seu procedimento pular para o manipulador de exceção em vez de executar o raise notice 'B' :quando atinge o commit , ele lança um erro e o manipulador o captura.

Isso é bastante simples de contornar, no entanto. BEGIN ... END blocos podem ser aninhados e apenas blocos com EXCEPTION cláusulas envolvem a configuração de pontos de salvamento, então você pode simplesmente envolver os comandos antes e depois do commit em seus próprios manipuladores de exceção:
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;
$$;

Infelizmente, isso leva a muita duplicação nos manipuladores de erros, mas não consigo pensar em uma boa maneira de evitá-lo.