如何在 PL/pgSQL 中获取手动引发的异常的异常上下文?

Tay*_*tay 11 postgresql error-handling exception plpgsql postgresql-9.3

在 Postgres 中,我们使用以下代码获取异常的“堆栈跟踪”:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;
Run Code Online (Sandbox Code Playgroud)

这适用于“自然”异常,但如果我们使用

RAISE EXCEPTION 'This is an error!';
Run Code Online (Sandbox Code Playgroud)

...然后没有堆栈跟踪。根据邮件列表条目,这可能是故意的,尽管我终生无法弄清楚原因。这让我想找出另一种抛出异常的方法,而不是使用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;
Run Code Online (Sandbox Code Playgroud)

Cra*_*ger 9

此行为似乎是设计使然。

src/pl/plpgsql/src/pl_exec.c错误上下文回调中显式检查它是否在 PL/PgSQLRAISE语句的上下文中被调用,如果是,则跳过发出错误上下文:

/*
 * 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;
Run Code Online (Sandbox Code Playgroud)

我找不到任何关于为什么会这样的具体参考。

在服务器内部,上下文堆栈是通过处理 生成的error_context_stack,这是一个链式回调,在调用时将信息附加到列表中。

当 PL/PgSQL 进入一个函数时,它会向错误上下文回调堆栈添加一个项目。当它离开一个函数时,它会从该堆栈中删除一个项目。

如果 PostgreSQL 服务器的错误报告函数,例如ereport或被elog调用,它会调用错误上下文回调。但是在 PL/PgSQL 中,如果它注意到它是从RAISE它的回调中被调用的,它故意什么都不做。

鉴于此,我看不出有任何方法可以在不修补 PostgreSQL 的情况下实现您想要的目标。我建议向 pgsql-general 发送邮件,询问为什么RAISE既然 PL/PgSQL 必须GET STACKED DIAGNOSTICS使用它,为什么不提供错误上下文。

(顺便说一句,异常上下文本身并不是堆栈跟踪。它看起来有点像,因为 PL/PgSQL 将每个函数调用添加到堆栈中,但它也用于服务器中的其他细节。)


Erw*_*ter 7

您可以解决此限制,并通过调用另一个为您引发(警告、通知...)错误的函数来使 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;
Run Code Online (Sandbox Code Playgroud)

细节:

我扩展了您发布的测试用例以证明它在 Postgres 9.3 中有效:

SQL小提琴。