¿Cómo obtener el contexto de excepción para una excepción planteada manualmente en PL / pgSQL?

11

En Postgres, obtenemos el "seguimiento de la pila" de excepciones usando este código:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Esto funciona bien para excepciones "naturales", pero si planteamos una excepción usando

RAISE EXCEPTION 'This is an error!';

... entonces no hay rastro de pila. De acuerdo con una entrada de la lista de correo , esto podría ser intencional, aunque por mi vida no puedo entender por qué. Me dan ganas de encontrar otra forma de lanzar una excepción que no sea usar RAISE. ¿Me estoy perdiendo algo obvio? ¿Alguien tiene un truco para esto? ¿Hay una excepción que pueda hacer que Postgres lance que contenga una cadena de mi elección, de modo que obtenga no solo mi cadena en el mensaje de error, sino también el seguimiento completo de la pila?

Aquí hay un ejemplo completo:

CREATE OR REPLACE FUNCTION error_test() RETURNS json AS $$
DECLARE
    v_error_stack text;
BEGIN

    -- Comment this out to see how a "normal" exception will give you the stack trace
    RAISE EXCEPTION 'This exception will not get a stack trace';

    -- This will give a divide by zero error, complete with stack trace
    SELECT 1/0;

-- In case of any exception, wrap it in error object and send it back as json
EXCEPTION WHEN others THEN

    -- If the exception we're catching is one that Postgres threw,
    -- like a divide by zero error, then this will get the full
    -- stack trace of the place where the exception was thrown.
    -- However, since we are catching an exception we raised manually
    -- using RAISE EXCEPTION, there is no context/stack trace!
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

    RAISE WARNING 'The stack trace of the error is: "%"', v_error_stack;

    return to_json(v_error_stack);
END;
$$ LANGUAGE plpgsql;
Taytay
fuente
Puede ser una buena idea mostrar un ejemplo simple aquí.
Craig Ringer
Buen punto @CraigRinger. ¡Hecho!
Taytay
No es autónomo. ¿Qué es error_info? Parece un tipo personalizado.
Craig Ringer
Lo siento, pensé que solo querías un contexto general. He eliminado las cosas extrañas.
Taytay

Respuestas:

9

Este comportamiento parece ser por diseño.

En src/pl/plpgsql/src/pl_exec.cel contexto de error, la devolución de llamada verifica explícitamente si se está llamando en el contexto de una RAISEinstrucción PL / PgSQL y, de ser así, omite la emisión del contexto de error:

/*
 * error context callback to let us supply a call-stack traceback
 */
static void
plpgsql_exec_error_callback(void *arg)
{
        PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;

        /* if we are doing RAISE, don't report its location */
        if (estate->err_text == raise_skip_msg)
                return;

No puedo encontrar ninguna referencia específica de por qué ese es el caso.

Internamente en el servidor, la pila de contexto se genera procesando el error_context_stack, que es una devolución de llamada encadenada que agrega información a una lista cuando se llama.

Cuando PL / PgSQL ingresa una función, agrega un elemento a la pila de devolución de llamada de contexto de error. Cuando deja una función, elimina un elemento de esa pila.

Si las funciones de informe de errores del servidor PostgreSQL, como ereporto elogse llaman, llama a la devolución de llamada de contexto de error. Pero en PL / PgSQL si se da cuenta de que se está llamando desde RAISEsus devoluciones de llamada intencionalmente no hace nada.

Dado eso, no veo ninguna manera de lograr lo que quieres sin parchear PostgreSQL. Sugiero publicar el correo en pgsql-general preguntando por qué RAISEno proporciona el contexto de error ahora que PL / PgSQL tiene GET STACKED DIAGNOSTICSque usarlo.

(Por cierto, el contexto de excepción no es un seguimiento de la pila como tal. Se parece un poco a uno porque PL / PgSQL agrega cada llamada de función a la pila, pero también se usa para otros detalles en el servidor).

Craig Ringer
fuente
Muchas gracias Craig por la respuesta rápida y completa. Me parece extraño, y ciertamente contraria a mis expectativas. La utilidad de se RAISEve disminuida por ese control. Les escribiré a ellos.
Taytay
@Taytay Incluya un enlace a su pregunta aquí, pero asegúrese de que su correo esté completo y se pueda entender sin seguir el enlace; muchas personas ignoran las publicaciones de solo enlace o principalmente de enlace. Si tienes la oportunidad de mostrar un enlace a tu publicación en los comentarios aquí, a través de archives.postgresql.org , sería realmente increíble ayudar a otras personas más adelante.
Craig Ringer
Gracias Craig Buen consejo. Creé un hilo aquí: postgresql.org/message-id/… A partir de ahora, están buscando una buena solución al problema.
Taytay
6

Puede evitar esta restricción y hacer que plpgsql emita un contexto de error según lo desee llamando a otra función que genera (advertencia, aviso, ...) el error por usted.

Publiqué una solución para eso hace un par de años, en una de mis primeras publicaciones aquí en dba .

-- helper function to raise an exception with CONTEXT
CREATE OR REPLACE FUNCTION f_raise(_lvl text = 'EXCEPTION'
                                  ,_msg text = 'Default error msg.')
  RETURNS void AS
$func$
BEGIN
   CASE upper(_lvl)
      WHEN 'EXCEPTION' THEN RAISE EXCEPTION '%', _msg;
      WHEN 'WARNING'   THEN RAISE WARNING   '%', _msg;
      WHEN 'NOTICE'    THEN RAISE NOTICE    '%', _msg;
      WHEN 'DEBUG'     THEN RAISE DEBUG     '%', _msg;
      WHEN 'LOG'       THEN RAISE LOG       '%', _msg;
      WHEN 'INFO'      THEN RAISE INFO      '%', _msg;
      ELSE RAISE EXCEPTION 'f_raise(): unexpected raise-level: "%"', _lvl;
   END CASE;
END
$func$  LANGUAGE plpgsql STRICT;

Detalles:

Expandí su caso de prueba publicado para demostrar que funciona en Postgres 9.3:

SQL Fiddle.

Erwin Brandstetter
fuente
Muchas gracias Erwin! Curiosamente, en realidad experimenté con su solución antes de publicar, pero debo haber hecho algo mal y no obtuve el contexto que esperaba. Ahora que he visto el violín (gracias por mostrarme eso también), ¡le daré otra oportunidad!
Taytay
Bien hecho; no debería ser necesario, pero parece que funcionaría.
Craig Ringer
@CraigRinger: Dado que las excepciones deberían ser, bueno, la excepción , el impacto mínimo en el rendimiento tampoco debería importar. Tenemos todas las opciones de esta manera.
Erwin Brandstetter
Totalmente de acuerdo, me gustaría ver que la necesidad de la solución alternativa desaparezca en algún momento.
Craig Ringer
@CraigRinger: Cierto. Si eso no va a suceder pronto, podríamos sugerir esta solución en el manual ...
Erwin Brandstetter