触发器中的动态函数调用



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

从逻辑上讲,我在想:

  1. 在自己的表中定义函数调用(即名称和静态参数)
  2. 将这些静态函数调用定义与另一个表中的记录相关联(插入该表将触发动态函数调用)
  3. 插入后,使用触发器查询静态函数定义表,并使用获得的静态参数执行获得的函数

到目前为止,我得出的结论是:

要动态调用的可用函数池

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表中的行引用的函数确实存在,并且定义的参数是有效的(或者至少可以被强制为有效的)。

我正在使用PostgreSQL9.4.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会失败,因为在约束完成计算之前,函数签名无效,这同样可靠。这反映在相应的错误消息中。

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

  • 如何检查给定模式中是否存在表

  • DROP FUNCTION而不知道参数的数量/类型?

作品:

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

失败,出现异常:

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

SQL Fiddle

我还将考虑对(func, args)UNIQUE约束。请注意,同一args可能有多个有效的文本表示。以下是发现隐藏重复项的快速检查:

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

不能在唯一索引中使用此表达式,因为regtype的强制转换不是IMMUTABLE。你必须耍花招。。。