来自PostgreSQL中C扩展的“ CREATE SCHEMA foo ...”查询(使用SPI_execute_with_args)

时间:2018-10-18 10:34:42

标签: c database postgresql postgresql-10 postgresql-extensions

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

我正在尝试通过使用SPI_execute_with_args来做到这一点,因为documentation声明以下内容:

  

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上找到了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:,例如:ANYOIDCSTRINGOIDCHAROIDREGNAMESPACEOIDTEXTOID等。而且我已经尝试将变量作为纯字符数组以及指向用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函数的工作原理,因为我将进一步需要它。项目。

有人对此有任何可行的例子吗?或者可以将我推向正确的方向?

1 个答案:

答案 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));
}