我想在Postgres函数中传递一个表名作为参数。我试过这个代码:
CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer AS $$ BEGIN IF EXISTS (select * from quote_ident($1) where quote_ident($1).id=1) THEN return 1; END IF; return 0; END; $$ LANGUAGE plpgsql; select some_f('table_name');
我得到这个:
ERROR: Syntax error at or near "." LINE 4: ...elect * from quote_ident($1) where quote_ident($1).id=1)... ^ ********** Error ********** ERROR: Syntax error at or near "."
这里是我得到的错误改变为这个select * from quote_ident($ 1)tab where tab.id = 1:
ERROR: column tab.id does not exist LINE 1: ...T EXISTS (select * from quote_ident($1) tab where tab.id...
可能,quote_ident($ 1)工作,因为没有where quote_ident($ 1).id = 1部分,我得到1,这意味着某事被选中。为什么第一个quote_ident($ 1)可以工作,第二个不同时工作?这怎么可能解决?
这可以进一步简化和改进:
CREATE OR REPLACE FUNCTION some_f(_tbl regclass,OUT result integer) AS $func$ BEGIN EXECUTE format('SELECT (EXISTS (SELECT 1 FROM %s WHERE id = 1))::int',_tbl) INTO result; END $func$ LANGUAGE plpgsql;
SELECT some_f('myschema.mytable'); -- would fail with quote_ident()
要么:
SELECT some_f('"my very uncommon table name"')
要点
>使用OUT参数简化功能。您可以直接选择动态sql的结果并完成。不需要额外的变量和代码。
> EXISTS确实是你想要的,我把它保存在我的查询。有多种方法可以做到这一点。
>你似乎想要一个整数回,所以我把布尔结果从EXISTS()转换为整数,这产生了你所拥有的。我会返回boolean。
>我使用对象标识符类型regclass
作为_tbl的输入类型。这一切都quote_ident(_tbl)
或format('%I',_tbl)
一切,但更好,因为:
> ..它也阻止sql注入。
> ..如果表名无效/不存在/对当前用户不可见,它立即失败并更优雅。
> ..它与模式限定的表名称,其中纯quote_ident(_tbl)或格式(%I)将失败,因为他们不能解决歧义。
>我仍然使用format()
,因为它简化了语法(并演示如何使用),但使用%s而不是%I。对于简单的例子,我们可以只是连接:
EXECUTE 'SELECT (EXISTS (SELECT 1 FROM ' || _tbl || ' WHERE id = 1))::int'
>不需要对id列进行表格限定。
用Postgresql 9.1测试。 format()至少需要该版本。
这就是为什么你总是正确转义动态sql的用户输入:
SQL Fiddle demo for SQL injection