Взаимодействие PostgreSQL с внешним сервисом для хранения изображений

от автора


Доброго времени суток. При работе с базой данных для сайта иногда возникает ситуация, когда приходится выбирать, как и где хранить изображения. Среди возможных вариантов, как правило, имеются следующие:

  • изображения находятся целиком в БД
  • изображения находятся в файловой системе, в БД хранится имя файла
  • изображения находятся во внешнем специализированном сервисе

Хоть PostgreSQL и предоставляет возможность хранения в БД файлов (непосредственно в bytea полях или через large objects), это наименее оптимальный вариант, как в плане скорости, так и потребляемой памяти. Другой, общепринятой практикой, является хранение изображений в виде файлов на диске, для сайта формируется путь к изображению. Из преимуществ — возможность кеширования или использование специализированной файловой системы. И третий вариант — для изображений выделяется отдельный сервис, в котором может быть кеширование, маштабирование на лету, изменение формата. Попробуем реализовать взаимодействие PostgreSQL с таким сервисом.

Реализация

Обрисуем немного картину происходящего. У нас имеется http-сервис, по типу этого, для изображений, поддерживающий такие команды:

  • загрузка изображения — отправка POST-запроса с формой, в ответ приходит JSON с некоторой информацией об изображении, среди которой сгенерированный идентификатор
  • получение изображения — отправка GET-запроса c идентификатором изображения my.service.local/1001
  • удаление изображения — отправка DELETE-запроса c идентификатором изображения my.service.local/1001

В БД будут хранится идентификаторы изображений, в таком случае, на страницах сайта можно будет втраивать теги вида:

<img src="http://my.service.local/1001"/>

Со стороны пользователя загрузка изображения (равно как сохранение и удаление) должна выглядеть как вызов функции upload_image (с параметром filename), которая возращает идентификатор изображения в сервисе, записуемый затем в таблицу. Так как напрямую из PostgreSQL нельзя доступится к http запросам, необходимо реализовывать требуемый функционал на хранимых функциях на С, а в них уже есть где разгулятся. Для простоты, обойдёмся библиотеками curl и jansson (последняя для работы с JSON). Можем начинать.

Определим в заголовочном файле barberry_impl.h наши прототипы функций:

// get last error char* barberry_error();  // upload file to BarBerry's service and return ID int barberry_upload_file(const char *host, const char *filename);  // download file from BarBerry's service by ID int barberry_download_file(const char *host, int id, const char *filename);  // delete file from BarBerry's service by ID int barberry_delete_file(const char *host, int id); 

В файле с исходном кодом barberry_impl.c поместим следующие глобальные переменные:

char last_error[1024]; FILE *file = NULL; int result = 0; 

Переменная last_error будет хранить последнюю ошибку, file — это указатель на файл, создаваемый при получении данных от сервиса, а в result будет сохранятся результат функций работы с сервисом.

Реализация функции barberry_error тривиальна — возврат last_error. Разберем подробно функцию barberry_upload_file.

Перед тем, как начать работу с библиотекой curl, необходимо проинициализировать окружение для неё (командой curl_gobal_init) и создать сессию (командой curl_easy_init, возращающей указатель на хэндл сессии). Далее, создаем submit-форму (через curl_formadd) и заполняем следующие опции:

  • CURLOPT_URL — хост, с которым мы работаем
  • CURLOPT_HTTPPOST — форма, отправляемая методом POST
  • CURLOPT_WRITEFUNCTION — CALLBACK-функция для ответа от хоста

Реализация barberry_upload_file:

int barberry_upload_file(const char *host, const char *filename) { 	result = -1;  	curl_global_init(CURL_GLOBAL_ALL);  	CURL *curl = curl_easy_init();  	if (curl) 	{ 		curl_easy_setopt(curl, CURLOPT_URL, host);  		struct curl_httppost *httppost = NULL; 		struct curl_httppost *last_ptr = NULL;  		curl_formadd(&httppost, &last_ptr, CURLFORM_COPYNAME, "sendfile", CURLFORM_FILE, filename, CURLFORM_END); 		curl_formadd(&httppost, &last_ptr, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "send", CURLFORM_END);  		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, upload_response); 		curl_easy_setopt(curl, CURLOPT_HTTPPOST, httppost);  		CURLcode res = curl_easy_perform(curl);  		if (res != CURLE_OK) 		{ 			sprintf(last_error, "%s", curl_easy_strerror(res)); 		}  		curl_easy_cleanup(curl); 		curl_formfree(httppost); 	}  	return result; } 

