表名称作为PostgreSQL函数参数
我想在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
参数来简化function。 您可以直接selectdynamicSQL的结果并完成。 不需要额外的variables和代码。 -
EXISTS
确实是你想要的,我把它保留在我的查询中。 有很多种方法可以做到这一点。 -
你似乎想要一个整数 ,所以我把
boolean
结果从EXISTS()
为integer
,这正是你所拥有的。 我会返回布尔值 。 -
我使用对象标识符types
regclass
作为_tbl
inputtypes。 这一切都quote_ident(_tbl)
或format('%I', _tbl)
会做,但更好,因为:-
它也可以防止SQL注入 。
-
..如果表名无效/不存在/对当前用户不可见,则立即失败并更优雅。
-
..它适用于模式限定的表名,其中一个明确的
quote_ident(_tbl)
或format(%I)
将失败,因为他们不能解决歧义。
-
-
我仍然使用
format()
,因为它简化了语法(并演示了它的使用方式),但使用%s
而不是%I
。 对于简单的例子,我们可以直接连接:EXECUTE 'SELECT (EXISTS (SELECT 1 FROM ' || _tbl || ' WHERE id = 1))::int'
-
不需要表格限定
id
列。
用PostgreSQL 9.1testing format()
至less需要那个版本。
这就是为什么你总是逃避用户input的dynamicSQL正确:
用于SQL注入的SQL Fiddle演示
不要这样做。
这就是答案。 这是一个可怕的反模式。 它的目的是什么? 如果客户端知道要从中获取数据的表,则SELECT FROM ThatTable
! 如果你已经devise好你的数据库,那么你可能devise错了。 如果数据访问层需要知道表中是否存在一个值,那么在该代码中执行dynamicSQL部分非常简单。 把它推入数据库并不好。
我有一个想法:让我们在电梯里安装一个设备,你可以input你想要的楼层数。 然后当您按下“开始”button时,会将机械手移到正确的button上,然后按下。 革命!
显然,我的回答太短,解释不清,所以我正在修复这个缺陷。
我无意嘲笑。 我愚蠢的电梯的例子是我能想象的最好的设备,用来简洁地指出问题中提出的技术的缺陷。 该技术增加了一个完全没有用处的间接层,并且使用一个强大且易于理解的DSL(SQL)将来自调用者空间的表名select不必要地移动到使用难懂/奇怪的服务器端SQL代码的混合中。
这种通过将查询构build逻辑移动到dynamicSQL中的责任分割使代码更难理解。 它破坏了一个完全合理的惯例(如何SQL查询select要select什么)的自定义代码的名称充满了可能的错误。
-
dynamicSQL提供了很难在前端代码或后端代码中单独识别的SQL注入的可能性(必须一起检查它们才能看到)。
-
存储过程和function可以访问SP /function所有者有权访问的资源,但调用者不能访问。 据我所知,当你使用生成dynamicSQL的代码并运行它时,数据库将在调用者的权限下执行dynamicSQL。 这意味着你根本不能使用特权对象,或者你必须打开所有的客户端,增加对特权数据的潜在攻击的表面积。 在创build时将SP /函数设置为始终以特定用户身份运行(在SQL Server中,
EXECUTE AS
)可能会解决该问题,但会使事情更加复杂。 这使得dynamicSQL成为一个非常诱人的攻击vector,从而加剧了前面提到的SQL注入的风险。 -
当开发人员必须了解应用程序代码在修改或修正错误时所做的工作时,他会发现要执行确切的SQL查询非常困难。 可以使用SQL事件探查器,但这需要特殊权限,并且可能会对生产系统产生负面的性能影响。 执行的查询可以由SPlogging,但是这无疑会增加复杂性(维护新表,清除旧数据等),并且完全不明显。 事实上,一些应用程序的架构使得开发人员没有数据库证书,因此他几乎不可能真正看到提交的查询。
-
当发生错误时,比如当你尝试select一个不存在的表时,你将会从数据库中得到一条消息“无效的对象名”。 无论您是在后端还是数据库中编写SQL,都会发生同样的情况,但不同之处在于,一些尝试排除系统故障的开发人员不得不将其深入到另一个洞中,问题实际上是存在的,深入了解这个难题程序,试图找出问题所在。 日志不会显示“GetWidget中的错误”,它将显示“OneProcedureToRuleThemAllRunner中的错误”。 这种抽象只会让你的系统变得更糟 。
下面是基于参数的伪C#切换表名称中一个更好的示例:
string sql = string.Format("SELECT * FROM {0};", escapeSqlIdentifier(tableName)); results = connection.Execute(sql);
这个例子中我完全没有提到其他技术中的每一个缺陷。
向存储过程提交表名没有任何目的,没有好处,也没有可能的改进。
在plpgsql代码中,必须使用EXECUTE语句来查询表名或列来自variables。 当dynamic生成query
时, IF EXISTS (<query>)
结构也是不允许的。
这是你的function,解决这两个问题:
CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer AS $$ DECLARE v int; BEGIN EXECUTE 'select 1 FROM ' || quote_ident(param) || ' WHERE ' || quote_ident(param) || '.id = 1' INTO v; IF v THEN return 1; ELSE return 0; END IF; END; $$ LANGUAGE plpgsql;
第一个实际上并不是你所说的“工作”,只是它不会产生错误。
尝试SELECT * FROM quote_ident('table_that_does_not_exist');
,你会看到为什么你的函数返回1:select是返回一列(名为quote_ident
)与一行(variables$1
或在这种特殊情况下table_that_does_not_exist
)的表。
你想要做的事情需要dynamicSQL,这实际上是quote_*
函数的用意。
我有PostgreSQL的9.4版本,我总是使用这个代码:
CREATE FUNCTION add_new_table(text) RETURNS void AS $BODY$ begin execute 'CREATE TABLE ' || $1 || '( item_1 type, item_2 type )'; end; $BODY$ LANGUAGE plpgsql
接着:
SELECT add_new_table('my_table_name');
这对我有好处。
注意! 上面的例子是显示“如果我们想要在查询数据库期间保持安全,怎么样”:P