我想使用SET
在会话/事务中记录用户的id,所以我以后可以使用current_setting
在触发器函数中访问它。基本上,我正在尝试从very similar ticket posted previously选择n2,区别在于我正在使用PG 10.1。
我一直在尝试3种设置变量的方法:
SET local myvars.user_id = 4
,从而在交易中在当地设置;SET myvars.user_id = 4
,从而在会议中设定它;SELECT set_config('myvars.user_id', '4', false)
,取决于最后一个参数,将是前两个选项的快捷方式。它们都不可用于触发器,当通过NULL
获取变量时接收current_setting
。这是我设计的用于对其进行故障排除的脚本(可以很容易地与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
我得出的结论是,该函数是在不同的事务和不同的(?)会话中触发的。这是可以配置的东西,所有这些都发生在同一个上下文中吗?
正确处理customized option的所有可能情况:
current_setting()
调用,否则所有对它的引用都会引发异常,包括missing_ok
。 The manual:
如果没有名为setting_name
的设置,current_setting
会抛出错误,除非提供missing_ok
并且是true
。SET LOCAL
或set_config('myvars.user_id3', '55', true)
设置自定义选项,则会在事务结束时重置选项值。它仍然存在,可以被引用,但它现在返回一个空字符串(''
) - 它不能被强制转换为integer
。除了你的演示中明显的错误,你需要准备所有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 <>小提琴here
笔记:
_user_id
。text
。在整理出无效输入后,我们将在稍后进行投射。EXCEPTION
子句的块比没有块的块更容易进入和退出。因此,不要使用EXCEPTION
。_user_id ~ '^\d+$'
。我为任何无效输入重置为NULL。适应您的需求。WARNING
,以方便您的调试。3.
和4.
的情况,因为自定义选项是字符串文字(类型为text
),无法自动强制执行有效数据类型。有关:
除此之外,根据您的具体要求,如果没有自定义选项,可能会有更优雅的解决方案。也许这个:
目前尚不清楚你为什么试图将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;