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

Chu*_*ckE 12 postgresql global-variables plpgsql postgresql-triggers

我想在会话/事务中记录用户的id,使用SET,所以我以后可以在触发器函数中使用它来访问它current_setting.基本上,我正在尝试从之前发布非常相似的票证中选择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
Run Code Online (Sandbox Code Playgroud)

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

kli*_*lin 7

目前尚不清楚为什么要尝试连接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;
Run Code Online (Sandbox Code Playgroud)

注意

SELECT 55 || NULL
Run Code Online (Sandbox Code Playgroud)

总是给NULL.

  • @ChuckE PS:我感觉你混淆了 SQL 字符串连接运算符“||”和 C 逻辑“或”运算符“||”... (2认同)

Erw*_*ter 6

正确处理自定义选项的所有可能情况:

  1. 选项尚未设置

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

    如果没有命名设置setting_name,current_setting除非missing_ok提供错误,否则抛出错误true.

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

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

  4. 选项复位(其中被烧毁到的特殊情况3)

    例如,如果使用SET LOCAL或设置自定义选项,set_config('myvars.user_id3', '55', true)则会在事务结束时重置选项值.它仍然存在,可以引用,但它返回一个空字符串now('') - 无法转换为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;
Run Code Online (Sandbox Code Playgroud)

db <> 在这里小提琴

笔记:

  • 避免使用与列名匹配的变量名.非常容易出错.一种流行的命名约定是使用下划线添加变量名称:_user_id.

  • 在声明时分配以保存一个作业.请注意数据类型text.在整理出无效输入后,我们将在稍后进行投射.

  • 如果可能,避免引发/捕获异常.手册:

    包含EXCEPTION子句的块比没有子块的块进入和退出要贵得多.因此,请勿在不需要的情况下使用 EXCEPTION.

  • 测试有效的整数字符串.这个简单的正则表达式只允许数字(没有领先的标志,没有空格)_user_id ~ '^\d+$'.我为任何无效输入重置为NULL.适应您的需求.

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

  • 只会出现个案3.,4.因为自定义选项是字符串文字(类型text),无法自动强制执行有效数据类型.

有关:

除此之外,根据您的具体要求,如果没有自定义选项,可能会有更优雅的解决方案.也许这个:


小智 5

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

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;
Run Code Online (Sandbox Code Playgroud)