CALLBACK-функция upload_response имеет прототип:

size_t function(char *ptr, size_t size, size_t nmemb, void *userdata); 

с параметрами:

  • ptr — указатель на получаемые данные
  • size * nmemb — их размер
  • userdata — указатель на FILE*, при необходимости устанавливаемый через опцию CURLOPT_WRITEDATA

Функция должна возратить фактической размер обработанных данных, т.е. size * nmemb. В данном, в этой функции необходимо распарсить JSON передаваемый в ответе:

size_t upload_response(char *ptr, size_t size, size_t nmemb, void *userdata) { 	(void)userdata;  	parse_upload_response(ptr);  	return size * nmemb; } 

Поручим это другой функции, в которой используем jansson для разбора ответа:

void parse_upload_response(const char *text) { 	if (!strcmp(text, "{}")) 	{ 		sprintf(last_error, "%s", "Empty file");  		return; 	}  	json_error_t error;  	json_t *root = json_loads(text, 0, &error);  	if (!root) 	{ 		sprintf(last_error, "%s", text);  		return; 	}  	json_t *id = json_object_get(root, "id");  	if(!json_is_integer(id)) 	{ 		sprintf(last_error, "%s", text);  		json_decref(root);  		return; 	}  	result = json_integer_value(id);  	json_decref(root); } 

В случае пустого файла, нам прийдёт ответ {}, обработаем этот случай. Если всё в порядке, файл был успешно загружен ответ прийдет в виде: { «id»:1001, «ext»:«png»… }. Интересует только id, его и записываем в result.

Функция для сохранения файла немного проще — нужно лишь сформировать GET-запрос, получить ответ и записать его в файл (обработав ситуацию, когда файл с нужным id не найден):

barberry_download_file

int barberry_download_file(const char *host, int id, const char *filename) { 	result = 0;  	file = fopen(filename, "wb");  	if (!file) 	{ 		sprintf(last_error, "%s", "Can't create file");  		return -1; 	}  	curl_global_init(CURL_GLOBAL_ALL);  	CURL *curl = curl_easy_init();  	if (curl) 	{ 		char buffer[1024];  		sprintf(buffer, "%s/%d", host, id);  		curl_easy_setopt(curl, CURLOPT_URL, buffer); 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_response);  		CURLcode res = curl_easy_perform(curl);  		if (res != CURLE_OK) 		{ 			sprintf(last_error, "%s", curl_easy_strerror(res));  			result = -1; 		}  		curl_easy_cleanup(curl); 	}  	fclose(file);  	return result; } 

download_response

size_t download_response(char *ptr, size_t size, size_t nmemb, void *userdata) { 	(void)userdata;  	if (!strcmp(ptr, "{}")) 	{ 		sprintf(last_error, "%s", "File on server not found");  		result = -1; 	} 	else 	{ 		fwrite(ptr, size * nmemb, 1, file); 	}  	return size * nmemb; } 

Удаление файла в сервисе — это DELETE-запрос (тип запроса для curl устанавливается через опцию CURLOPT_CUSTOMREQUEST):

barberry_delete_file

int barberry_delete_file(const char *host, int id) { 	result = 0;  	curl_global_init(CURL_GLOBAL_ALL);  	CURL *curl = curl_easy_init();  	if (curl) 	{ 		char buffer[1024];  		sprintf(buffer, "%s/%d", host, id);  		curl_easy_setopt(curl, CURLOPT_URL, buffer); 		curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, delete_response);  		CURLcode res = curl_easy_perform(curl);  		if (res != CURLE_OK) 		{ 			sprintf(last_error, "%s", curl_easy_strerror(res));  			result = -1; 		}  		curl_easy_cleanup(curl); 	}  	return result; } 

delete_response

size_t delete_response(char *ptr, size_t size, size_t nmemb, void *userdata) { 	(void)ptr; 	(void)userdata;  	return size * nmemb; } 

Прежде чем перейти к PostgreSQL-части, напишем небольшую консольную утилиту для тестирования наших функций. В ней проверяем переданные параметры, если они соответствуют ожидаемым (пример в print_help), то делаем нужные действия:

barberry_test.c

