Как получить контекст исключения для вручную вызванного исключения в PL / pgSQL?


11

В Postgres мы получаем «трассировку стека» исключений, используя этот код:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Это прекрасно работает для «естественных» исключений, но если мы возбуждаем исключение, используя

RAISE EXCEPTION 'This is an error!';

... тогда нет трассировки стека. Согласно записи в списке рассылки , это может быть преднамеренным, хотя я не могу понять, почему. Это заставляет меня хотеть найти другой способ вызвать исключение, кроме использования RAISE. Я что-то упускаю из виду? У кого-нибудь есть хитрость для этого? Есть ли исключение, которое я могу заставить Postgres выдавать, который будет содержать выбранную мной строку, чтобы я мог получить не только свою строку в сообщении об ошибке, но и полную трассировку стека?

Вот полный пример:

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;

Это может быть хорошей идеей, чтобы показать простой пример здесь.
Крейг Рингер

Хороший вопрос @CraigRinger. Готово!
Taytay

Это не автономно. Что error_info? Похоже, пользовательский тип.
Крейг Рингер

Извините - думал, что вы просто хотели общий контекст. Я удалил посторонние вещи.
Taytay

Ответы:


9

Такое поведение, кажется, по замыслу.

В src/pl/plpgsql/src/pl_exec.cконтексте ошибки обратный вызов явно проверяет, вызывается ли он в контексте оператора PL / PgSQL, RAISEи, если да, пропускает выдачу контекста ошибки:

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

Я не могу найти какую-либо конкретную ссылку на то, почему это так.

Внутри на сервере стек контекста генерируется путем обработки error_context_stackобратного обратного вызова, который добавляет информацию в список при вызове.

Когда PL / PgSQL входит в функцию, он добавляет элемент в стек обратного вызова контекста ошибки. Когда он покидает функцию, он удаляет элемент из этого стека.

Если функции отчетов об ошибках сервера PostgreSQL, такие как ereportили elogвызываются, вызывают обратный вызов контекста ошибки. Но в PL / PgSQL, если он замечает, что его вызывают из-за RAISEсвоих обратных вызовов, намеренно ничего не делают.

Учитывая это, я не вижу способа достичь того, чего вы хотите, не устанавливая исправления в PostgreSQL. Я предлагаю отправлять почту на pgsql-general, спрашивая, почему RAISEне предоставляется контекст ошибки сейчас, когда PL / PgSQL должен GET STACKED DIAGNOSTICSиспользовать его.

(Кстати, контекст исключения не является трассировкой стека как таковой. Он немного похож на один, потому что PL / PgSQL добавляет каждый вызов функции в стек, но он также используется для других деталей на сервере.)


Большое спасибо Крейг за быстрый и тщательный ответ. Мне это кажется странным и, безусловно, противоречит моим ожиданиям. Эта RAISEпроверка снижает полезность . Я напишу им.
Taytay

@Taytay Пожалуйста, включите ссылку на ваш вопрос здесь, но убедитесь, что ваша почта заполнена и понятна без перехода по ссылке; многие люди игнорируют посты, содержащие только ссылки или ссылки. Если у вас есть возможность добавить ссылку на свое сообщение в комментариях здесь, через archives.postgresql.org , было бы здорово помочь другим людям позже.
Крейг Рингер

Спасибо, Крейг. Хороший совет. Я создал ветку здесь: postgresql.org/message-id/… На данный момент они ищут хорошее решение проблемы.
Taytay

6

Вы можете обойти это ограничение и заставить plpgsql генерировать контекст ошибки по желанию, вызывая другую функцию, которая вызывает (предупреждение, уведомление, ...) ошибку для вас.

Я опубликовал решение для этого пару лет назад - в одном из моих первых сообщений здесь, на 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;

Подробности:

Я расширил ваш тестовый пример, чтобы продемонстрировать, что он работает в Postgres 9.3:

SQL Fiddle.


Большое спасибо, Эрвин! Как ни странно, я на самом деле экспериментировал с вашим решением перед публикацией, но я, должно быть, сделал что-то не так, и я не получил ожидаемого контекста. Теперь, когда я увидел скрипку (спасибо, что показал мне это тоже), я сделаю еще один снимок!
Тайтай

Красиво сделано; в этом не должно быть необходимости, но, похоже, это поможет.
Крейг Рингер

@CraigRinger: Поскольку исключения должны быть исключениями , минимальное влияние на производительность также не должно иметь значения. У нас есть все варианты таким образом.
Эрвин Брандштеттер,

Полностью согласен, я просто хотел бы, чтобы в какой-то момент необходимость обходного пути ушла.
Крейг Рингер

@CraigRinger: правда. Если это не произойдет в ближайшее время, мы могли бы предложить этот обходной путь в руководстве ...
Эрвин Брандштеттер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.