我正在尝试使用服务器编程接口 (SPI) 从为 PostgreSQL 构建的 C 扩展执行 SQL 查询。查询应该创建一个包含大量表的新模式。(基本上它应该为用户设置一个工作区。)但是由于用户应该能够创建多个工作区,所以在编写脚本时我不知道架构名称。所以我需要一种在运行时提供这个的方法。但我无法让它工作。
我正在尝试通过使用文档说明以下内容来做到SPI_execute_with_args
这一点:
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 上找到了这个例子,函数看起来像这样:
... 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
作为答案。
如果我是对的,那么您想要替换的位置和内容似乎很重要。并且 SPI 不只是在?$1
$1
在测试各种变量类型时,我尝试了一些不同的 OID:s,例如:ANYOID
、CSTRINGOID
、CHAROID
、REGNAMESPACEOID
等TEXTOID
。我尝试将变量作为纯 char 数组和指向分配有SPI_palloc()
or的文本块的指针发送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 函数是如何工作的,因为我需要在项目中进一步使用它。
有没有人对此有任何可行的例子或将我推向正确方向的东西?