如何在触发器函数中使用变量设置?
How to use variable settings in trigger functions?
我想使用 SET
在 session/transaction 中记录用户的 ID,这样我可以稍后在触发器函数中使用 current_setting
访问它.基本上,我正在尝试 very similar ticket posted previously 中的选项 n2,不同之处在于我使用的是 PG 10.1。
我一直在尝试 3 种方法来设置变量:
SET local myvars.user_id = 4
,从而在事务本地设置;
SET myvars.user_id = 4
,从而在session中设置;
SELECT set_config('myvars.user_id', '4', false)
,这取决于最后一个参数,将是前两个选项的快捷方式。
其中None个在触发器中是可用的,触发器在通过current_setting
获取变量时收到NULL
。这是我设计的一个脚本来解决它(可以很容易地与 postgres docker 图像一起使用):
database=$POSTGRES_DB
user=$POSTGRES_USER
[ -z "$user" ] && user="postgres"
psql -v ON_ERROR_STOP=1 --username "$user" $database <<-EOSQL
DROP TRIGGER IF EXISTS add_transition1 ON houses;
CREATE TABLE IF NOT EXISTS houses (
id SERIAL NOT NULL,
name VARCHAR(80),
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS transitions1 (
id SERIAL NOT NULL,
house_id INTEGER,
user_id INTEGER,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
PRIMARY KEY(id),
FOREIGN KEY(house_id) REFERENCES houses (id) ON DELETE CASCADE
);
CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
DECLARE
user_id integer;
BEGIN
user_id := current_setting('myvars.user_id')::integer || NULL;
INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER add_transition1 AFTER INSERT OR UPDATE ON houses FOR EACH ROW EXECUTE PROCEDURE add_transition1();
BEGIN;
%1% SELECT current_setting('myvars.user_id');
%2% SELECT set_config('myvars.user_id', '55', false);
%3% SELECT current_setting('myvars.user_id');
INSERT INTO houses (name) VALUES ('HOUSE PARTY') RETURNING houses.id;
SELECT * from houses;
SELECT * from transitions1;
COMMIT;
DROP TRIGGER IF EXISTS add_transition1 ON houses;
DROP FUNCTION IF EXISTS add_transition1;
DROP TABLE transitions1;
DROP TABLE houses;
EOSQL
我得出的结论是该函数是在不同的事务和不同的 (?) 会话中触发的。这是可以配置的东西,以便所有内容都发生在同一上下文中吗?
不清楚您为什么要尝试将 NULL
连接到 user_id
,但这显然是问题的原因。摆脱它:
CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
DECLARE
user_id integer;
BEGIN
user_id := current_setting('myvars.user_id')::integer;
INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
注意
SELECT 55 || NULL
总是给出 NULL
.
您可以在值不存在时捕获异常 - 这是我为使其正常工作所做的更改:
CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
DECLARE
user_id integer;
BEGIN
BEGIN
user_id := current_setting('myvars.user_id')::integer;
EXCEPTION WHEN OTHERS THEN
user_id := 0;
END;
INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION insert_house() RETURNS void as $$
DECLARE
user_id integer;
BEGIN
PERFORM set_config('myvars.user_id', '55', false);
INSERT INTO houses (name) VALUES ('HOUSE PARTY');
END; $$ LANGUAGE plpgsql;
正确处理 customized option 的所有可能情况:
选项尚未设置
所有对其的引用都会引发 异常,包括 current_setting()
unless called with the second parameter missing_ok
. The manual:
If there is no setting named setting_name
, current_setting
throws an error unless missing_ok
is supplied and is true
.
选项设置为有效整数文字
选项设置为无效整数文字
选项重置(这会导致 3. 的特例)
例如,如果您使用 SET LOCAL
或 set_config('myvars.user_id3', '55', true)
设置自定义选项,选项值将在交易结束时重置。它仍然 存在 ,可以引用,但它 returns 现在是一个空字符串 (''
) - 无法转换为 integer
.
撇开你的demo中明显的错误不谈,你需要为所有4种情况做好准备。所以:
CREATE OR REPLACE FUNCTION add_transition1()
RETURNS trigger AS
$func$
DECLARE
_user_id text := current_setting('myvars.user_id', true); -- see 1.
BEGIN
IF _user_id ~ '^\d+$' THEN -- one or more digits?
INSERT INTO transitions1 (user_id, house_id)
VALUES (_user_id::int, NEW.id); -- valid int, cast is safe
ELSE
INSERT INTO transitions1 (user_id, house_id)
VALUES (NULL, NEW.id); -- use NULL instead
RAISE WARNING 'Invalid user_id % for house_id % was reset to NULL!'
, quote_literal(_user_id), NEW.id; -- optional
END IF;
RETURN NULL; -- OK for AFTER trigger
END
$func$ LANGUAGE plpgsql;
db<>fiddle here
备注:
避免变量名与列名匹配。非常容易出错。一种流行的命名约定是在变量名前加上下划线:_user_id
.
在声明时分配以节省一个分配。请注意数据类型 text
。我们稍后会在整理出无效输入后进行转换。
尽可能避免引发/捕获异常。 The manual:
A block containing an EXCEPTION
clause is significantly more expensive
to enter and exit than a block without one. Therefore, don't use
EXCEPTION
without need.
测试有效的整数字符串。这个简单的正则表达式只允许数字(没有前导符号,没有白色 space):_user_id ~ '^\d+$'
。对于任何无效输入,我都重置为 NULL。适应您的需求。
为了调试方便,我添加了一个可选的WARNING
。
案例3.
和4.
只出现是因为自定义选项是字符串文字(类型text
),无法自动强制执行有效数据类型。
相关:
- User defined variables in PostgreSQL
- Is there a way to define a named constant in a PostgreSQL query?
除此之外,根据您的具体要求,在没有自定义选项的情况下,可能会有更优雅的解决方案来解决您的问题。也许是这样:
我想使用 SET
在 session/transaction 中记录用户的 ID,这样我可以稍后在触发器函数中使用 current_setting
访问它.基本上,我正在尝试 very similar ticket posted previously 中的选项 n2,不同之处在于我使用的是 PG 10.1。
我一直在尝试 3 种方法来设置变量:
SET local myvars.user_id = 4
,从而在事务本地设置;SET myvars.user_id = 4
,从而在session中设置;SELECT set_config('myvars.user_id', '4', false)
,这取决于最后一个参数,将是前两个选项的快捷方式。
None个在触发器中是可用的,触发器在通过current_setting
获取变量时收到NULL
。这是我设计的一个脚本来解决它(可以很容易地与 postgres docker 图像一起使用):
database=$POSTGRES_DB
user=$POSTGRES_USER
[ -z "$user" ] && user="postgres"
psql -v ON_ERROR_STOP=1 --username "$user" $database <<-EOSQL
DROP TRIGGER IF EXISTS add_transition1 ON houses;
CREATE TABLE IF NOT EXISTS houses (
id SERIAL NOT NULL,
name VARCHAR(80),
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS transitions1 (
id SERIAL NOT NULL,
house_id INTEGER,
user_id INTEGER,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
PRIMARY KEY(id),
FOREIGN KEY(house_id) REFERENCES houses (id) ON DELETE CASCADE
);
CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
DECLARE
user_id integer;
BEGIN
user_id := current_setting('myvars.user_id')::integer || NULL;
INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER add_transition1 AFTER INSERT OR UPDATE ON houses FOR EACH ROW EXECUTE PROCEDURE add_transition1();
BEGIN;
%1% SELECT current_setting('myvars.user_id');
%2% SELECT set_config('myvars.user_id', '55', false);
%3% SELECT current_setting('myvars.user_id');
INSERT INTO houses (name) VALUES ('HOUSE PARTY') RETURNING houses.id;
SELECT * from houses;
SELECT * from transitions1;
COMMIT;
DROP TRIGGER IF EXISTS add_transition1 ON houses;
DROP FUNCTION IF EXISTS add_transition1;
DROP TABLE transitions1;
DROP TABLE houses;
EOSQL
我得出的结论是该函数是在不同的事务和不同的 (?) 会话中触发的。这是可以配置的东西,以便所有内容都发生在同一上下文中吗?
不清楚您为什么要尝试将 NULL
连接到 user_id
,但这显然是问题的原因。摆脱它:
CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
DECLARE
user_id integer;
BEGIN
user_id := current_setting('myvars.user_id')::integer;
INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
注意
SELECT 55 || NULL
总是给出 NULL
.
您可以在值不存在时捕获异常 - 这是我为使其正常工作所做的更改:
CREATE OR REPLACE FUNCTION add_transition1() RETURNS TRIGGER AS $$
DECLARE
user_id integer;
BEGIN
BEGIN
user_id := current_setting('myvars.user_id')::integer;
EXCEPTION WHEN OTHERS THEN
user_id := 0;
END;
INSERT INTO transitions1 (user_id, house_id) VALUES (user_id, NEW.id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION insert_house() RETURNS void as $$
DECLARE
user_id integer;
BEGIN
PERFORM set_config('myvars.user_id', '55', false);
INSERT INTO houses (name) VALUES ('HOUSE PARTY');
END; $$ LANGUAGE plpgsql;
正确处理 customized option 的所有可能情况:
选项尚未设置
所有对其的引用都会引发 异常,包括
current_setting()
unless called with the second parametermissing_ok
. The manual:If there is no setting named
setting_name
,current_setting
throws an error unlessmissing_ok
is supplied and istrue
.选项设置为有效整数文字
选项设置为无效整数文字
选项重置(这会导致 3. 的特例)
例如,如果您使用
SET LOCAL
或set_config('myvars.user_id3', '55', true)
设置自定义选项,选项值将在交易结束时重置。它仍然 存在 ,可以引用,但它 returns 现在是一个空字符串 (''
) - 无法转换为integer
.
撇开你的demo中明显的错误不谈,你需要为所有4种情况做好准备。所以:
CREATE OR REPLACE FUNCTION add_transition1()
RETURNS trigger AS
$func$
DECLARE
_user_id text := current_setting('myvars.user_id', true); -- see 1.
BEGIN
IF _user_id ~ '^\d+$' THEN -- one or more digits?
INSERT INTO transitions1 (user_id, house_id)
VALUES (_user_id::int, NEW.id); -- valid int, cast is safe
ELSE
INSERT INTO transitions1 (user_id, house_id)
VALUES (NULL, NEW.id); -- use NULL instead
RAISE WARNING 'Invalid user_id % for house_id % was reset to NULL!'
, quote_literal(_user_id), NEW.id; -- optional
END IF;
RETURN NULL; -- OK for AFTER trigger
END
$func$ LANGUAGE plpgsql;
db<>fiddle here
备注:
避免变量名与列名匹配。非常容易出错。一种流行的命名约定是在变量名前加上下划线:
_user_id
.在声明时分配以节省一个分配。请注意数据类型
text
。我们稍后会在整理出无效输入后进行转换。尽可能避免引发/捕获异常。 The manual:
A block containing an
EXCEPTION
clause is significantly more expensive to enter and exit than a block without one. Therefore, don't useEXCEPTION
without need.测试有效的整数字符串。这个简单的正则表达式只允许数字(没有前导符号,没有白色 space):
_user_id ~ '^\d+$'
。对于任何无效输入,我都重置为 NULL。适应您的需求。为了调试方便,我添加了一个可选的
WARNING
。案例
3.
和4.
只出现是因为自定义选项是字符串文字(类型text
),无法自动强制执行有效数据类型。
相关:
- User defined variables in PostgreSQL
- Is there a way to define a named constant in a PostgreSQL query?
除此之外,根据您的具体要求,在没有自定义选项的情况下,可能会有更优雅的解决方案来解决您的问题。也许是这样: