Como obter o contexto de exceção para uma exceção gerada manualmente no PL / pgSQL?

11

No Postgres, obtemos o "rastreamento de pilha" das exceções usando este código:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Isso funciona bem para exceções "naturais", mas se criarmos uma exceção usando

RAISE EXCEPTION 'This is an error!';

... então não há rastreamento de pilha. De acordo com uma entrada na lista de discussão , isso pode ser intencional, embora eu não consiga descobrir o porquê. Isso me faz querer descobrir outra maneira de lançar uma exceção diferente de usar RAISE. Só estou perdendo algo óbvio? Alguém tem um truque para isso? Existe uma exceção que eu possa lançar o Postgres que contenha uma sequência de minha escolha, para que eu receba não apenas minha sequência na mensagem de erro, mas também o rastreamento completo da pilha?

Aqui está um exemplo 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
fonte
Pode ser uma boa ideia mostrar um exemplo simples aqui.
Craig campainha
Bom ponto @CraigRinger. Feito!
Taytay
Não é independente. O que é error_info? Parece um tipo personalizado.
Craig campainha
Desculpe - pensei que você só queria um contexto geral. Eu removi as coisas estranhas.
Taytay

Respostas:

9

Esse comportamento parece ser por design.

No src/pl/plpgsql/src/pl_exec.ccontexto de erro, o retorno de chamada verifica explicitamente se está sendo chamado no contexto de uma RAISEinstrução PL / PgSQL e, nesse caso, ignora a emissão do contexto de erro:

/*
 * 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;

Não consigo encontrar nenhuma referência específica sobre por que esse é o caso.

Internamente no servidor, a pilha de contexto é gerada processando o error_context_stack, que é um retorno de chamada em cadeia que acrescenta informações a uma lista quando chamada.

Quando o PL / PgSQL entra em uma função, ele adiciona um item à pilha de retorno de chamada do contexto de erro. Quando sai de uma função, remove um item dessa pilha.

Se o relatório de erros do servidor PostgreSQL funcionar, como ereportou elogé chamado, ele chama o retorno de chamada do contexto de erro. Mas no PL / PgSQL, se perceber que está sendo chamado de um RAISEretorno de chamada intencionalmente, não faz nada.

Dado isso, não vejo como alcançar o que você deseja sem aplicar o patch no PostgreSQL. Sugiro postar mensagens para o pgsql-general perguntando por RAISEque não fornece o contexto de erro agora que o PL / PgSQL precisa GET STACKED DIAGNOSTICSusá-lo.

(BTW, o contexto de exceção não é um rastreamento de pilha como tal. Parece um pouco porque PL / PgSQL adiciona cada chamada de função à pilha, mas também é usado para outros detalhes no servidor.)

Craig Ringer
fonte
Muito obrigado, Craig, pela resposta rápida e completa. Parece estranho para mim e certamente contraria minhas expectativas. A utilidade de RAISEé diminuída por essa verificação. Vou escrever para eles.
Taytay
@Taytay Inclua aqui um link para sua pergunta, mas verifique se o seu e-mail está completo e pode ser entendido sem seguir o link; muitas pessoas ignoram postagens somente de link ou principalmente de link. Se você tiver a chance de colocar um link para sua postagem nos comentários aqui, via archives.postgresql.org, seria realmente incrível para ajudar outras pessoas mais tarde.
Craig campainha
Obrigado craig. Bom conselho. Criei um tópico aqui: postgresql.org/message-id/… A partir de agora, eles estão procurando uma boa solução para o problema.
Taytay
6

Você pode contornar essa restrição e fazer com que o plpgsql emita o contexto de erro conforme desejado, chamando outra função que gera (aviso, aviso, ...) o erro para você.

Postei uma solução para isso há alguns anos - em um dos meus primeiros posts aqui no dba.SE :

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

Detalhes:

Expandi seu caso de teste publicado para demonstrar que funciona no Postgres 9.3:

SQL Fiddle.

Erwin Brandstetter
fonte
Muito obrigado Erwin! Curiosamente, na verdade, experimentei sua solução antes de postar, mas devo ter feito algo errado e não entendi o contexto que esperava. Agora que eu vi o violino (obrigado por me mostrar isso também), vou tentar outra vez!
Taytay
Bem feito; não deveria ser necessário, mas parece que isso funcionaria.
Craig campainha
@ CraigRinger: Como as exceções devem ser, bem, a exceção , o impacto mínimo no desempenho também não deve importar. Temos todas as opções dessa maneira.
Erwin Brandstetter
Concordo totalmente, eu gostaria de ver a necessidade da solução alternativa desaparecer em algum momento.
Craig campainha
@ CraigRinger: Verdadeiro. Se isso não vai acontecer tão cedo, poderíamos sugerir esta solução alternativa no manual ...
Erwin Brandstetter