PostgreSQL interaction with external image storage service


    Good day. When working with a database for a site, a situation sometimes arises when you have to choose how and where to store images. Among the possible options, as a rule, there are the following:
    • images are entirely in the database
    • images are in the file system, the file name is stored in the database
    • images are in an external specialized service

    Although PostgreSQL provides the ability to store files in the database (directly in bytea fields or through large objects ), this is the least optimal option, both in terms of speed and memory consumption. Another common practice is the storage of images in the form of files on disk, the path to the image is formed for the site. Of the benefits - the ability to cache or use a specialized file system. And the third option - a separate service is allocated for images, which can be caching, scaling on the fly, changing the format. Let's try to implement the interaction of PostgreSQL with such a service.

    Implementation


    We outline a little picture of what is happening. We have an http service, like this , for images, supporting such commands:
    • image loading - sending a POST request with a form, in response comes JSON with some information about the image, among which the generated identifier
    • image acquisition - sending a GET request with the image identifier my.service.local / 1001
    • image deletion - sending a DELETE request with the image identifier my.service.local / 1001

    Image identifiers will be stored in the database, in this case, you can embed tags of the form on the pages of the site:

    On the part of the user, loading the image (as well as saving and deleting) should look like a call to the upload_image function (with the filename parameter ), which returns the image identifier in the service, which is then written to the table. Since it is impossible to access http requests directly from PostgreSQL , it is necessary to implement the required functionality on stored functions in C, and they already have a place to go around. For simplicity, let’s do with the curl and jansson libraries (the latter for working with JSON). We can start.

    Define our function prototypes in the barberry_impl.h header file :
    // 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);
    

    In the source file barberry_impl.c, put the following global variables:
    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 - the host we are working with
    • CURLOPT_HTTPPOST - form submitted by the POST method
    • CURLOPT_WRITEFUNCTION - CALLBACK function to respond from the host

    Barberry_upload_file implementation :
    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-function upload_response has prototype:
    size_t function(char *ptr, size_t size, size_t nmemb, void *userdata);
    

    with parameters:
    • ptr - pointer to the received data
    • size * nmemb - their size
    • userdata - pointer to FILE *, optionally set via the CURLOPT_WRITEDATA option

    The function should return the actual size of the processed data, i.e. size * nmemb. In this, in this function, it is necessary to parse the JSON transmitted in the response:
    size_t upload_response(char *ptr, size_t size, size_t nmemb, void *userdata)
    {
    	(void)userdata;
    	parse_upload_response(ptr);
    	return size * nmemb;
    }
    

    We will delegate this to another function in which we use jansson to parse the answer:
    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);
    }
    

    In the case of an empty file, we will receive the answer {}, we will handle this case. If everything is in order, the file has been successfully uploaded, the answer will come in the form: {"id": 1001, "ext": "png" ...}. Only id is of interest, and we write it in result.

    The function for saving the file is a little simpler - you just need to generate a GET request, get the response and write it to the file (after processing the situation when the file with the desired id was not found):
    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;
    }
    


    Deleting a file in the service is a DELETE request (the request type for curl is set via the CURLOPT_CUSTOMREQUEST option):
    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;
    }
    


    Before moving on to the PostgreSQL part, we will write a small console utility for testing our functions. In it, we check the passed parameters, if they correspond to the expected ones (example in print_help ), then we do the necessary actions:
    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;
    }
    


    Putting this whole thing together (the paths in your OS to header files and libraries may vary) and testing:
    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
    

    If everything is in order, you can go to the PostgreSQL part of our library (for more details on stored C functions in PostgreSQL, see [4] ).

    Let's declare the functions exported for the database (with version 1):
    PG_FUNCTION_INFO_V1(bb_upload_file);
    PG_FUNCTION_INFO_V1(bb_download_file);
    PG_FUNCTION_INFO_V1(bb_delete_file);
    

    To convert from text (type in PostgreSQL) to c-string, a small function will help:
    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;
    }
    

    The implementation of exported functions consists in delegating the previously written functionality, with the generation of an error if something went wrong:
    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();
    }
    


    We build a dynamic library and copy it to PostgreSQL (the paths in your OS to header files and libraries may vary):
    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 functions created in the database look like:
    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;
    

    We will design a dynamic library and an SQL script as an extension to PostgreSQL (described in more detail in [5] ). To do this, you need the barberry.control control file :
    # BarBerry image service
    comment = 'BarBerry image service'
    default_version = '1.0'
    module_pathname = '$libdir/barberry'
    relocatable = true
    

    The SQL script for our extension must be called barberry - 1.0.sql (according to the PostgreSQL documentation ). We copy these two files to where PostgreSQL stores its extensions.

    Creating and using the extension is extremely simple:
    CREATE EXTENSION barberry;
    UPDATE avatar SET image = bb_upload_file ( 'my.service.local', 'images/avatar_admin.png' ) WHERE name = 'admin';
    

    Source files


    The library above is a small utility, so it is not hosted on github. To facilitate assembly, a Makefile has been added for the purposes of barberry, barberry_test, clean, rebuild, install.
    barberry_impl.h
    #ifndef BARBERRY_IMPL_H
    #define BARBERRY_IMPL_H
    #include 
    #include 
    #include 
    #include 
    // 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 
    #include 
    #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
    


    Notes


    • since the dynamic library is loaded on behalf of postgres (the default user for the DBMS), it must also have access to the downloaded files and the right to create saved files
    • you can expand the idea by making an interface for accessing curl from PostgreSQL , screwing up the description of the form, headers and everything else in XML format, then parsing in C code and executing the corresponding commands in curl

    List of references


    1. PostgreSQL documentation .
    2. Documentation curl .
    3. Documentation Jansson .
    4. C stored functions in PostgreSQL .
    5. Creating Extensions in PostgreSQL .

    Also popular now: