我正在尝试使用服务器编程接口(SPI)从为PostgreSQL构建的C扩展名执行SQL查询。该查询应创建一个具有大量表的新架构。 (基本上,它应该为用户设置一个工作空间。)但是由于用户应该能够创建多个工作空间,因此在编写脚本时我不知道架构名称。因此,我需要一种在运行时提供此方法的方法。但是我无法正常工作。
我正在尝试通过使用SPI_execute_with_args
来做到这一点,因为documentation声明以下内容:
SPI_execute_with_args
执行可能包含引用的命令 外部提供的参数。命令文本是指 参数为$n
,并且调用为每个参数指定数据类型和值 这样的符号。read_only
和count
的解释与SPI_execute
。与
SPI_execute
相比,此例程的主要优点是 数据值可以轻松插入命令中 引用/转义,从而大大降低了SQL注入的风险 攻击。
SQL脚本如下所示(如果我用真实的架构名称手动替换$1
并将其作为普通脚本运行,那么一切都会正常进行):
CREATE SCHEMA $1;
ALTER SCHEMA $1 OWNER TO some_user;
CREATE FUNCTION $1.foo() ...
CREATE TABLE $1.bar ...
...
但是现在我想从C代码运行它,并且由于该文档缺少有关SPI的任何有效示例,因此我不得不四处搜寻以寻找可以进一步指导我的内容。我在SO上找到了this example,其功能如下:
... Datum foo(PG_FUNCTION_ARGS) { int ret; Datum args[1]; Oid argtypes[1] = { INT4OID }; Datum result; bool isnull; SPI_connect(); args[0] = PG_GETARG_INT32(0); /* ensure expected result type by casting */ ret = SPI_execute_with_args("SELECT ($1 + 10)::int", 1, argtypes, args, NULL, true, 1); Assert(SPI_processed == 1); result = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); Assert(!isnull); SPI_finish(); PG_RETURN_DATUM(result); } ...
这可以正常工作(用输入的数字代替$1
作为参数)。
但是,一旦我开始对其进行修改以与自己的查询一起使用,一切都会中断。我什至无法使其仅与查询的第一行一起使用。
据记录,我也尝试过运行一个简单的SELECT '$1'
查询并将其替换为各种变量。但是,除了该示例以外,其他任何操作都没有。服务器崩溃,或者在$1
返回无效的语法,或者仅返回$1
作为答案。
如果我是对的,那么在何处和什么似乎很重要,您要用{strong>替换$1
。那个SPI不仅在$1
上进行“查找并替换”?
在测试各种变量类型时,我尝试了一些不同的OID:,例如:ANYOID
,CSTRINGOID
,CHAROID
,REGNAMESPACEOID
,TEXTOID
等。而且我已经尝试将变量作为纯字符数组以及指向用SPI_palloc()
或palloc()
分配的文本块的指针发送。但是没有成功...
我从发现的示例和文档中汇总的示例代码:
PG_FUNCTION_INFO_V1(foobar);
Datum foobar(PG_FUNCTION_ARGS)
{
Datum arguments[1];
Oid argument_types[1] = { ANYOID };
Datum result;
bool isnull;
arguments[0] = "some_text";
SPI_connect();
SPI_execute_with_args("SELECT '$1'", 1, argument_types, arguments, NULL, false, 0);
result = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
SPI_finish();
PG_RETURN_DATUM(result);
}
运行此代码时,我得到以下结果:
SELECT foobar();
foobar
--------
$1
(1 row)
我不确定这是执行此操作的最佳方法,但是即使不是,也希望了解更多此SPI函数的工作原理,因为我将进一步需要它。项目。
有人对此有任何可行的例子吗?或者可以将我推向正确的方向?
答案 0 :(得分:1)
您的$1
中的SELECT '$1'
位于单引号内,因此它是字符串文字而不是参数。
改为使用以下内容:
SELECT $1
请注意,您可以使用参数,也可以使用相同类型的文字,但是不能将参数用于表或列名之类的标识符。
如果在这样的位置需要变量,则必须使用snprintf
来构造查询字符串。
为避免SQL注入,请使用quote_identifier
中的utils/builtins.h
。
这是您代码的固定版本:
#include "postgres.h"
#include "fmgr.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "utils/builtins.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(foobar);
Datum foobar(PG_FUNCTION_ARGS)
{
Datum arguments[1];
Oid argument_types[1] = { TEXTOID };
char *res;
bool isnull;
Datum result;
/* for when we don't want to use the SPI context */
MemoryContext context = CurrentMemoryContext;
arguments[0] = CStringGetTextDatum("some_text");
SPI_connect();
SPI_execute_with_args("SELECT $1", 1, argument_types, arguments, NULL, false, 0);
result = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
res = MemoryContextStrdup(context, TextDatumGetCString(result));
SPI_finish();
PG_RETURN_TEXT_P(CStringGetTextDatum(res));
}