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

11

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 Eintrag in einer Mailingliste 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 auszulösenRAISE . 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;
Taytay
quelle
Es könnte eine gute Idee sein, hier ein einfaches Beispiel zu zeigen.
Craig Ringer
Guter Punkt @CraigRinger. Erledigt!
Taytay
Es ist nicht in sich geschlossen. Was ist error_info? Sieht aus wie ein benutzerdefinierter Typ.
Craig Ringer
Entschuldigung - dachte, Sie wollten nur den allgemeinen Kontext. Ich habe das fremde Zeug entfernt.
Taytay

Antworten:

9

Dieses Verhalten scheint beabsichtigt zu sein.

Im src/pl/plpgsql/src/pl_exec.cFehlerkontext prüft der Rückruf explizit, ob er im Kontext einer PL / PgSQL- RAISEAnweisung aufgerufen wird , und überspringt in diesem Fall das Ausgeben 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, warum dies der Fall ist.

Intern auf dem Server wird der Kontextstapel durch Verarbeiten des generiert. error_context_stackHierbei 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 Fehlerberichterstattungsfunktionen des PostgreSQL-Servers wie ereportoder elogaufgerufen werden, wird der Fehlerkontext-Rückruf aufgerufen. Aber in PL / PgSQL, wenn es bemerkt, dass es von einem RAISEseiner Rückrufe aufgerufen wird, tun sie 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 RAISEder Fehlerkontext nicht bereitgestellt wird GET STACKED DIAGNOSTICS, da PL / PgSQL ihn verwenden muss.

(Ü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.)

Craig Ringer
quelle
Vielen Dank Craig für die schnelle und gründliche Antwort. Es scheint mir seltsam und widerspricht sicherlich meinen Erwartungen. Die Nützlichkeit von RAISEwird durch diese Prüfung verringert. Ich werde ihnen schreiben.
Taytay
@Taytay Bitte fügen Sie hier einen Link zu Ihrer Frage hinzu. Stellen Sie jedoch sicher, dass Ihre E-Mail vollständig und verständlich ist, ohne dem Link zu folgen. Viele Leute ignorieren Beiträge, die nur Links oder meistens Links enthalten. Wenn Sie die Möglichkeit haben, einen Link zu Ihrem Beitrag in den Kommentaren hier über archives.postgresql.org zu platzieren , wäre es wirklich großartig, später anderen Menschen zu helfen.
Craig Ringer
Danke Craig. Guter Rat. Ich habe hier einen Thread erstellt: postgresql.org/message-id/… Ab sofort suchen sie nach einer guten Lösung für das Problem.
Taytay
6

Sie können diese Einschränkung umgehen und plpgsql dazu bringen, den gewünschten Fehlerkontext auszugeben, indem Sie eine andere Funktion aufrufen, 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;

Einzelheiten:

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

SQL Fiddle.

Erwin Brandstetter
quelle
Vielen Dank Erwin! Komischerweise habe ich vor dem Posten tatsächlich mit Ihrer Lösung experimentiert, aber ich muss etwas falsch gemacht haben und habe nicht den Kontext erhalten, den ich erwartet hatte. Jetzt, wo ich die Geige gesehen habe (danke, dass du mir das auch gezeigt hast), werde ich es noch einmal versuchen!
Taytay
Schön gemacht; es sollte nicht notwendig sein, aber es sieht so aus, als würde es den Trick machen.
Craig Ringer
@CraigRinger: Da Ausnahmen sollte, na ja, die Ausnahme , die minimale Auswirkungen auf die Leistung sollte keine Rolle, auch nicht . Auf diese Weise haben wir alle Möglichkeiten.
Erwin Brandstetter
Ich stimme voll und ganz zu, ich möchte nur, dass die Notwendigkeit einer Problemumgehung irgendwann verschwindet.
Craig Ringer
@CraigRinger: Stimmt. Wenn dies nicht in Kürze geschieht, empfehlen wir diese Problemumgehung möglicherweise im Handbuch ...
Erwin Brandstetter,