Программирование на C для PostgreSQL
LVEE 2015
PostgreSQL обладает отличной расширяемостью, пользователи могут добавлять сами буквально всё: типы данных, функции, операторы, типы индексов, языки хранимых процедур и т.д. Но для того, чтобы использовать многие из этих возможностей, нужно уметь программировать под PostgreSQL на C.
Традиционно считается, что это сложно и порог вхождения очень велик. Из-за этого вся мощь расширяемости PostgreSQL оказывается не так востребована, как могла бы быть. Мне хотелось бы это обстоятельство постепенно преодолевать. Действительно, как и в любом большом проекте, написанном на языке C, программирование под PostgreSQL имеет свои особенности.
В PostgreSQL используется свой calling convention, благодаря которому функции, написанные как на C, так и на процедурных языках, могут быть видны из SQL. Есть универсальный тип данных – Datum, к которому может быть приведено значение любого типа. Параметры и результат передаются как Datum. Ниже приведён пример функции, использующей PostgreSQL calling convention.
PG_FUNCTION_INFO_V1(increment);
Datum
increment(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
В PostgreSQL есть свой менеджер памяти: любое выделение памяти осуществляется в рамках некоторого контекста памяти. Контексты памяти, в свою очередь, образуют иерархическую структуру. Ниже приведён шаблон функции, которая возвращает набор строк (set-returning function). Такая функция вызывается для каждой отдельной строки. Память, которую эта функция выделяет, будут освобождена перед следующим вызовом, но также доступен контекст памяти, которых сохраняется между вызовами.
Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Datum result;
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* Инициализация структур памяти, которая выполняется только один раз */
пользовательский код
MemoryContextSwitchTo(oldcontext);
}
/* Инициализация структур памяти, которая выполняется каждый раз */
пользовательский код
funcctx = SRF_PERCALL_SETUP();
пользовательский код
/* Нужно ли вернуть ещё одну строку или все строки уже возвращены? */
if (funcctx->call_cntr < funcctx->max_calls)
{
/* Возврат следующей строки результата (result) */
пользовательский код
SRF_RETURN_NEXT(funcctx, result);
}
else
{
/* Все строки уже были возвращены, выполняется освобождение использованных ресурсов, если нужно */
пользовательский код
SRF_RETURN_DONE(funcctx);
}
}
Благодаря тому, что любой контекст памяти можно очистить в любой момент времени, во многих случаях можно не освобождать отдельно каждый участок памяти. При этом накладные расходы несравнимо малы по сравнению с применением сборщика мусора.
Посылать к БД SQL-запросы можно напрямую в тот же backend, из которого вызвана функция. Для этого есть специальный интерфейс – SPI. Ниже приведён пример функции, который выполняет запрос и выводит его результаты в виде INFO сообщений. Её аргументами являются текст запроса в виде text, и максимальное число выводимых строк ответа.
int
execq(text *sql, int cnt)
{
char *command;
int ret;
int proc;
/* Преобразуем text в C-строку */
command = text_to_cstring(sql);
SPI_connect();
ret = SPI_exec(command, cnt);
proc = SPI_processed;
/*
* Если удалось получить строки, то выводим их через elog(INFO).
*/
if (ret > 0 && SPI_tuptable != NULL)
{
TupleDesc tupdesc = SPI_tuptable->tupdesc;
SPITupleTable *tuptable = SPI_tuptable;
char buf[8192];
int i, j;
/* Цикл по строкам ответа */
for (j = 0; j < proc; j++)
{
HeapTuple tuple = tuptable->vals[j];
/* Цикл по полям строки ответа */
for (i = 1, buf[0] = 0; i <= tupdesc->natts; i++)
snprintf(buf + strlen (buf), sizeof(buf) - strlen(buf), " %s%s",
SPI_getvalue(tuple, tupdesc, i),
(i == tupdesc->natts) ? " " : " |");
elog(INFO, "EXECQ: %s", buf);
}
}
SPI_finish();
pfree(command);
return (proc);
}
Но если немного привыкнуть, то для того, кто уже имеет опыт на C, программировать под PostgreSQL не так уж и сложно. А благодаря тому, что уже есть готовые удобные макросы, функции, структуры данных, это может быть даже проще, чем на чистом C.
Abstract licensed under Creative Commons Attribution-ShareAlike 3.0 license
Back