postgresql 如何在触发函数中使用变量设置?

jxct1oxe  于 2022-11-04  发布在  PostgreSQL
关注(0)|答案(3)|浏览(263)

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

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

4xrmg8kj

4xrmg8kj1#

正确处理 * customized option * 的所有可能情况:
1.尚未设置选项
所有对它的引用都会引发异常,包括current_setting(),除非使用第二个参数 * missing_ok * 调用。手册:
如果没有名为 * setting_name * 的设定,则current_setting会掷回错误,除非提供了 * missing_ok *,而且是true
1.选项设置为有效的整数文本
1.选项设置为无效的整数文字
1.选项重置(可分解为特殊情况 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〈〉小提琴

备注:

  • 避免使用与列名匹配的变量名。这很容易出错。一个流行的命名约定是在变量名前面加上下划线:_user_id .
  • 在声明时赋值以保存一个赋值。注意数据类型text。我们将在稍后对无效输入进行分类后进行强制转换。
    • 如果可能 *,避免引发/捕获异常。手册:

一个包含EXCEPTION子句的块比一个没有EXCEPTION子句的块的进入和退出开销要大得多。因此,如果没有必要,不要使用EXCEPTION

  • 测试有效的整数字符串。此简单正则表达式只允许数字(无前导符号,白色):_user_id ~ '^\d+$'。对于任何无效的输入,我都重置为NULL。根据您的需要进行调整。
  • 我添加了一个可选的WARNING,以方便您进行调试。
  • 3.4.的情况仅在自定义选项为字符串文字(类型text)时出现,无法自动强制有效的数据类型。

相关:

除此之外,根据您的具体需求,可能还有更优雅的解决方案来解决您试图在没有定制选项的情况下执行的操作。

  • 在Postgres中获取当前用户OID的最快方法是什么?
lf5gs5x2

lf5gs5x22#

不清楚为什么要将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

4urapxun

4urapxun3#

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

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;

相关问题