Joh*_*Doe 69 postgresql function dynamic-sql plpgsql identifier
我想在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');
Run Code Online (Sandbox Code Playgroud)
我得到了这个:
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 "."
Run Code Online (Sandbox Code Playgroud)
以下是更改为此时出现的错误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...
Run Code Online (Sandbox Code Playgroud)
可能,quote_ident($1)
有效,因为没有where quote_ident($1).id=1
我得到的部分1
,这意味着选择了一些东西.为什么第一个quote_ident($1)
工作和第二个工作不能同时工作?这怎么可以解决?
Erw*_*ter 104
这可以进一步简化和改进:
CREATE OR REPLACE FUNCTION some_f(_tbl regclass, OUT result integer) AS
$func$
BEGIN
EXECUTE format('SELECT (EXISTS (SELECT FROM %s WHERE id = 1))::int', _tbl)
INTO result;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
使用模式限定名称调用(见下文):
SELECT some_f('myschema.mytable'); -- would fail with quote_ident()
Run Code Online (Sandbox Code Playgroud)
要么:
SELECT some_f('"my very uncommon table name"');
Run Code Online (Sandbox Code Playgroud)
使用OUT
参数来简化功能.您可以直接在其中选择动态SQL的结果并完成.无需额外的变量和代码.
EXISTS
完全符合你的要求.你true
如果行存在,或false
以其他方式.有多种方法可以做到这一点,EXISTS
通常效率最高.
你似乎想要一个整数,所以我将boolean
结果从EXISTS
to转换integer
,这产生了你所拥有的.我会返回布尔值.
我使用对象标识符类型regclass
作为输入类型_tbl
.,做一切quote_ident(_tbl)
还是format('%I', _tbl)
会做,但更好的,这是因为:
..它也可以防止SQL注入.
..如果表名无效/不存在/当前用户不可见,它会立即失败并且更优雅.(regclass
参数仅适用于现有表.)
..它适用于模式限定的表名,其中普通quote_ident(_tbl)
或format(%I)
失败,因为它们无法解决歧义.您必须分别传递和转义模式和表名.
我仍然使用format()
,因为它简化了语法(并演示了如何使用它),但%s
代替%I
.通常,查询更复杂,因此format()
更有帮助.对于简单的例子,我们也可以连接:
EXECUTE 'SELECT (EXISTS (SELECT FROM ' || _tbl || ' WHERE id = 1))::int'
Run Code Online (Sandbox Code Playgroud)id
当FROM
列表中只有一个表时,无需对列进行表限定.在这个例子中没有歧义.(动态)SQL命令里面EXECUTE
有一个单独的作用域,函数变量或参数在那里是不可见的 - 而不是函数体中的普通SQL命令.
使用PostgreSQL 9.1进行测试.format()
至少需要该版本.
这就是为什么你总是正确地转义动态SQL的用户输入:
Eri*_*ikE 12
不要这样做.
这就是答案.这是一种可怕的反模式.它有什么用途?如果客户端知道它想要数据的表,那么SELECT FROM ThatTable
!如果您以某种方式设计数据库,那么您可能设计错了.如果您的数据访问层需要知道表中是否存在值,那么在该代码中执行动态SQL部分非常容易.将它推入数据库并不好.
我有一个想法:让我们在电梯内安装一个设备,您可以在其中输入您想要的楼层数.然后当您按下"Go"时,它会将机械手移到所需地板的正确按钮上并按下它.革命!
显然我的答案太简短,所以我正在修复这个缺陷的更多细节.
我无意嘲笑.我愚蠢的电梯示例是我能想象的最好的设备,用于简洁地指出问题中提出的技术缺陷.这种技术增加了一个完全无用的间接层,并且使用一个强大且易于理解的DSL(SQL),使用晦涩/奇怪的服务器端SQL代码,不必要地将表名选择从调用者空间移动到混合中.
通过将查询构造逻辑移动到动态SQL中,这种责任分裂使代码更难理解.它破坏了一个完全合理的约定(SQL查询如何选择要选择的内容)在自定义代码的名称中充满了潜在的错误.
动态SQL提供了SQL注入的可能性,难以在前端代码或后端代码中单独识别(必须一起检查它们才能看到这一点).
存储过程和函数可以访问SP /函数所有者有权访问的资源,但调用者没有.据我所知,当您使用生成动态SQL并运行它的代码时,数据库会根据调用者的权限执行动态SQL.这意味着您要么根本无法使用特权对象,要么必须向所有客户端打开它们,从而增加了对特权数据的潜在攻击的表面区域.在创建时将SP /函数设置为始终作为特定用户运行(在SQL Server中EXECUTE AS
)可以解决该问题,但会使事情变得更复杂.通过使动态SQL成为一个非常诱人的攻击向量,这加剧了前一点中提到的SQL注入的风险.
当开发人员必须了解应用程序代码正在做什么以修改它或修复错误时,他会发现很难获得正在执行的SQL查询.可以使用SQL事件探查器,但这需要特殊权限,并且可能对生产系统产生负面的性能影响.执行的查询可以由SP记录,但这会无缘无故地增加复杂性(维护新表,清除旧数据等)并且完全不明显.实际上,某些应用程序的架构使得开发人员没有数据库凭据,因此他几乎不可能真正看到提交的查询.
发生错误时,例如当您尝试选择不存在的表时,您将从数据库中获得一条"无效对象名称"的消息.无论你是在后端还是在数据库中编写SQL,情况都会完全相同,但不同的是,一些正在尝试对系统进行故障排除的可怜的开发人员必须将一个级别深入到另一个洞穴之下的另一个洞穴中.问题确实存在,深入了解"尽力而为"的奇迹程序,并试图找出问题所在.日志不会显示"GetWidget中的错误",它将显示"OneProcedureToRuleThemAllRunner中的错误".这种抽象只会让你的系统变得更糟.
这是基于参数的伪C#切换表名称中更好的示例:
string sql = string.Format("SELECT * FROM {0};", EscapeSqlIdentifier(tableName));
results = connection.Execute(sql);
Run Code Online (Sandbox Code Playgroud)
我在这个例子中完全没有提到我用其他技术提到的每个缺陷.
向存储过程提交表名没有任何目的,没有好处,没有可能的改进.
在plpgsql代码中,EXECUTE语句必须用于表名或列来自变量的查询.另外,IF EXISTS (<query>)
当构建体是不允许的query
是动态生成的.
这是你的功能,修复了两个问题:
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;
Run Code Online (Sandbox Code Playgroud)
小智 9
我知道这是一个旧线程,但我最近在尝试解决同样的问题时遇到了它 - 就我而言,对于一些相当复杂的脚本。
将整个脚本变成动态 SQL 并不理想。这是一项乏味且容易出错的工作,并且您失去了参数化的能力:参数必须插入到 SQL 中的常量中,这会对性能和安全性产生不良后果。
如果您只需要从表中进行选择,这里有一个简单的技巧,可以让您保持 SQL 完整 - 使用动态 SQL 创建临时视图:
CREATE OR REPLACE FUNCTION some_f(_tbl varchar) returns integer
AS $$
BEGIN
drop view if exists myview;
execute format('create temporary view myview as select * from %s', _tbl);
-- now you can reference myview in the SQL
IF EXISTS (select * from myview where myview.id=1) THEN
return 1;
END IF;
return 0;
END;
$$ language plpgsql;
Run Code Online (Sandbox Code Playgroud)