【问题标题】:How to use variable settings in trigger functions?如何在触发函数中使用变量设置?
【发布时间】:2019-01-23 15:23:43
【问题描述】:

我想使用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),取决于最后一个参数,将是前两个选项的快捷方式。

它们都不能在触发器中使用,当通过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

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

【问题讨论】:

    标签: postgresql global-variables plpgsql postgresql-triggers


    【解决方案1】:

    妥善处理customized option的所有可能情况:

    1. 尚未设置选项

      对它的所有引用都会引发异常,包括current_setting(),除非使用第二个参数missing_ok 调用。 The manual:

      如果没有名为 setting_name 的设置,current_setting 会抛出错误,除非提供了 missing_ok 并且是 true

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

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

    4. 选项重置(烧毁为 3. 的特殊情况)

      例如,如果您使用SET LOCALset_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。我们将在整理出无效输入后进行投射。

    • 避免引发/捕获异常如果可能The manual:

      包含EXCEPTION 子句的块明显更昂贵 进出比没有一个街区。因此,请勿使用 EXCEPTION 不需要。

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

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

    • 3.4. 的情况只会出现,因为自定义选项是字符串文字(类型 text),无法自动强制执行有效的数据类型。

    相关:

    除此之外,根据您的具体要求,在没有自定义选项的情况下,您可能会尝试做更优雅的解决方案。也许是这样:

    【讨论】:

    • 您的回复是最全面的,尽管我选择了@garysieling 的回复。但是你触及了大部分重要的事情;一开始,myvars.user_id 为 NULL,使用SET LOCAL 后是否在事务结束时重置为'',我必须考虑所有情况。
    【解决方案2】:

    目前尚不清楚您为什么要尝试将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

    【讨论】:

    • 但如何设置等效项?当设置不可用时,我想取回 NULL。
    • @ChuckE user_id := current_setting('myvars.user_id', true)::integer;
    • @ChuckE PS:我感觉你混淆了 SQL 字符串连接运算符 || 和 C 逻辑“或”运算符 ||...
    【解决方案3】:

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

    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;
    

    【讨论】:

    • 虽然我没有选择它,但您的示例为我的最终实现提供了蓝图。谢谢一堆!
    猜你喜欢
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-17
    • 2013-08-09
    • 1970-01-01
    相关资源
    最近更新 更多