Postgres:从存储函数返回结果或错误

Posted

技术标签:

【中文标题】Postgres:从存储函数返回结果或错误【英文标题】:Postgres: Returning Results or Error from Stored Functions 【发布时间】:2018-05-07 11:02:19 【问题描述】:

我正在努力弄清楚如何最好地处理从 Postgres 存储函数返回到我的应用程序的结果或错误。

考虑以下人为的伪代码示例:

app.get_resource(_username text)
    RETURNS <???>

BEGIN

    IF ([ ..user exists.. ] = FALSE) THEN
        RETURN 'ERR_USER_NOT_FOUND';
    END IF;

    IF ([ ..user has permission.. ] = FALSE) THEN
        RETURN 'ERR_NO_PERMISSION';
    END IF;

    -- Return the full user object.
    RETURN QUERY( SELECT 1 
        FROM app.resources
        WHERE app.resources.owner = _username);

END

函数可能因特定错误而失败,也可能成功并返回 0 个或更多资源。

起初我尝试创建一个自定义类型以始终用作每个函数中的标准返回类型:

CREATE TYPE app.appresult AS (
  success boolean,
  error   text,
  result  anyelement
);

Postgres 不允许这样做:

[42P16] ERROR: column "result" has pseudo-type anyelement

然后我发现了 OUT 参数并尝试了以下用途:

CREATE OR REPLACE FUNCTION app.get_resource(
    IN      _username   text,
    OUT     _result app.appresult -- Custom type 
                                  -- success bool, error text
)

RETURNS SETOF record
AS
$$
BEGIN

  IF 1 = 1 THEN -- just a test
    _result.success = false;
    _result.error   = 'ERROR_ERROR';
    RETURN NULL;
  END IF;

  RETURN QUERY(SELECT * FROM app.resources);

END;
$$
  LANGUAGE 'plpgsql' VOLATILE;

Postgres 也不喜欢这样:

[42P13] ERROR: function result type must be app.appresult because of OUT parameters

还尝试了类似的功能,但相反:返回自定义 app.appresult 对象并将 OUT 参数设置为“SETOF RECORD”。这也是不允许的。

最后我研究了 Postgres 异常处理使用

RAISE EXCEPTION 'ERR_MY_ERROR';

所以在示例函数中,我会提出这个错误并返回。 这导致驱动程序将错误发送回:

"ERROR:  ERR_MY_ERROR\nCONTEXT:  PL/pgSQL function app.test(text) line 6 at RAISE\n(P0001)"

这很容易解析,但以这种方式做事感觉不对。

解决此问题的最佳方法是什么? 是否有可能有一个我可以返回的自定义 AppResult 对象?

类似:

 success bool, error text, result <whatever type> 

//编辑1 //

我认为我更倾向于@Laurenz Albe 解决方案。

我的主要目标很简单:调用一个可以返回错误或一些数据的存储过程。

使用 RAISE 似乎可以做到这一点,并且 C++ 驱动程序允许轻松检查从查询返回的错误条件。

if ([error code returned from the query] == 90100)

    // 1. Parse out my overly verbose error from the raw driver
    //    error string.
    // 2. Handle the error.    

我还想知道使用自定义 SQLSTATE 代码而不是解析驱动程序字符串。

抛出“__404”可能意味着在我的 SP 执行过程中,它无法继续,因为找不到所需的某些记录。

当从我的应用程序调用 sql 函数时,我大致了解它因“__404”而失败意味着什么以及如何处理它。这避免了解析驱动程序错误字符串的额外步骤。

我也看出这是个坏主意。

睡前阅读: https://www.postgresql.org/docs/current/static/errcodes-appendix.html

【问题讨论】:

发生错误时引发异常正确的解决方案 【参考方案1】:

这有点基于意见,但我认为抛出错误是最好和最优雅的解决方案。这就是错误的原因!

为了区分各种错误信息,您可以使用以 6、8 或 9 开头的 SQLSTATE(这些都没有使用),那么您不必依赖错误信息的措辞。

你可以提出这样的错误

RAISE EXCEPTION SQLSTATE '90001' USING MESSAGE = 'my own error';

【讨论】:

【参考方案2】:

我们执行的操作与您尝试执行的操作类似,但我们使用 TEXT 而不是 ANYELEMENT,因为(几乎?)任何类型都可以转换为 TEXT 并返回。所以我们的类型看起来像:

(errors our_error_type[], result TEXT)

返回 this 的函数将错误存储在 errors 数组中(它只是一些自定义错误类型),并且可以将结果(转换为文本)存储在 result 字段中。

调用函数知道它期望的类型,因此它可以首先检查errors 数组以查看是否返回了任何错误,如果没有,它可以将result 值转换为期望的返回类型。

作为一般观察,我认为异常更优雅(可能是因为我来自 c# 背景)。唯一的问题是 plpgsql 异常处理(相对)慢,所以它取决于上下文 - 如果您在循环中多次运行某些东西,我更喜欢不使用异常处理的解决方案;如果它是一个单一的呼叫,和/或特别是当您希望它中止时,我更喜欢引发异常。在实践中,我们在调用堆栈的各个点都使用了这两种方法。

正如 Laurenz Albe 指出的那样,您并不是要“解析”异常,而是要在特定字段中引发具有特定值的异常,然后捕获异常的函数可以提取并直接对其进行操作。


举个例子:

设置:

CREATE TABLE my_table (id INTEGER, txt TEXT);
INSERT INTO my_table VALUES (1,'blah');

CREATE TYPE my_type AS (result TEXT);

CREATE OR REPLACE FUNCTION my_func()
RETURNS my_type AS
$BODY$
DECLARE
    m my_type;
BEGIN
    SELECT my_table::TEXT
        INTO m.result
    FROM my_table;

    RETURN m;
END
$BODY$
LANGUAGE plpgsql STABLE;

运行:

SELECT (m.result::my_table).*
FROM my_func() AS m

结果:

| id | txt  |
-------------
| 1  | blah |

【讨论】:

我将自定义返回类型重新创建为 success bool, error text, result text 并尝试从表中选择行到它的结果字段中:gist.github.com/Think7/7d30047c73e4c41d78b293a85127dee2 结果是“(f,MY_ERROR, Wendy)" 由驱动程序以字符串形式返回。我需要将整个结果集返回给应用程序。 @Andrew 如果您希望它返回单行,请执行以下操作:SELECT resources::TEXT FROM app.resources INTO vReturn.result;。这将选择处于非扩展状态(不是技术术语)的整个记录​​。然后调用代码可以将其转换回并根据需要扩展它,以访问各个字段。但是,如果您想返回多行,这将行不通(除非您将它们聚合,但它会变得很愚蠢)并且您最好使用异常。 @Andrew 查看编辑以了解其工作原理。

以上是关于Postgres:从存储函数返回结果或错误的主要内容,如果未能解决你的问题,请参考以下文章

Postgres存储函数如何返回表

如何存储我从 Postgres 存储函数返回的数据

无法使用 Postgres 存储函数获取 ID - int 类型的值错误

存储过程和存储函数区别

从 postgres 函数映射自定义结果记录时面临问题

Postgres 存储函数可以同时具有返回值和输出参数吗?