sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Puede existir una confirmación de Postgres en un procedimiento que tenga un bloque de excepción?

La semántica del manejo de errores dictar que:

Esto se implementa mediante subtransacciones, que son básicamente lo mismo que savepoints . En otras palabras, cuando ejecuta el siguiente código PL/pgSQL:

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

...lo que realmente está pasando es algo como esto:

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

UN COMMIT dentro del bloque rompería esto por completo; sus cambios se harían permanentes, el punto de guardado se descartaría y el controlador de excepciones no tendría forma de retroceder. Como resultado, las confirmaciones no están permitidas en este contexto e intentar ejecutar un COMMIT dará como resultado un error "no se puede confirmar mientras una subtransacción está activa".

Es por eso que ve que su procedimiento salta al controlador de excepciones en lugar de ejecutar el raise notice 'B' :cuando llega al commit , arroja un error y el controlador lo detecta.

Sin embargo, esto es bastante sencillo de solucionar. BEGIN ... END los bloques se pueden anidar y solo los bloques con EXCEPTION Las cláusulas implican establecer puntos de guardado, por lo que puede envolver los comandos antes y después de la confirmación en sus propios controladores de excepción:

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

Desafortunadamente, genera mucha duplicación en los controladores de errores, pero no puedo pensar en una buena manera de evitarlo.