The OpenNET Project / Index page

[ новости/++ | форум | wiki | теги ]

C-функции для PostgreSQL (gcc postgresql function)


<< Предыдущая ИНДЕКС Исправить src / Печать Следующая >>
Ключевые слова: gcc, postgresql, function,  (найти похожие документы)
From: akie <kolya@pisem.net.> Date: Sun, 18 Sep 2007 17:02:14 +0000 (UTC) Subject: C-функции для PostgreSQL Оригинал: http://chernowiki.ru/index.php?node=59 C-Language functions for PostgreSQL / C-функции для PostgreSQL * Пишем Set Returning Function * Простейшее использование Server Processing Interface * Отладка (debug) кода * Компиляция кода с помощью Makefile * Замечания Введение Данная статья содержит материалы, посвященные написанию на C функций для PostgreSQL - оказывается, делать это довольно легко, ну а бонусов вы получаете существенно больше, чем если бы вы писали всю ту же логику на процедурных языках. Скомпиленные в shared object функции затем можно будет загрузить в PostgreSQL и использовать по своему усмотрению как SQL команды, например. Простейшие функции можно посмотреть и в мануале (заходите сюда только после изучения этого документа). Да, еще обязательно посмотрите на туториал с OSCON-2004 под названием "Power PostgreSQL: Extending Database with C". Но если вы хотите чего-то большего, то мануал быстро перестанет вас устраивать. У меня лично с ходу во всем разобраться не очень получилось - так что эта статья посвящена таким, как я :) Важное предупреждение: функции написаны без использования best practices, это всего лишь работающие черновики (даже нет обработки нулевого result set-а, который может вернуть SPI_execute, например). Автор особо не парился над внешним видом своего кода, поэтому нельзя считать его готовым продакшн-вариантом. Пишем Set Returning Function Итак, попробуем написать простейшую Set Returning Function (функцию, возвращающую сет значений, result set) - у нас она будет брать на вход инт с длиной сета и на выходе будет возвращать сет значений от единицы до заданного числа (вот так просто и тупо). Такая тупость выбрана не случайно - в функции нет абсолютно никаких наворотов кроме каркаса для демонстрации multicall-работы SRF. Напомню, что в стандартном случае, SRF функция работает в режиме value-per-call, то есть она за каждый вызов возвращает только одно значение и вызывается столько раз, сколько рядов у нас в result set-е. Между вызовами информация хранится в специальной структуре - контексте функции. #include "postgres.h" // main include file (include always) #include "fmgr.h" // "Function Manager" for V1 style #include "funcapi.h" // to return set of rows /* Version 1 Calling Conventions - так нужно писать все функции теперь */ PG_FUNCTION_INFO_V1(iz_test); Datum iz_test(PG_FUNCTION_ARGS) { /* Тот самый контекст функции */ FuncCallContext *funcctx; /* Тоже нужно для multicall persistence */ MemoryContext oldcontext; /* заходим сюда только в первом вызове функции */ if (SRF_IS_FIRSTCALL()) { /* * инициализация структуры-контекста фунции для * хранения информации между вызовами */ funcctx = SRF_FIRSTCALL_INIT(); /* * говорим Постгресу, что у нас тут multicall-функция * и все серьезно */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* * говорим, что функцию нужно дергать столько раз, сколько указано * в ее первом аргументе */ funcctx->max_calls = PG_GETARG_INT32(0); MemoryContextSwitchTo(oldcontext); } /* код, который исполняется при каждом вызове функции */ funcctx = SRF_PERCALL_SETUP(); // контекст функции освежили if (funcctx->call_cntr < funcctx->max_calls) { /* * Это, собственно, возвращение каждого item-а * Обратите внимание, что SRF_RETURN_NEXT в качестве аргументов * принимает контекст функции для его обновления (хотя бы даже * счетчик передвинуть) и собственно то, что нужно вернуть, только * в виде Datum-а, который мы тут и делаем из инта */ SRF_RETURN_NEXT(funcctx, Int32GetDatum(funcctx->call_cntr)); } else { // так нужно все заканчивать SRF_RETURN_DONE(funcctx); } } Дальше вы должны сами скомпилить это дело в .so, положить Постгресу в нужное место и просто создать эту функцию: CREATE OR REPLACE FUNCTION iz_test(integer) RETURNS setof int4 AS '/usr/lib/pgsql/c-func_test.so', 'iz_test' LANGUAGE C STRICT; Потом все будет выглядеть приблизительно вот так: test_db=# select iz_test(10); iz_test --------- 1 2 3 4 5 6 7 8 9 10 (10 rows) Time: 0.300 ms Простейшее использование Server Processing Interface Теперь попробуем использовать Server Processing Interface (SPI) для того, чтобы мы могли выполнять SQL-запросы. Каркас multicall функции трогать не будем, просто добавим туда работу со SPI_* функциями. Известные по первому примеру места я уже не комментирую. #include "postgres.h" // main include file (include always) #include "fmgr.h" // "Function Manager" for V1 style #include "executor/spi.h" // Server Processing Interface #include "funcapi.h" // to return set of rows and cope with tuples #include <string.h> #include <stdio.h> #include <stdlib.h> // мы будем использовать atoi() PG_FUNCTION_INFO_V1(get_level1_c); Datum get_level1_c(PG_FUNCTION_ARGS) { int32 pid = PG_GETARG_INT32(0); int spi_ret; char sql[100]; // не будем особо париться, пишем чтобы работало char *tupval; FuncCallContext *funcctx; MemoryContext oldcontext; Datum result; if (SRF_IS_FIRSTCALL()) { funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Готовимся выполнять запрос */ SPI_connect(); // функция коннекта // непосредственно сама строка запроса snprintf(sql, sizeof(sql), "SELECT edge_pid2 FROM edge WHERE edge_pid1 = %d AND edge_pid2 <> %d", pid, pid); /* * выполняем запрос. 0 в качестве третьего аргумента означает, * что нужно обработать все туплы */ spi_ret = SPI_execute(sql, true, 0); /* * наша функция будет вызвана столько раз, сколько туплов в * нашем результате */ funcctx->max_calls = SPI_processed; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); if (funcctx->call_cntr < funcctx->max_calls) { /* * Получаем строковое значение из текущего тупла * Обратите внимание, хотя мы выбирали в запросе всего одну колонку, * ее индекс равен 1, а не 0! Я лично потратил пару часов, чтобы это * понять, не повторяйте моих ошибок */ tupval = SPI_getvalue(SPI_tuptable->vals[funcctx->call_cntr], SPI_tuptable->tupdesc, 1); // дальше тривиально - просто делаем инт из строки и в датум его result = Int32GetDatum(atoi(tupval)); SRF_RETURN_NEXT(funcctx, result); } else { SPI_finish(); SRF_RETURN_DONE(funcctx); } } Отладка (debug) кода Отлаживать собственный код можно, пользуясь, например, макросом elog и выводя в виде NOTICE-ов содержание каких-либо переменных. Например: elog(NOTICE, "My name is %s, I'm %d years old", "Vanya", 21); Но если хочется делать все по-взрослому, то нужно использовать стандартный GNU debugger (gdb). Так как отлаживать мы будем процесс postmaster, нужно иметь привилегии пользователя, под которым он запущен. Итак, последовательность действий такова: 1. Стартуем какого-либо клиента, например psql 2. Из клиента выполняем SELECT pg_backend_pid(), узнавая тем самым pid процесса postmaster (это можно сделать и утилитой ps из командной строки) 3. Загружаем из клиента нашу динамическую библиотеку: LOAD '/usr/lib/pgsql/c-func_test.so' 4. В другой терминальной сессии под рутом, либо под хозяином postmaster-а (назовем его postgres, как это бывает обычно) стартуем дебаггер: gdb postgres server-process-id 5. Ставим breakpoint в нашей функции: (gdb) break my-function 6. Идем в назад в клиента и запускаем нашу функцию: SELECT my-function() 7. Нажимаем continue в дебаггере: (gdb) c или читаем help, если мы в нем первый раз оказались: (gdb) help Компиляция кода с помощью Makefile Для начала вам подойдет простейший Makefile: # Makefile for building C-functions shared objects for PostgreSQL # Author: IZ # Date: 2005/10/20 SERVER_INCLUDES += -I $(shell pg_config --includedir) SERVER_INCLUDES += -I $(shell pg_config --includedir-server) CFLAGS += -g $(SERVER_INCLUDES) .SUFFIXES: .so .c.so: $(CC) $(CFLAGS) -fpic -c $< $(CC) $(CFLAGS) -shared -o $@ $(basename $<).o @echo Built! clean: -rm -f *.o *.so *~ 2>/dev/null @echo Cleaned! Затем просто запускаем make c-func_test.so и нам делается правильный со-шник в текущей директории. После этой процедуры уже можно делать LOAD в клиенте psql. Важное примечание: уж не знаю по какой причине, но если я в качестве header-файлов беру те, что содержатся в pg_config --includedir-server, то есть из глобальных путей, при попытке загрузки so-шника в постгрес появляются сообщения вида undefined symbol: elog. Если при этом брать header-файлы из исходников постгреса, то есть указывать глобальные пути к ним, например, указывая в качестве ключа компилятору -I /home/ns/postgresql-8.0.2/src/include/, то определение elog-а найдется и все будет работать. Уж не знаю, почему при установке постгреса .h-файлы так неприятно обрезаются :( Замечания 1. У вас не получится писать рекурсивные функции. Посмотрите на аргументы и возвращаемые значения, их типы и т.д. и догадайтесь сами, почему не получится. 2. Меня поразило отсутствие нормальной девелоперской документации к этой разработке. Я не считаю себе гуру в сишном программировании, скорее наоборот, но разве нельзя было как-нибудь по-нормальному все задокументировать?

<< Предыдущая ИНДЕКС Исправить src / Печать Следующая >>

Ваш комментарий
Имя:         
E-Mail:      
Заголовок:
Текст:





  Закладки на сайте
  Проследить за страницей
Created 1996-2017 by Maxim Chirkov  
ДобавитьРекламаВебмастеруГИД  
Hosting by Ihor