Запрос «CREATE SCHEMA foo ...» из расширения C в PostgreSQL (с использованием SPI_execute_with_args) - PullRequest
0 голосов
/ 18 октября 2018

Я пытаюсь выполнить SQL-запрос из расширения C, созданного для PostgreSQL, используя интерфейс программирования сервера (SPI).Запрос должен создать новую схему с довольно большим количеством таблиц.(По сути, это должно настроить рабочее пространство для работы пользователей.) Но поскольку пользователь должен иметь возможность создавать несколько рабочих областей, я не знаю имя схемы при написании сценария.Поэтому мне нужен способ предоставить это во время выполнения.Но я не могу заставить его работать.

Я пытаюсь сделать это, используя 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, мне пришлось поискать в Google что-нибудь, что могло бы помочьменя дальше.И я нашел этот пример на 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 в качестве ответа.


Если я прав, кажется, что имеет значение где и что вы хотите заменить $1 на .И этот SPI не просто выполняет «поиск и замену» на $1?

. Я пробовал несколько разных OID: s при тестировании различных типов переменных, таких как: 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как мне понадобится в дальнейшем.

У кого-нибудь есть какие-нибудь рабочие примеры для этого или что-то, что подтолкнуло бы меня в правильном направлении?

1 Ответ

0 голосов
/ 18 октября 2018

$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));
}
...