Установите временную глобальную переменную во время запроса Postgres - PullRequest
1 голос
/ 14 апреля 2019

Можно ли установить переменную во время запроса (допустимо только для рассматриваемого запроса), которая может быть захвачена процедурой TRIGGER?

Например, я хочу записать идентификатор исполнителя запроса (current_user всегда один и тот же). Поэтому я бы сделал что-то вроде этого:

tbl_executor (
  id   PRIMARY KEY,
  name VARCHAR
);
tbl_log (
  executor REFERENCE tbl_executor(id),
  op VARCHAR
);
tbl_other ...

CREATE TRIGGER t AFTER INSERT OR UPDATE OR DELETE ON tbl_executor 
FOR EACH ROW 
EXECUTE PROCEDURE (INSERT INTO tbl_log VALUES( ID_VAR_OF_THIS_QUERY ,TG_OP))

Теперь, если я выполню запрос вроде:

INSERT INTO tbl_other 
VALUES(.......) - and set ID_VAR_OF_THIS_QUERY='id of executor' -

Я получаю следующий результат:

           tbl_log
-----------------------------
id                | op      |
-----------------------------
'id of executor'  | 'INSERT'|

Надеюсь, я сделал идею ... и думаю, что это вряд ли осуществимо ... но есть ли кто-нибудь, кто мог бы мне помочь?

Ответы [ 2 ]

2 голосов
/ 14 апреля 2019

Чтобы ответить на вопрос

Вы можете SET a ( настраиваемый параметр ) примерно так:

SET myvar.role_id = '123';

Но для этого требуется буквальное значение.Также есть функция set_config(). Цитирование руководства:

set_config(setting_name, new_value, is_local) ... установить параметр и вернуть новое значение

set_config установить параметр setting_name в new_value,Если is_local равно true, новое значение будет применяться только к текущей транзакции.

Соответственно, считайте значения опций с SHOW или current_setting().Связано:

Но ваш триггер находится на неправильной таблице (tbl_executor) с неправильнымсинтаксис.Похоже на код Oracle, где вы можете предоставить код для CREATE TRIGGER напрямую.В Postgres вам нужна функция триггера первая:

Итак:

CREATE OR REPLACE FUNCTION trg_log_who()
  RETURNS trigger AS
$func$
BEGIN
   INSERT INTO tbl_log(executor, op)
   VALUES(current_setting('myvar.role_id')::int, TG_OP);  -- !

   RETURN NULL;  -- irrelevant for AFTER trigger     
END
$func$  LANGUAGE plpgsql;

В вашем примере настройки требуется приведение типа ::int.
Затем:

CREATE TRIGGER trg_log_who
AFTER INSERT OR UPDATE OR DELETE ON tbl_other  -- !
FOR EACH ROW EXECUTE PROCEDURE trg_log_who();  -- !

Наконец, извлечение id из таблицы tbl_executorчтобы установить переменную:

BEGIN;
SELECT set_config('myvar.role_id', id::text, true)   -- !
FROM   tbl_executor
WHERE  name = current_user;

INSERT INTO tbl_other VALUES( ... );
INSERT INTO tbl_other VALUES( ... );
--  more?
COMMIT;

Установите третий параметр (is_local) из set_config() в true, чтобы сделать его локальным для сессии как просили.(Эквивалент SET LOCAL.)

Но почему на строку ?Казалось бы, более разумно сделать это для оператора ?

...
FOR EACH <b>STATEMENT</b> EXECUTE PROCEDURE trg_foo();

Другой подход

Несмотря на это, я бы рассмотрел другой подход: простая функция, возвращающаяid столбец по умолчанию:

CREATE OR REPLACE FUNCTION f_current_role_id()
  RETURNS int LANGUAGE sql STABLE AS
'SELECT id FROM tbl_executor WHERE name = current_user';

CREATE TABLE tbl_log (
  executor int DEFAULT f_current_role_id() REFERENCES tbl_executor(id)
, op VARCHAR
);

Затем в функции триггера игнорируйте столбец executor;будет заполнено автоматически:

...
   INSERT INTO tbl_log(op) VALUES(TG_OP);
...

Обратите внимание на разницу между current_user и session_user.См .:

2 голосов
/ 14 апреля 2019

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


    create table connection_global_vars(
       backend_pid bigint primary key,
       id_of_executor varchar(50)
    );
    insert into connection_global_vars(backend_pid) select pg_backend_pid() on conflict do nothing;
    update connection_global_vars set id_of_executor ='id goes here' where backend_pid = pg_backend_pid();


    -- in the trigger: 

CREATE TRIGGER t AFTER INSERT OR UPDATE OR DELETE ON tbl_executor 
FOR EACH ROW 
EXECUTE PROCEDURE (INSERT INTO tbl_log VALUES( (select id_of_executor from connection_global_vars where backend_pid = pg_backend_pid()) ,TG_OP))

Другой вариант - создать временную таблицу (которая существует для каждого соединения).

 create temporary table if not exists connection_global_vars(
       id_of_executor varchar(50)
    ) on commit delete rows;
    insert into connection_global_vars(id_of_executor) select null where not exists (select 1 from connection_global_vars);
    update connection_global_vars set id_of_executor ='id goes here';



    -- in the trigger: 

CREATE TRIGGER t AFTER INSERT OR UPDATE OR DELETE ON tbl_executor 
FOR EACH ROW 
EXECUTE PROCEDURE (INSERT INTO tbl_log VALUES( (select id_of_executor from connection_global_vars where backend_pid = pg_backend_pid()) ,TG_OP))

ДляВ частности, в PostgreSQL это, вероятно, не сильно повлияет на производительность, за исключением того, что временная таблица без меток может быть немного быстрее.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...