#include "barberry_impl.h"  void print_help() { 	fprintf(stdout, "Usage:\n"); 	fprintf(stdout, "  bbtest upload my.service.local /home/username/image1000.png\n"); 	fprintf(stdout, "  bbtest download my.service.local 1000 /home/username/image1000.png\n"); 	fprintf(stdout, "  bbtest delete my.service.local 1000\n\n"); }  int main(int argc, char *argv[]) { 	(void)argc; 	(void)argv;  	if (argc <= 2) 	{ 		print_help();  		return 0; 	}  	if (!strcmp(argv[1], "upload")) 	{ 		if (argc != 4) 		{ 			print_help();  			return 0; 		}  		int id = barberry_upload_file(argv[2], argv[3]);  		if (id != -1) 		{ 			fprintf(stdout, "File uploaded with id %d\n", id); 		} 		else 		{ 			fprintf(stderr, "%s\n", barberry_error()); 		} 	} 	else if (!strcmp(argv[1], "download")) 	{ 		if (argc != 5) 		{ 			print_help();  			return 0; 		}  		int result = barberry_download_file(argv[2], atoi(argv[3]), argv[4]);  		if (result != -1) 		{ 			fprintf(stdout, "%s\n", "File downloaded"); 		} 		else 		{ 			fprintf(stderr, "%s\n", barberry_error()); 		} 	} 	else if (!strcmp(argv[1], "delete")) 	{ 		if (argc != 4) 		{ 			print_help();  			return 0; 		}  		int result = barberry_delete_file(argv[2], atoi(argv[3]));  		if (result != -1) 		{ 			fprintf(stdout, "%s\n", "File deleted"); 		} 		else 		{ 			fprintf(stderr, "%s\n", barberry_error()); 		} 	} 	else 	{ 		print_help(); 	}  	return 0; } 

Собираем всё это дело (пути в Вашей ОС к заголовочным файлам и библиотекам могут отличатся) и тестируем:

cc -c barberry_impl.c cc -c barberry_test.c cc -L/usr/lib -lcurl -ljansson -o bbtest barberry_test.o barberry_impl.o ./bbtest upload my.service.local ~/picture01.png File uploaded with id 1017 

Если всё в порядке, можно переходит к PostgreSQL-части нашей библиотеки (подробней о хранимых функциях на C в PostgreSQL описано в [4]).

Обьявим экспортируемые для БД функции (с версией 1):

PG_FUNCTION_INFO_V1(bb_upload_file); PG_FUNCTION_INFO_V1(bb_download_file); PG_FUNCTION_INFO_V1(bb_delete_file); 

Для конвертирования из text (тип в PostgreSQL) в c-string поможет небольшая функция:

char* text_to_string(text *txt) { 	size_t size = VARSIZE(txt) - VARHDRSZ;  	char *buffer = (char*)palloc(size + 1);  	memcpy(buffer, VARDATA(txt), size);  	buffer[size] = '\0';  	return buffer; } 

Реализация экспортируемых функций заключается в делегировании написанному ранее функционалу, с генерацией ошибки, если что-то пошло не так:

bb_upload_file

Datum bb_upload_file(PG_FUNCTION_ARGS) { 	char *host = text_to_string(PG_GETARG_TEXT_P(0)); 	char *filename = text_to_string(PG_GETARG_TEXT_P(1));  	int result = barberry_upload_file(host, filename);  	if (result == -1) 	{ 		elog(ERROR, "%s", barberry_error()); 	}  	pfree(host); 	pfree(filename);  	PG_RETURN_INT32(result); } 

bb_download_file

Datum bb_download_file(PG_FUNCTION_ARGS) { 	char *host = text_to_string(PG_GETARG_TEXT_P(0)); 	int id = PG_GETARG_INT32(1); 	char *filename = text_to_string(PG_GETARG_TEXT_P(2));  	int result = barberry_download_file(host, id, filename);  	if (result == -1) 	{ 		elog(ERROR, "%s", barberry_error()); 	}  	pfree(host); 	pfree(filename);  	PG_RETURN_VOID(); } 

bb_delete_file

Datum bb_delete_file(PG_FUNCTION_ARGS) { 	char *host = text_to_string(PG_GETARG_TEXT_P(0)); 	int id = PG_GETARG_INT32(1);  	int result = barberry_delete_file(host, id);  	if (result == -1) 	{ 		elog(ERROR, "%s", barberry_error()); 	}  	pfree(host);  	PG_RETURN_VOID(); } 

