如何在触发功能中使用变量设置?

时间:2018-08-16 15:46:45

标签: postgresql global-variables plpgsql postgresql-triggers

我想使用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

我得出的结论是,该函数是在不同的事务和不同的(?)会话中触发的。这是可以配置的东西,以便所有事情都在同一上下文中发生吗?

3 个答案:

答案 0 :(得分:6)

It is not clear why you are trying to concat NULL to user_id but it is obviously the cause of the problem. Get rid of it:

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;

Note that

SELECT 55 || NULL

always gives NULL.

答案 1 :(得分:5)

正确处理 customized option 的所有可能情况:

  1. 选项尚未设置

    对其的所有引用都会引发例外,包括current_setting(),除非使用第二个参数 missing_ok 进行调用。 The manual:

      

    如果没有名为 setting_name 的设置,除非提供了 current_setting 且{{1 }}。

  2. 选项设置为有效的整数文字

  3. 选项设置为无效的整数文字

  4. 选项重置(这会烧成 3的特殊情况。

    例如,如果您使用missing_oktrue设置了自定义选项,则在交易结束时将重置选项值。它仍然存在,可以被引用,但是现在返回一个空字符串(SET LOCAL)-无法将其强制转换为set_config('myvars.user_id3', '55', true)

除了演示中的明显错误外,您还需要为所有4种情况做准备。所以:

''

db <>提琴here

注意:

  • 避免与列名匹配的变量名。非常容易出错。一种流行的命名约定是在变量名前加上下划线:integer

  • 在声明时分配以保存一个分配。请注意数据类型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; 。我们将在筛选出无效输入之后进行投射。

  • 在可能的情况下避免引发/捕获异常 The manual:

      

    包含_user_id子句的代码块要贵得多   进入和退出比没有一个块。因此,请勿使用   text,不需要。

  • 测试有效的整数字符串。这个简单的正则表达式只允许数字(没有前导符号,没有空格):EXCEPTION。对于任何无效的输入,我都会重置为NULL。适应您的需求。

  • 为方便调试,我添加了一个可选的EXCEPTION

  • 情况_user_id ~ '^\d+$'WARNING的出现仅是因为自定义选项是字符串文字(类型3.),有效数据类型无法自动执行。

相关:

此外,根据您的确切要求,对于没有定制选项的尝试,可能会有更优雅的解决方案。也许是这样:

答案 2 :(得分:4)

当值不存在时,您可以捕获异常-这是我为使其正常工作所做的更改:

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;