触发器中的动态函数调用

时间:2015-02-27 16:05:14

标签: postgresql function database-design plpgsql check-constraints

在我的用例中,我需要能够在插入表后使用静态参数执行动态(预定义)函数。

逻辑上我在想:

  1. 在自己的表中定义函数调用(即名称和静态args)。
  2. 将这些静态函数调用定义与另一个表中的记录相关联(插入将触发动态函数调用)。
  3. 插入后,使用触发器查询静态函数定义表,并使用获取的静态参数执行获取的函数。
  4. 这是我到目前为止所得到的:

    可动态调用的函数池

    create function f1(num int) returns boolean as $$
      -- ...
    $$ language plpgsql;
    
    create function f2(name text, age int) returns boolean as $$
      -- ...
    $$ language plpgsql;
    
    create function f3(first_name text, last_name text) returns boolean as $$
      -- ...
    $$ language plpgsql;
    

    函数调用

    create table function_invocations(
      id integer not null,
      name text not null,
      args text not null, -- (not sure if this should be an array)
      primary key(id)
    );
    
    create function verify_function_exists() returns trigger as $$
      -- query information_schema to verify there is
      -- a function with specified name and that
      -- specified args satisfy function's
      -- signature.
    $$ language plpgsql;
    
    create trigger function_exists_trig
      before insert on function_invocations
      for each row
      execute procedure verify_function_exists();
    

    其插入导致动态函数调用的表

    create table my_data(
      id integer not null,
      function_invocation_id integer not null,
      -- etc.
      primary key(id),
      foreign key(function_invocation_id) references function_invocations(id)
    );
    
    create function exec_dynamic_function() returns trigger as $$
      -- retrieve the function name and args from
      -- function_definitions and execute the
      -- function specified by `name` with the
      -- provided `args`.  
    $$ language plpgsql;
    
    create trigger function_invocations_trig
      after update on my_data
      for each row
      execute procedure exec_dynamic_function();
    

    这是关于完成任务的正确方法吗?来自JS背景我很可能会以错误的方式思考它,即

    var api = {
      my_func: function (age, name) {
        console.log('%d %s', age, name);
      }
    };
    
    var fn = 'my_func';
    var args = [50, 'john'];
    
    api[fn].apply(api, args);
    

    我主要关注的是如何确保function_invocations表中的行引用的函数实际存在且定义的args是有效的(或者至少可以强制为有效)。

    我正在使用PostgreSQL 9.4.1

1 个答案:

答案 0 :(得分:1)

以下是带有简单CHECK约束而不是触发器的解决方案:

CREATE TABLE func (
  func_id serial PRIMARY KEY
, func text NOT NULL
, args text NOT NULL
, CHECK ((func || '(' || args || ')')::regprocedure IS NOT NULL)
);

CHECK约束比任何可能的触发器解决方案更简单,更快速,更可靠。此变体适用于任何现代Postgres版本。

在约束可以完成评估之前,转换为regprocedure失败了无效函数signatues - 这同样可靠。这反映在相应的错误消息中。

在Postgres 9.4+中使用新的to_regprocedure()而不是强制转换,这不会引发异常。您会从CHECK约束中获得异常。更多(最后一章):

使用:

INSERT INTO func(func, args) VALUES ('substring','text, int');

失败,但有例外:

INSERT INTO func(func, args) VALUES ('nonexistant','text, int');

SQL Fiddle.

我还会在UNIQUE上考虑(func, args)约束。请注意,同一args可以有多个有效的文本表示形式。这是一个快速检查,以发现隐藏的重复:

SELECT func, string_to_array(args, ', ')::regtype[], count(*)
FROM   func
GROUP  BY 1, 2
HAVING count(*) > 1;

您无法在唯一索引中使用此表达式,因为regtype的强制转换不是IMMUTABLE。你必须玩弄技巧......