2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2010, 2012, 2015 GNUnet e.V.
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
22 * @file datacache/plugin_datacache_postgres.c
23 * @brief postgres for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
27 #include "gnunet_util_lib.h"
28 #include "gnunet_postgres_lib.h"
29 #include "gnunet_datacache_plugin.h"
31 #define LOG(kind,...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
34 * Per-entry overhead estimate
36 #define OVERHEAD (sizeof(struct GNUNET_HashCode) + 24)
39 * Context for all functions in this plugin.
44 * Our execution environment.
46 struct GNUNET_DATACACHE_PluginEnvironment *env;
49 * Native Postgres database handle.
54 * Number of key-value pairs in the database.
56 unsigned int num_items;
61 * @brief Get a database handle
63 * @param plugin global context
64 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
67 init_connection (struct Plugin *plugin)
71 plugin->dbh = GNUNET_POSTGRES_connect (plugin->env->cfg,
72 "datacache-postgres");
73 if (NULL == plugin->dbh)
77 "CREATE TEMPORARY TABLE IF NOT EXISTS gn090dc ("
78 " type INTEGER NOT NULL DEFAULT 0,"
79 " discard_time BIGINT NOT NULL DEFAULT 0,"
80 " key BYTEA NOT NULL DEFAULT '',"
81 " value BYTEA NOT NULL DEFAULT '',"
82 " path BYTEA DEFAULT '')"
85 ((PQresultStatus (ret) != PGRES_COMMAND_OK) &&
86 (0 != strcmp ("42P07", /* duplicate table */
91 (void) GNUNET_POSTGRES_check_result (plugin->dbh, ret,
95 PQfinish (plugin->dbh);
99 if (PQresultStatus (ret) == PGRES_COMMAND_OK)
102 GNUNET_POSTGRES_exec (plugin->dbh,
103 "CREATE INDEX IF NOT EXISTS idx_key ON gn090dc (key)")) ||
105 GNUNET_POSTGRES_exec (plugin->dbh,
106 "CREATE INDEX IF NOT EXISTS idx_dt ON gn090dc (discard_time)")))
109 PQfinish (plugin->dbh);
111 return GNUNET_SYSERR;
117 "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL");
119 GNUNET_POSTGRES_check_result (plugin->dbh,
125 PQfinish (plugin->dbh);
127 return GNUNET_SYSERR;
130 ret = PQexec (plugin->dbh,
131 "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN");
133 GNUNET_POSTGRES_check_result (plugin->dbh,
139 PQfinish (plugin->dbh);
141 return GNUNET_SYSERR;
145 GNUNET_POSTGRES_prepare (plugin->dbh,
147 "SELECT discard_time,type,value,path FROM gn090dc "
148 "WHERE key=$1 AND type=$2 ", 2)) ||
150 GNUNET_POSTGRES_prepare (plugin->dbh,
152 "SELECT discard_time,type,value,path FROM gn090dc "
153 "WHERE key=$1", 1)) ||
155 GNUNET_POSTGRES_prepare (plugin->dbh,
157 "SELECT length(value),oid,key FROM gn090dc "
158 "ORDER BY discard_time ASC LIMIT 1", 0)) ||
160 GNUNET_POSTGRES_prepare (plugin->dbh,
162 "SELECT discard_time,type,value,path,key FROM gn090dc "
163 "ORDER BY key ASC LIMIT 1 OFFSET $1", 1)) ||
165 GNUNET_POSTGRES_prepare (plugin->dbh,
167 "SELECT discard_time,type,value,path,key FROM gn090dc "
168 "WHERE key>=$1 ORDER BY key ASC LIMIT $2", 1)) ||
170 GNUNET_POSTGRES_prepare (plugin->dbh,
172 "DELETE FROM gn090dc WHERE oid=$1", 1)) ||
174 GNUNET_POSTGRES_prepare (plugin->dbh,
176 "INSERT INTO gn090dc (type, discard_time, key, value, path) "
177 "VALUES ($1, $2, $3, $4, $5)", 5)))
179 PQfinish (plugin->dbh);
181 return GNUNET_SYSERR;
188 * Store an item in the datastore.
190 * @param cls closure (our `struct Plugin`)
191 * @param key key to store @a data under
192 * @param size number of bytes in @a data
193 * @param data data to store
194 * @param type type of the value
195 * @param discard_time when to discard the value in any case
196 * @param path_info_len number of entries in @a path_info
197 * @param path_info a path through the network
198 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
201 postgres_plugin_put (void *cls,
202 const struct GNUNET_HashCode *key,
205 enum GNUNET_BLOCK_Type type,
206 struct GNUNET_TIME_Absolute discard_time,
207 unsigned int path_info_len,
208 const struct GNUNET_PeerIdentity *path_info)
210 struct Plugin *plugin = cls;
212 uint32_t btype = htonl (type);
213 uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value_us__;
215 const char *paramValues[] = {
216 (const char *) &btype,
217 (const char *) &bexpi,
220 (const char *) path_info
222 int paramLengths[] = {
225 sizeof (struct GNUNET_HashCode),
227 path_info_len * sizeof (struct GNUNET_PeerIdentity)
229 const int paramFormats[] = { 1, 1, 1, 1, 1 };
232 PQexecPrepared (plugin->dbh, "put", 5, paramValues, paramLengths,
235 GNUNET_POSTGRES_check_result (plugin->dbh, ret,
236 PGRES_COMMAND_OK, "PQexecPrepared", "put"))
240 return size + OVERHEAD;
245 * Iterate over the results for a particular key
248 * @param cls closure (our `struct Plugin`)
249 * @param key key to look for
250 * @param type entries of which type are relevant?
251 * @param iter maybe NULL (to just count)
252 * @param iter_cls closure for @a iter
253 * @return the number of results found
256 postgres_plugin_get (void *cls,
257 const struct GNUNET_HashCode *key,
258 enum GNUNET_BLOCK_Type type,
259 GNUNET_DATACACHE_Iterator iter,
262 struct Plugin *plugin = cls;
263 uint32_t btype = htonl (type);
265 const char *paramValues[] = {
267 (const char *) &btype
269 int paramLengths[] = {
270 sizeof (struct GNUNET_HashCode),
273 const int paramFormats[] = { 1, 1 };
274 struct GNUNET_TIME_Absolute expiration_time;
278 unsigned int path_len;
279 const struct GNUNET_PeerIdentity *path;
283 PQexecPrepared (plugin->dbh, (type == 0) ? "getk" : "getkt",
284 (type == 0) ? 1 : 2, paramValues, paramLengths,
287 GNUNET_POSTGRES_check_result (plugin->dbh,
291 (type == 0) ? "getk" : "getkt"))
293 LOG (GNUNET_ERROR_TYPE_DEBUG,
294 "Ending iteration (postgres error)\n");
298 if (0 == (cnt = PQntuples (res)))
301 LOG (GNUNET_ERROR_TYPE_DEBUG,
302 "Ending iteration (no more results)\n");
311 if ( (4 != PQnfields (res)) ||
312 (sizeof (uint64_t) != PQfsize (res, 0)) ||
313 (sizeof (uint32_t) != PQfsize (res, 1)))
319 for (i = 0; i < cnt; i++)
321 expiration_time.abs_value_us =
322 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
323 type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
324 size = PQgetlength (res, i, 2);
325 path_len = PQgetlength (res, i, 3);
326 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
331 path_len %= sizeof (struct GNUNET_PeerIdentity);
332 path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, i, 3);
333 LOG (GNUNET_ERROR_TYPE_DEBUG,
334 "Found result of size %u bytes and type %u in database\n",
335 (unsigned int) size, (unsigned int) type);
337 iter (iter_cls, key, size, PQgetvalue (res, i, 2),
338 (enum GNUNET_BLOCK_Type) type,
343 LOG (GNUNET_ERROR_TYPE_DEBUG,
344 "Ending iteration (client error)\n");
355 * Delete the entry with the lowest expiration value
356 * from the datacache right now.
358 * @param cls closure (our `struct Plugin`)
359 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
362 postgres_plugin_del (void *cls)
364 struct Plugin *plugin = cls;
367 struct GNUNET_HashCode key;
370 res = PQexecPrepared (plugin->dbh,
372 0, NULL, NULL, NULL, 1);
374 GNUNET_POSTGRES_check_result (plugin->dbh,
380 LOG (GNUNET_ERROR_TYPE_DEBUG,
381 "Ending iteration (postgres error)\n");
384 if (0 == PQntuples (res))
387 LOG (GNUNET_ERROR_TYPE_DEBUG,
388 "Ending iteration (no more results)\n");
390 return GNUNET_SYSERR;
392 if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) ||
393 (sizeof (oid) != PQfsize (res, 1)) ||
394 (sizeof (struct GNUNET_HashCode) != PQgetlength (res, 0, 2)))
400 size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
401 oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
402 GNUNET_memcpy (&key, PQgetvalue (res, 0, 2), sizeof (struct GNUNET_HashCode));
405 GNUNET_POSTGRES_delete_by_rowid (plugin->dbh,
408 return GNUNET_SYSERR;
410 plugin->env->delete_notify (plugin->env->cls,
418 * Obtain a random key-value pair from the datacache.
420 * @param cls closure (our `struct Plugin`)
421 * @param iter maybe NULL (to just count)
422 * @param iter_cls closure for @a iter
423 * @return the number of results found, zero (datacache empty) or one
426 postgres_plugin_get_random (void *cls,
427 GNUNET_DATACACHE_Iterator iter,
430 struct Plugin *plugin = cls;
433 struct GNUNET_TIME_Absolute expiration_time;
435 unsigned int path_len;
436 const struct GNUNET_PeerIdentity *path;
437 const struct GNUNET_HashCode *key;
440 const char *paramValues[] = {
441 (const char *) &off_be
443 int paramLengths[] = {
446 const int paramFormats[] = { 1 };
448 if (0 == plugin->num_items)
452 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
454 off_be = htonl (off);
456 PQexecPrepared (plugin->dbh, "get_random",
457 1, paramValues, paramLengths, paramFormats,
460 GNUNET_POSTGRES_check_result (plugin->dbh,
469 if (0 == PQntuples (res))
474 if ( (5 != PQnfields (res)) ||
475 (sizeof (uint64_t) != PQfsize (res, 0)) ||
476 (sizeof (uint32_t) != PQfsize (res, 1)) ||
477 (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
483 expiration_time.abs_value_us =
484 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, 0, 0));
485 type = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
486 size = PQgetlength (res, 0, 2);
487 path_len = PQgetlength (res, 0, 3);
488 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
493 path_len %= sizeof (struct GNUNET_PeerIdentity);
494 path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, 0, 3);
495 key = (const struct GNUNET_HashCode *) PQgetvalue (res, 0, 4);
496 LOG (GNUNET_ERROR_TYPE_DEBUG,
497 "Found random value with key %s of size %u bytes and type %u in database\n",
500 (unsigned int) type);
501 (void) iter (iter_cls,
504 PQgetvalue (res, 0, 2),
505 (enum GNUNET_BLOCK_Type) type,
515 * Iterate over the results that are "close" to a particular key in
516 * the datacache. "close" is defined as numerically larger than @a
517 * key (when interpreted as a circular address space), with small
520 * @param cls closure (internal context for the plugin)
521 * @param key area of the keyspace to look into
522 * @param num_results number of results that should be returned to @a iter
523 * @param iter maybe NULL (to just count)
524 * @param iter_cls closure for @a iter
525 * @return the number of results found
528 postgres_plugin_get_closest (void *cls,
529 const struct GNUNET_HashCode *key,
530 unsigned int num_results,
531 GNUNET_DATACACHE_Iterator iter,
534 struct Plugin *plugin = cls;
535 uint32_t nbo_limit = htonl (num_results);
536 const char *paramValues[] = {
538 (const char *) &nbo_limit,
540 int paramLengths[] = {
541 sizeof (struct GNUNET_HashCode),
545 const int paramFormats[] = { 1, 1 };
546 struct GNUNET_TIME_Absolute expiration_time;
551 unsigned int path_len;
552 const struct GNUNET_PeerIdentity *path;
556 PQexecPrepared (plugin->dbh,
564 GNUNET_POSTGRES_check_result (plugin->dbh,
570 LOG (GNUNET_ERROR_TYPE_DEBUG,
571 "Ending iteration (postgres error)\n");
575 if (0 == (cnt = PQntuples (res)))
578 LOG (GNUNET_ERROR_TYPE_DEBUG,
579 "Ending iteration (no more results)\n");
588 if ( (5 != PQnfields (res)) ||
589 (sizeof (uint64_t) != PQfsize (res, 0)) ||
590 (sizeof (uint32_t) != PQfsize (res, 1)) ||
591 (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
597 for (i = 0; i < cnt; i++)
599 expiration_time.abs_value_us =
600 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
601 type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
602 size = PQgetlength (res, i, 2);
603 path_len = PQgetlength (res, i, 3);
604 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
609 path_len %= sizeof (struct GNUNET_PeerIdentity);
610 path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, i, 3);
611 key = (const struct GNUNET_HashCode *) PQgetvalue (res, i, 4);
612 LOG (GNUNET_ERROR_TYPE_DEBUG,
613 "Found result of size %u bytes and type %u in database\n",
615 (unsigned int) type);
620 PQgetvalue (res, i, 2),
621 (enum GNUNET_BLOCK_Type) type,
626 LOG (GNUNET_ERROR_TYPE_DEBUG,
627 "Ending iteration (client error)\n");
638 * Entry point for the plugin.
640 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
641 * @return the plugin's closure (our `struct Plugin`)
644 libgnunet_plugin_datacache_postgres_init (void *cls)
646 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
647 struct GNUNET_DATACACHE_PluginFunctions *api;
648 struct Plugin *plugin;
650 plugin = GNUNET_new (struct Plugin);
653 if (GNUNET_OK != init_connection (plugin))
655 GNUNET_free (plugin);
659 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
661 api->get = &postgres_plugin_get;
662 api->put = &postgres_plugin_put;
663 api->del = &postgres_plugin_del;
664 api->get_random = &postgres_plugin_get_random;
665 api->get_closest = &postgres_plugin_get_closest;
666 LOG (GNUNET_ERROR_TYPE_INFO,
667 "Postgres datacache running\n");
673 * Exit point from the plugin.
675 * @param cls closure (our `struct Plugin`)
679 libgnunet_plugin_datacache_postgres_done (void *cls)
681 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
682 struct Plugin *plugin = api->cls;
684 PQfinish (plugin->dbh);
685 GNUNET_free (plugin);
692 /* end of plugin_datacache_postgres.c */