0

我正在尝试使用服务器编程接口 (SPI) 从为 PostgreSQL 构建的 C 扩展执行 SQL 查询。查询应该创建一个包含大量表的新模式。(基本上它应该为用户设置一个工作区。)但是由于用户应该能够创建多个工作区,所以在编写脚本时我不知道架构名称。所以我需要一种在运行时提供这个的方法。但我无法让它工作。

我正在尝试通过使用文档说明以下内容来做到SPI_execute_with_args一点

SPI_execute_with_args执行可能包含对外部提供的参数的引用的命令。命令文本将参数称为$n,并且调用为每个此类符号指定数据类型和值。read_onlycount具有与中相同的解释 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,例如:ANYOIDCSTRINGOIDCHAROIDREGNAMESPACEOIDTEXTOID。我尝试将变量作为纯 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 函数是如何工作的,因为我需要在项目中进一步使用它。

有没有人对此有任何可行的例子或将我推向正确方向的东西?

4

1 回答 1

1

$1in your在SELECT '$1'单引号内,因此它是字符串文字而不是参数。

请改用以下内容:

SELECT $1

请注意,您可以使用参数,也可以使用相同类型的文字,但不能将参数用于标识符,如表或列名。

如果您需要在这样的地方使用变量,则必须使用snprintf.

为避免 SQL 注入,请使用quote_identifierfrom 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));
}
于 2018-10-18T12:03:30.223 回答