Собираем динамическую библиотеку и копируем ее к PostgreSQL (пути в Вашей ОС к заголовочным файлам и библиотекам могут отличатся):

rm -rf *.o cc -I/usr/include/postgresql/server -fpic -c barberry.c cc -I/usr/include/postgresql/server -fpic -c barberry_impl.c cc -L/usr/lib -lpq -lcurl -ljansson -shared -o barberry.so barberry.o barberry_impl.o cp *.so /usr/lib/postgresql 

SQL-функции, создаваемые в БД, имеют вид:

CREATE OR REPLACE FUNCTION public.bb_upload_file ( p_host text, p_filename text ) RETURNS integer AS 'barberry', 'bb_upload_file' LANGUAGE c VOLATILE STRICT;  CREATE OR REPLACE FUNCTION public.bb_download_file ( p_host text, p_id integer, p_filename text ) RETURNS void AS 'barberry', 'bb_download_file' LANGUAGE c VOLATILE STRICT;  CREATE OR REPLACE FUNCTION public.bb_delete_file ( p_host text, p_id integer ) RETURNS void AS 'barberry', 'bb_delete_file' LANGUAGE c VOLATILE STRICT; 

Оформим динамическую библиотеку и SQL-скрипт в виде расширения к PostgreSQL (подробнее описано в [5]). Для этого потребуется управляющий файл barberry.control:

# BarBerry image service comment = 'BarBerry image service' default_version = '1.0' module_pathname = '$libdir/barberry' relocatable = true 

SQL-скрипт для нашего расширения необходимо назвать как barberry—1.0.sql (согласно документации PostgreSQL). Скопируем эти два файла туда, где PostgreSQL хранить свои расширения.

Создание и использование расширения предельно простое:

CREATE EXTENSION barberry; UPDATE avatar SET image = bb_upload_file ( 'my.service.local', 'images/avatar_admin.png' ) WHERE name = 'admin'; 

Исходные файлы

Библиотека выша как небольшая утилита, поэтому не размещена на github. Для облегчения сборки добавлен Makefile с целями barberry, barberry_test, clean, rebuild, install.

barberry_impl.h

#ifndef BARBERRY_IMPL_H #define BARBERRY_IMPL_H  #include <stdio.h> #include <string.h> #include <curl/curl.h> #include <jansson.h>  // get last error char* barberry_error();  // upload file to BarBerry's service and return ID int barberry_upload_file(const char *host, const char *filename);  // download file from BarBerry's service by ID int barberry_download_file(const char *host, int id, const char *filename);  // delete file from BarBerry's service by ID int barberry_delete_file(const char *host, int id);  #endif // BARBERRY_IMPL_H 

barberry_impl.c

