Come ottenere il contesto di eccezione per un'eccezione sollevata manualmente in PL / pgSQL?


11

In Postgres, otteniamo la "traccia stack" delle eccezioni usando questo codice:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Questo funziona bene per le eccezioni "naturali", ma se solleviamo un'eccezione usando

RAISE EXCEPTION 'This is an error!';

... quindi non c'è traccia dello stack. Secondo una voce della mailing list , questo potrebbe essere intenzionale, anche se non posso per la vita di me capire perché. Mi fa venire voglia di capire un altro modo per lanciare un'eccezione diversa dall'uso RAISE. Mi sto perdendo qualcosa di ovvio? Qualcuno ha un trucco per questo? Esiste un'eccezione che posso generare da Postgres che contiene una stringa di mia scelta, in modo da ottenere non solo la mia stringa nel messaggio di errore, ma anche la traccia dello stack completo?

Ecco un esempio 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;

Potrebbe essere una buona idea mostrare un semplice esempio qui.
Craig Ringer

Buon punto @CraigRinger. Fatto!
Taytay,

Non è autonomo. Cosa error_info? Sembra un tipo personalizzato.
Craig Ringer

Scusa, pensavo volessi solo un contesto generale. Ho rimosso le cose estranee.
Taytay,

Risposte:


9

Questo comportamento sembra essere in base alla progettazione.

Nel src/pl/plpgsql/src/pl_exec.ccontesto dell'errore callback controlla esplicitamente se viene chiamato nel contesto di RAISEun'istruzione PL / PgSQL e, in tal caso, salta l'emissione del contesto dell'errore:

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

Non riesco a trovare alcun riferimento specifico sul perché sia così.

Internamente nel server, lo stack di contesto viene generato elaborando error_context_stack, che è un callback concatenato che aggiunge informazioni a un elenco quando viene chiamato.

Quando PL / PgSQL inserisce una funzione, aggiunge un elemento allo stack di callback del contesto di errore. Quando lascia una funzione rimuove un oggetto da quella pila.

Se le funzioni di segnalazione errori del server PostgreSQL, come ereporto elogvengono chiamate, chiama il callback del contesto di errore. Ma in PL / PgSQL se nota che viene chiamato da un RAISEsuo callback intenzionalmente non fa nulla.

Detto questo, non vedo alcun modo per ottenere ciò che vuoi senza patchare PostgreSQL. Suggerisco di inviare posta a pgsql-general chiedendo perché RAISEnon fornisce il contesto di errore ora che PL / PgSQL deve GET STACKED DIAGNOSTICSutilizzarlo.

(A proposito, il contesto dell'eccezione non è una traccia dello stack in quanto tale. Sembra un po 'come uno perché PL / PgSQL aggiunge ogni chiamata di funzione allo stack, ma è anche usata per altri dettagli nel server.)


Grazie mille Craig per la risposta rapida e completa. Mi sembra strano, e certamente in contrasto con le mie aspettative. L'utilità di RAISEè ridotta da tale controllo. Scriverò a loro.
Taytay,

@Taytay Inserisci qui un link alla tua domanda, ma assicurati che la tua posta sia completa e possa essere compresa senza seguire il link; molte persone ignorano i post solo link o principalmente link. Se hai la possibilità di inserire un link al tuo post nei commenti qui, tramite archives.postgresql.org, sarebbe davvero fantastico aiutare gli altri in seguito.
Craig Ringer,

Grazie Craig. Buon Consiglio. Ho creato una discussione qui: postgresql.org/message-id/… A partire da ora, stanno cercando una buona soluzione al problema.
Taytay,

6

Puoi aggirare questa limitazione e far sì che plpgsql emetta il contesto di errore come desiderato chiamando un'altra funzione che genera (avviso, avviso, ...) l'errore per te.

Ho pubblicato una soluzione per questo un paio di anni fa - in uno dei miei primi post qui su 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;

Dettagli:

Ho ampliato il tuo test case pubblicato per dimostrare che funziona in Postgres 9.3:

SQL Fiddle.


Grazie mille Erwin! Stranamente, ho effettivamente sperimentato la tua soluzione prima di pubblicare, ma devo aver fatto qualcosa di sbagliato e non ho avuto il contesto che mi aspettavo. Ora che ho visto il violino (grazie per avermelo mostrato anche io), ci proverò ancora!
Taytay,

Ben fatto; non dovrebbe essere necessario, ma sembra che farebbe il trucco.
Craig Ringer

@CraigRinger: poiché le eccezioni dovrebbero essere, beh, l' eccezione , anche l'impatto minimo sulle prestazioni non dovrebbe importare. Abbiamo tutte le opzioni in questo modo.
Erwin Brandstetter,

Totalmente d'accordo, mi piacerebbe solo vedere la necessità della soluzione alternativa a un certo punto.
Craig Ringer

@CraigRinger: True. Se ciò non accadrà presto, potremmo suggerire questa soluzione alternativa nel manuale ...
Erwin Brandstetter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.