it-swarm.com.de

Wie erhalte ich einen Ausnahmekontext für eine manuell ausgelöste Ausnahme in PL / pgSQL?

In Postgres erhalten wir die "Stapelverfolgung" von Ausnahmen unter Verwendung dieses Codes:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Dies funktioniert gut für "natürliche" Ausnahmen, aber wenn wir eine Ausnahme mit auslösen

RAISE EXCEPTION 'This is an error!';

... dann gibt es keine Stack-Trace. Laut einem Mailinglisteneintrag könnte dies beabsichtigt sein, obwohl ich für mein ganzes Leben nicht herausfinden kann, warum. Ich möchte einen anderen Weg finden, um eine andere Ausnahme als die Verwendung von RAISE auszulösen. Vermisse ich nur etwas Offensichtliches? Hat jemand einen Trick dafür? Gibt es eine Ausnahme, die Postgres auslösen kann und die eine Zeichenfolge meiner Wahl enthält, sodass ich nicht nur meine Zeichenfolge in der Fehlermeldung, sondern auch die vollständige Stapelverfolgung erhalte?

Hier ist ein vollständiges Beispiel:

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

Dieses Verhalten scheint beabsichtigt zu sein.

In src/pl/plpgsql/src/pl_exec.c Überprüft der Fehlerkontext-Rückruf explizit, ob er im Kontext einer PL/PgSQL RAISE -Anweisung aufgerufen wird, und überspringt in diesem Fall die Ausgabe des Fehlerkontexts:

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

Ich kann keinen konkreten Hinweis darauf finden, dass warum das der Fall ist.

Intern im Server wird der Kontextstapel durch Verarbeiten des error_context_stack Generiert. Hierbei handelt es sich um einen verketteten Rückruf, der beim Aufruf Informationen an eine Liste anfügt.

Wenn PL/PgSQL eine Funktion eingibt, fügt es dem Fehlerkontext-Rückrufstapel ein Element hinzu. Wenn eine Funktion verlassen wird, wird ein Element von diesem Stapel entfernt.

Wenn die Fehlerberichtsfunktionen des PostgreSQL-Servers wie ereport oder elog aufgerufen werden, wird der Fehlerkontext-Rückruf aufgerufen. Aber in PL/PgSQL, wenn es bemerkt, dass es von einem RAISE aufgerufen wird, tun seine Rückrufe absichtlich nichts.

Angesichts dessen sehe ich keine Möglichkeit, das zu erreichen, was Sie wollen, ohne PostgreSQL zu patchen. Ich schlage vor, E-Mails an pgsql-general zu senden und zu fragen, warum RAISE den Fehlerkontext nicht mehr bereitstellt, da PL/PgSQL GET STACKED DIAGNOSTICS Hat, um ihn zu verwenden.

(Übrigens ist der Ausnahmekontext kein Stack-Trace als solcher. Er sieht ein bisschen wie einer aus, da PL/PgSQL jeden Funktionsaufruf zum Stack hinzufügt, aber auch für andere Details auf dem Server verwendet wird.)

9
Craig Ringer

Sie können diese Einschränkung umgehen und plpgsql wie gewünscht Fehlerkontext ausgeben lassen durch Aufrufen einer anderen Funktion, die den Fehler für Sie auslöst (Warnung, Hinweis, ...).

Ich habe vor ein paar Jahren eine Lösung dafür veröffentlicht - in einem meiner ersten Beiträge hier auf 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;

Details:

Ich habe Ihren veröffentlichten Testfall erweitert, um zu demonstrieren, dass er in Postgres 9.3 funktioniert:

SQL Fiddle.

6