#include "barberry_impl.h"  char last_error[1024]; FILE *file = NULL; int result = 0;  void parse_upload_response(const char *text) { 	if (!strcmp(text, "{}")) 	{ 		sprintf(last_error, "%s", "Empty file");  		return; 	}  	json_error_t error;  	json_t *root = json_loads(text, 0, &error);  	if (!root) 	{ 		sprintf(last_error, "%s", text);  		return; 	}  	json_t *id = json_object_get(root, "id");  	if(!json_is_integer(id)) 	{ 		sprintf(last_error, "%s", text);  		json_decref(root);  		return; 	}  	result = json_integer_value(id);  	json_decref(root); }  size_t upload_response(char *ptr, size_t size, size_t nmemb, void *userdata) { 	(void)userdata;  	parse_upload_response(ptr);  	return size * nmemb; }  size_t download_response(char *ptr, size_t size, size_t nmemb, void *userdata) { 	(void)userdata;  	if (!strcmp(ptr, "{}")) 	{ 		sprintf(last_error, "%s", "File on server not found");  		result = -1; 	} 	else 	{ 		fwrite(ptr, size * nmemb, 1, file); 	}  	return size * nmemb; }  size_t delete_response(char *ptr, size_t size, size_t nmemb, void *userdata) { 	(void)ptr; 	(void)userdata;  	return size * nmemb; }   char* barberry_error() { 	return last_error; }  int barberry_upload_file(const char *host, const char *filename) { 	result = -1;  	curl_global_init(CURL_GLOBAL_ALL);  	CURL *curl = curl_easy_init();  	if (curl) 	{ 		curl_easy_setopt(curl, CURLOPT_URL, host);  		struct curl_httppost *httppost = NULL; 		struct curl_httppost *last_ptr = NULL;  		curl_formadd(&httppost, &last_ptr, CURLFORM_COPYNAME, "sendfile", CURLFORM_FILE, filename, CURLFORM_END); 		curl_formadd(&httppost, &last_ptr, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "send", CURLFORM_END);  		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, upload_response); 		curl_easy_setopt(curl, CURLOPT_HTTPPOST, httppost);  		CURLcode res = curl_easy_perform(curl);  		if (res != CURLE_OK) 		{ 			sprintf(last_error, "%s", curl_easy_strerror(res)); 		}  		curl_easy_cleanup(curl); 		curl_formfree(httppost); 	}  	return result; }  int barberry_download_file(const char *host, int id, const char *filename) { 	result = 0;  	file = fopen(filename, "wb");  	if (!file) 	{ 		sprintf(last_error, "%s", "Can't create file");  		return -1; 	}  	curl_global_init(CURL_GLOBAL_ALL);  	CURL *curl = curl_easy_init();  	if (curl) 	{ 		char buffer[1024];  		sprintf(buffer, "%s/%d", host, id);  		curl_easy_setopt(curl, CURLOPT_URL, buffer); 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_response);  		CURLcode res = curl_easy_perform(curl);  		if (res != CURLE_OK) 		{ 			sprintf(last_error, "%s", curl_easy_strerror(res));  			result = -1; 		}  		curl_easy_cleanup(curl); 	}  	fclose(file);  	return result; }  int barberry_delete_file(const char *host, int id) { 	result = 0;  	curl_global_init(CURL_GLOBAL_ALL);  	CURL *curl = curl_easy_init();  	if (curl) 	{ 		char buffer[1024];  		sprintf(buffer, "%s/%d", host, id);  		curl_easy_setopt(curl, CURLOPT_URL, buffer); 		curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, delete_response);  		CURLcode res = curl_easy_perform(curl);  		if (res != CURLE_OK) 		{ 			sprintf(last_error, "%s", curl_easy_strerror(res));  			result = -1; 		}  		curl_easy_cleanup(curl); 	}  	return result; } 

barberry.c

#include <postgres.h> #include <fmgr.h>  #include "barberry_impl.h"  #ifdef PG_MODULE_MAGIC 	PG_MODULE_MAGIC; #endif  PG_FUNCTION_INFO_V1(bb_upload_file); PG_FUNCTION_INFO_V1(bb_download_file); PG_FUNCTION_INFO_V1(bb_delete_file);  char* text_to_string(text *txt) { 	size_t size = VARSIZE(txt) - VARHDRSZ;  	char *buffer = (char*)palloc(size + 1);  	memcpy(buffer, VARDATA(txt), size);  	buffer[size] = '\0';  	return buffer; }  Datum bb_upload_file(PG_FUNCTION_ARGS) { 	char *host = text_to_string(PG_GETARG_TEXT_P(0)); 	char *filename = text_to_string(PG_GETARG_TEXT_P(1));  	int result = barberry_upload_file(host, filename);  	if (result == -1) 	{ 		elog(ERROR, "%s", barberry_error()); 	}  	pfree(host); 	pfree(filename);  	PG_RETURN_INT32(result); }  Datum bb_download_file(PG_FUNCTION_ARGS) { 	char *host = text_to_string(PG_GETARG_TEXT_P(0)); 	int id = PG_GETARG_INT32(1); 	char *filename = text_to_string(PG_GETARG_TEXT_P(2));  	int result = barberry_download_file(host, id, filename);  	if (result == -1) 	{ 		elog(ERROR, "%s", barberry_error()); 	}  	pfree(host); 	pfree(filename);  	PG_RETURN_VOID(); }  Datum bb_delete_file(PG_FUNCTION_ARGS) { 	char *host = text_to_string(PG_GETARG_TEXT_P(0)); 	int id = PG_GETARG_INT32(1);  	int result = barberry_delete_file(host, id);  	if (result == -1) 	{ 		elog(ERROR, "%s", barberry_error()); 	}  	pfree(host);  	PG_RETURN_VOID(); } 

barberry_test.c

