Jak uzyskać kontekst wyjątku dla ręcznie zgłaszanego wyjątku w PL / pgSQL?

11

W Postgres otrzymujemy „ślad stosu” wyjątków za pomocą tego kodu:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Działa to dobrze w przypadku „naturalnych” wyjątków, ale jeśli zgłaszamy wyjątek za pomocą

RAISE EXCEPTION 'This is an error!';

... wtedy nie ma śladu stosu. Zgodnie z wpisem na liście mailowej może to być celowe, chociaż nie mogę przez całe życie dowiedzieć się, dlaczego. To sprawia, że ​​chcę wymyślić inny sposób zgłoszenia wyjątku niż użycie RAISE. Czy brakuje mi czegoś oczywistego? Czy ktoś ma na to jakiś sposób? Czy istnieje wyjątek, który mogę skłonić Postgres do rzucenia, który zawierałby wybrany przeze mnie ciąg, tak że dostałbym nie tylko mój ciąg w komunikacie o błędzie, ale także ślad pełnego stosu?

Oto pełny przykład:

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
źródło
Dobrym pomysłem może być pokazanie tutaj prostego przykładu.
Craig Ringer
Dobry punkt @CraigRinger. Gotowe!
Taytay,
To nie jest samodzielne. Co jest error_info? Wygląda na typ niestandardowy.
Craig Ringer
Przepraszam - myślałem, że chcesz tylko ogólny kontekst. Usunąłem obce rzeczy.
Taytay,

Odpowiedzi:

9

To zachowanie wydaje się zgodne z projektem.

W src/pl/plpgsql/src/pl_exec.ckontekście błędu wywołanie zwrotne wyraźnie sprawdza, czy jest wywoływane w kontekście RAISEinstrukcji PL / PgSQL , a jeśli tak, pomija emitowanie kontekstu błędu:

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

Nie mogę znaleźć żadnego konkretnego odniesienia do tego , dlaczego tak jest.

Wewnętrznie na serwerze stos kontekstowy jest generowany przez przetwarzanie error_context_stack, który jest połączeniem zwrotnym z łańcuchem, który dodaje informacje do listy po wywołaniu.

Gdy PL / PgSQL wchodzi w funkcję, dodaje element do stosu wywołania zwrotnego kontekstu błędu. Kiedy opuszcza funkcję, usuwa przedmiot ze stosu.

Jeśli funkcje raportowania błędów serwera PostgreSQL, takie jak ereportlub elogsą wywoływane, wywołuje wywołanie zwrotne kontekstu błędu. Ale w PL / PgSQL, jeśli zauważy, że jest wywoływany przez RAISEwywołania zwrotne celowo nie rób nic.

Biorąc to pod uwagę, nie widzę żadnego sposobu na osiągnięcie tego, co chcesz bez łatania PostgreSQL. Sugeruję wysyłanie wiadomości do pgsql-general z pytaniem, dlaczego RAISEnie udostępnia kontekstu błędu, skoro PL / PgSQL musi GET STACKED DIAGNOSTICSz niego skorzystać.

(BTW, kontekst wyjątku nie jest śladem stosu jako takim. Wygląda trochę jak jeden, ponieważ PL / PgSQL dodaje każde wywołanie funkcji do stosu, ale jest również używane do innych szczegółów na serwerze.)

Craig Ringer
źródło
Dziękuję bardzo Craig za szybką i dokładną odpowiedź. Wydaje mi się to dziwne iz pewnością sprzeczne z moimi oczekiwaniami. Ta RAISEkontrola zmniejsza użyteczność . Napiszę do nich.
Taytay,
@Taytay Podaj tutaj link do swojego pytania, ale upewnij się, że poczta jest kompletna i że można ją zrozumieć bez podążania za linkiem; wiele osób ignoruje posty zawierające tylko łącze lub głównie linki. Jeśli masz szansę zamieścić link do swojego wpisu w komentarzach tutaj, za pośrednictwem archives.postgresql.org , byłoby to naprawdę niesamowite, aby pomóc innym ludziom później.
Craig Ringer
Dzięki Craig. Dobra rada. Utworzyłem tutaj wątek: postgresql.org/message-id/... Na razie szukają dobrego rozwiązania tego problemu.
Taytay,
6

Możesz obejść to ograniczenie i sprawić, aby plpgsql emitował kontekst błędu zgodnie z potrzebami, wywołując inną funkcję, która podnosi (ostrzeżenie, zawiadomienie, ...) błąd za Ciebie.

Rozwiązałem to kilka lat temu - w jednym z moich pierwszych postów tutaj na 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;

Detale:

Rozszerzyłem opublikowany przypadek testowy, aby wykazać, że działa on w Postgres 9.3:

SQL Fiddle.

Erwin Brandstetter
źródło
Dziękuję bardzo Erwin! Co zabawne, eksperymentowałem z twoim rozwiązaniem przed opublikowaniem, ale musiałem zrobić coś złego i nie dostałem kontekstu, którego oczekiwałem. Teraz, gdy widziałem skrzypce (dziękuję, że mi to pokazałeś), dam mu jeszcze jedną szansę!
Taytay,
Ładnie wykonane; nie powinno to być konieczne, ale wygląda na to, że załatwi sprawę.
Craig Ringer
@CraigRinger: Ponieważ wyjątki powinny stanowić wyjątek , minimalny wpływ na wydajność również nie powinien mieć znaczenia. W ten sposób mamy wszystkie opcje.
Erwin Brandstetter
Całkowicie się zgadzam, chciałbym tylko zobaczyć, jak w pewnym momencie potrzeba obejścia tego problemu zniknie.
Craig Ringer
@CraigRinger: True. Jeśli to się wkrótce nie wydarzy, możemy zasugerować to obejście w instrukcji ...
Erwin Brandstetter