Почему в PostgreSQL 10 ошибка нарушения внешнего ключа не перехватывается обработкой исключений pgplsql для отложенных ограничений? - PullRequest
0 голосов
/ 14 января 2019

При вызове функции SQL, выполняющей вставку, из функции PGPLSQL с блоком EXCEPTION WHEN OTHERS исключение возникает, а не перехватывается, если нарушенное ограничение внешнего ключа откладывается.

Я использую Amazon Aurora PostgreSQL совместимый (v 10.4). Я обнаружил, что мой обработчик исключений не всегда перехватывал исключения, которые вместо этого вызывались в приложении (в моем случае это функция Python AWS Lambda, использующая Pyscopg2).

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

Я также воспроизвел такое же поведение на экземпляре RDS (не Aurora), работающем под управлением версии 10.5, а также на экземпляре RDS 9.6.6, так что это не Aurora и не относится к версии 10.4.

Это ошибка? Или я упускаю что-то, что задокументировано с отложенными ограничениями?

Вот две таблицы и две функции.

CREATE TABLE public.load (
  load_id           BIGINT                                 NOT NULL,
  created_timestamp TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
  ds_code           TEXT                                   NOT NULL,
  route             TEXT,
  file_name         TEXT,
  user_name         TEXT,
  staged            BOOLEAN,
  staging_duration  INTERVAL,
  CONSTRAINT load_pkey
    PRIMARY KEY (load_id));


CREATE TABLE load_content (
  load_id           BIGINT NOT NULL,
  load_content      TEXT,
  CONSTRAINT data_load_pk
    PRIMARY KEY (load_id),
  CONSTRAINT data_load_load_load_id_fk
    FOREIGN KEY (load_id) REFERENCES public.load
)
;
CREATE FUNCTION insert_something() RETURNS void
  LANGUAGE SQL
AS
$$
INSERT INTO public.load_content values (1);

$$
;
CREATE FUNCTION test_load() RETURNS TEXT
  LANGUAGE plpgsql
AS
$$

BEGIN

  PERFORM public.insert_something();
  RETURN 'success';


  EXCEPTION
  WHEN OTHERS THEN
    RETURN 'failure';

END
  ;
$$
;

Выполнение public.test_load () возвращает одну строку «fail».

Если вы затем сделаете это:

alter table public.load_content
  drop constraint data_load_load_load_id_fk;

ALTER TABLE public.load_content
  ADD CONSTRAINT data_load_load_load_id_fk
    FOREIGN KEY (load_id) REFERENCES public.load
DEFERRABLE INITIALLY DEFERRED
;

Затем выполните public.test_load (), исключение просто происходит:

[2019-01-14 16:36:32] [23503] ERROR: insert or update on table "load_content" violates foreign key constraint "data_load_load_load_id_fk"
[2019-01-14 16:36:32] Detail: Key (load_id)=(1) is not present in table "load".

Очевидно, я значительно упростил это для целей тестирования - то, что на самом деле происходит в моей системе, это триггер в таблице «загрузки», который срабатывает для нормализации необработанных данных (обычно JSON) в промежуточную модель данных.

1 Ответ

0 голосов
/ 14 января 2019

A DEFERRED проверка ограничения откладывается до фиксации, IMMEDIATE проверка ограничения выполняется, как следует из названия, немедленно. Поведение ограничения DEFERRABLE можно изменить в текущей транзакции с помощью SET CONSTRAINTS { ALL | name [, ...] } { DEFERRED | IMMEDIATE } (ссылка на документы) .

Если вы хотите изменить поведение функции test_load(), вот пример кода.

CREATE FUNCTION test_load() RETURNS TEXT
  LANGUAGE plpgsql
AS
$$

BEGIN

  SET CONSTRAINTS data_load_load_load_id_fk IMMEDIATE;
  PERFORM public.insert_something();
  RETURN 'success';


  EXCEPTION
  WHEN OTHERS THEN
    RETURN 'failure';

END
  ;
$$
;
...