#include "barberry_impl.h"  void print_help() { 	fprintf(stdout, "Usage:\n"); 	fprintf(stdout, "  bbtest upload my.service.local /home/username/image1000.png\n"); 	fprintf(stdout, "  bbtest download my.service.local 1000 /home/username/image1000.png\n"); 	fprintf(stdout, "  bbtest delete my.service.local 1000\n\n"); }  int main(int argc, char *argv[]) { 	(void)argc; 	(void)argv;  	if (argc <= 2) 	{ 		print_help();  		return 0; 	}  	if (!strcmp(argv[1], "upload")) 	{ 		if (argc != 4) 		{ 			print_help();  			return 0; 		}  		int id = barberry_upload_file(argv[2], argv[3]);  		if (id != -1) 		{ 			fprintf(stdout, "File uploaded with id %d\n", id); 		} 		else 		{ 			fprintf(stderr, "%s\n", barberry_error()); 		} 	} 	else if (!strcmp(argv[1], "download")) 	{ 		if (argc != 5) 		{ 			print_help();  			return 0; 		}  		int result = barberry_download_file(argv[2], atoi(argv[3]), argv[4]);  		if (result != -1) 		{ 			fprintf(stdout, "%s\n", "File downloaded"); 		} 		else 		{ 			fprintf(stderr, "%s\n", barberry_error()); 		} 	} 	else if (!strcmp(argv[1], "delete")) 	{ 		if (argc != 4) 		{ 			print_help();  			return 0; 		}  		int result = barberry_delete_file(argv[2], atoi(argv[3]));  		if (result != -1) 		{ 			fprintf(stdout, "%s\n", "File deleted"); 		} 		else 		{ 			fprintf(stderr, "%s\n", barberry_error()); 		} 	} 	else 	{ 		print_help(); 	}  	return 0; } 

barberry—1.0.sql

CREATE OR REPLACE FUNCTION public.bb_upload_file ( p_host text, p_filename text ) RETURNS integer AS 'barberry', 'bb_upload_file' LANGUAGE c VOLATILE STRICT;  CREATE OR REPLACE FUNCTION public.bb_download_file ( p_host text, p_id integer, p_filename text ) RETURNS void AS 'barberry', 'bb_download_file' LANGUAGE c VOLATILE STRICT;  CREATE OR REPLACE FUNCTION public.bb_delete_file ( p_host text, p_id integer ) RETURNS void AS 'barberry', 'bb_delete_file' LANGUAGE c VOLATILE STRICT 

barberry.control

# BarBerry image service comment = 'BarBerry image service' default_version = '1.0' module_pathname = '$libdir/barberry' relocatable = true 

Makefile

################################# # Makefile for barberry library # #################################  # options  CC=cc CFLAGS=-fpic -c INCLUDEPATH=-I/usr/include/postgresql/server LIBS=-L/usr/lib -lpq -lcurl -ljansson  # targets  all: barberry barberry_test  barberry: barberry.o barberry_impl.o 	$(CC) $(LIBS) -shared -o barberry.so barberry.o barberry_impl.o  barberry_test: barberry_test.o barberry_impl.o 	$(CC) $(LIBS) -o bbtest barberry_test.o barberry_impl.o  barberry.o: 	$(CC) $(INCLUDEPATH) $(CFLAGS) barberry.c  barberry_impl.o: 	$(CC) $(INCLUDEPATH) $(CFLAGS) barberry_impl.c  barberry_test.o: 	$(CC) $(INCLUDEPATH) $(CFLAGS) barberry_test.c  clean: 	rm -rf *.o *.so bbtest  rebuild: clean all  install: 	cp *.so /usr/lib/postgresql 	cp *.control /usr/share/postgresql/extension 	cp *.sql /usr/share/postgresql/extension 

Примечания

  • так как динамическая библиотека загружается от имени postgres (пользователь по умолчанию для СУБД), он же должен иметь доступ к загружаемым файлам и право на создание сохраняемых файлов
  • можно расширить идею, сделав интерфейс для доступа к curl из PostgreSQL, прикрутив описание формы, заголовков и всего прочего в XML-формате, распарсивая потом в C-коде и выполняя соответствующие команды в curl

Список литературы

  1. Документация по PostgreSQL.
  2. Документация по curl.
  3. Документация по jansson.
  4. Хранимые функции на C в PostgreSQL.
  5. Создание расширений в PostgreSQL.

ссылка на оригинал статьи http://habrahabr.ru/post/206576/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *