Postgres: возврат результатов или ошибки из сохраненных функций - PullRequest
0 голосов
/ 07 мая 2018

Я изо всех сил пытаюсь выяснить, как лучше всего обработать возврат результатов или ошибок в мое приложение из хранимых функций Postgres.

Рассмотрим следующий придуманный пример psudeocode:

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

Ответы [ 2 ]

0 голосов
/ 07 мая 2018

Мы делаем что-то похожее на то, что вы пытаетесь сделать, но мы используем TEXT вместо ANYELEMENT, потому что (почти?) Любой тип может быть приведен к TEXT и обратно.Таким образом, наш тип выглядит примерно так:

(errors our_error_type[], result TEXT)

Функция, которая возвращает это, сохраняет ошибки в массиве errors (это просто некоторый пользовательский тип ошибки) и может сохранить результат (приведение к тексту) в поле result.

Вызывающая функция знает, какого типа она ожидает, поэтому она может сначала проверить массив errors, чтобы узнать, были ли возвращены какие-либо ошибки, и, если нет, она может привестиresult значение ожидаемого типа возвращаемого значения.

Как общее наблюдение, я думаю, что исключения являются более элегантными (возможно, потому что я пришел из ac # background).Единственная проблема заключается в том, что обработка исключений в plpgsql является (относительно) медленной, поэтому она зависит от контекста - если вы выполняете что-то много раз в цикле, я бы предпочел решение, которое не использует обработку исключений;если это один вызов и / или особенно когда вы хотите, чтобы он был прерван, я предпочитаю выдавать исключение.На практике мы используем оба в разных точках стека вызовов.

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


Например:

Настройка:

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 |
0 голосов
/ 07 мая 2018

Это немного основано на мнении, но я думаю, что выдача ошибки - лучшее и самое элегантное решение. Вот для чего ошибки!

Чтобы различать различные сообщения об ошибках, вы можете использовать SQLSTATE, которые начинаются с 6, 8 или 9 (они не используются), тогда вам не нужно зависеть от формулировки сообщения об ошибке.

Вы можете вызвать такую ​​ошибку с помощью

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