2014-12-05 95 views
2

我想写一个触发器函数,将值输入到单独的子表中,但是我收到了一个我以前没见过的错误。创建子表插入触发器返回混淆错误

下面是一个例子设置:

-- create initial table 
CREATE TABLE public.testlog(
    id serial not null, 
    col1 integer, 
    col2 integer, 
    col3 integer, 
    name text 
); 

-- create child table 
CREATE TABLE public.testlog_a (primary key(id)) INHERITS(public.testlog); 

-- make trigger function for insert 
CREATE OR REPLACE FUNCTION public.test_log() RETURNS trigger AS 
$$ 
DECLARE 
    qry text; 
BEGIN 
    qry := 'INSERT INTO public.testlog_' || NEW.name || ' SELECT ($1).*'; 

    EXECUTE qry USING NEW.*; 

    RETURN OLD; 
END 
$$ 
LANGUAGE plpgsql VOLATILE SECURITY DEFINER; 

-- add function to table 
CREATE TRIGGER test_log_sorter BEFORE INSERT 
ON public.testlog FOR EACH ROW 
EXECUTE PROCEDURE public.test_log(); 

和查询:

INSERT INTO public.testlog (col1, col2, col3, name) values (1, 2, 3, 'a'); 

错误消息:

[Err] ERROR: query "SELECT NEW.*" returned 5 columns 
CONTEXT: PL/pgSQL function test_log() line 7 at EXECUTE statement 

5列正是我要寻找它返回,所以显然有一些我不理解,但错误消息似乎没有任何意义。

有人可以解释为什么我得到这个?

回答

2

您的解决方案修复了行式NEW变量的传递。然而,你的代码中有一个偷偷摸摸的SQL注入漏洞,在SECURITY DEFINER函数中这是特别危险的。用户输入必须永不转换为未经转义的SQL代码。

消毒这样的:

CREATE OR REPLACE FUNCTION trg_test_log() 
    RETURNS trigger AS 
$$ 
BEGIN 
    EXECUTE 'INSERT INTO public.' || quote_ident('testlog_' || NEW.name) 
     || ' SELECT ($1).*' 
    USING NEW; 

    RETURN NULL; 
END 
$$ 
LANGUAGE plpgsql SECURITY DEFINER; 

另外:

  • OLD未在INSERT触发定义。
  • 你不需要一个变量。 plpgsql中的赋值相对比较昂贵。
+0

非常好的一点,“名称”列是一个占位符,因为我打算使用来自一组整数的子选择来指定子表,注入的想法甚至没有给我留下任何印象。也就是说,你永远不能过于小心这些事情,并且总有可能我会不假思索地重新使用易受攻击的代码并且被蜇伤。 – Lucas 2014-12-07 12:29:37

+0

@Lucas:“整数”列当然是安全的,但这个例子使用的是“text”,答案是针对大众的。无论如何,就像你说的那样......顺便说一下,我修正了我滑过的格式错误的“VALUES”表达。 – 2014-12-07 19:26:57

1

EXECUTE qry USING NEW.*传递NEW.*作为查询的参数。由于NEW.*返回五列,查询应具有$1,$2,$3,$4$5以便绑定五列。

您期待一个参数($1),其中有五列。我相信,如果你改变了该行

EXECUTE qry USING NEW;

,你希望它会工作。

1

至于罗伯特·莱夫科维茨M.”回应,答案就是这么简单:NEW,而不是NEW.*

CREATE OR REPLACE FUNCTION public.test_log() RETURNS trigger AS 
$$ 
DECLARE 
    qry text; 
BEGIN 
    qry := 'INSERT INTO public.testlog_' || NEW.name || ' SELECT ($1).*'; 

    EXECUTE qry USING NEW; 

    RETURN OLD; 
END 
$$ 
LANGUAGE plpgsql VOLATILE SECURITY DEFINER 
COST 100; 

感谢。