2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2010, 2012, 2015 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, 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 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 idx_key ON gn090dc (key)")) ||
105 GNUNET_POSTGRES_exec (plugin->dbh,
106 "CREATE INDEX 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 "DELETE FROM gn090dc WHERE oid=$1", 1)) ||
169 GNUNET_POSTGRES_prepare (plugin->dbh,
171 "INSERT INTO gn090dc (type, discard_time, key, value, path) "
172 "VALUES ($1, $2, $3, $4, $5)", 5)))
174 PQfinish (plugin->dbh);
176 return GNUNET_SYSERR;
183 * Store an item in the datastore.
185 * @param cls closure (our `struct Plugin`)
186 * @param key key to store @a data under
187 * @param size number of bytes in @a data
188 * @param data data to store
189 * @param type type of the value
190 * @param discard_time when to discard the value in any case
191 * @param path_info_len number of entries in @a path_info
192 * @param path_info a path through the network
193 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
196 postgres_plugin_put (void *cls,
197 const struct GNUNET_HashCode *key,
200 enum GNUNET_BLOCK_Type type,
201 struct GNUNET_TIME_Absolute discard_time,
202 unsigned int path_info_len,
203 const struct GNUNET_PeerIdentity *path_info)
205 struct Plugin *plugin = cls;
207 uint32_t btype = htonl (type);
208 uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value_us__;
210 const char *paramValues[] = {
211 (const char *) &btype,
212 (const char *) &bexpi,
215 (const char *) path_info
217 int paramLengths[] = {
220 sizeof (struct GNUNET_HashCode),
222 path_info_len * sizeof (struct GNUNET_PeerIdentity)
224 const int paramFormats[] = { 1, 1, 1, 1, 1 };
227 PQexecPrepared (plugin->dbh, "put", 5, paramValues, paramLengths,
230 GNUNET_POSTGRES_check_result (plugin->dbh, ret,
231 PGRES_COMMAND_OK, "PQexecPrepared", "put"))
235 return size + OVERHEAD;
240 * Iterate over the results for a particular key
243 * @param cls closure (our `struct Plugin`)
244 * @param key key to look for
245 * @param type entries of which type are relevant?
246 * @param iter maybe NULL (to just count)
247 * @param iter_cls closure for @a iter
248 * @return the number of results found
251 postgres_plugin_get (void *cls,
252 const struct GNUNET_HashCode *key,
253 enum GNUNET_BLOCK_Type type,
254 GNUNET_DATACACHE_Iterator iter,
257 struct Plugin *plugin = cls;
258 uint32_t btype = htonl (type);
260 const char *paramValues[] = {
262 (const char *) &btype,
264 int paramLengths[] = {
265 sizeof (struct GNUNET_HashCode),
268 const int paramFormats[] = { 1, 1 };
269 struct GNUNET_TIME_Absolute expiration_time;
273 unsigned int path_len;
274 const struct GNUNET_PeerIdentity *path;
278 PQexecPrepared (plugin->dbh, (type == 0) ? "getk" : "getkt",
279 (type == 0) ? 1 : 2, paramValues, paramLengths,
282 GNUNET_POSTGRES_check_result (plugin->dbh,
286 (type == 0) ? "getk" : "getkt"))
288 LOG (GNUNET_ERROR_TYPE_DEBUG,
289 "Ending iteration (postgres error)\n");
293 if (0 == (cnt = PQntuples (res)))
296 LOG (GNUNET_ERROR_TYPE_DEBUG,
297 "Ending iteration (no more results)\n");
306 if ( (4 != PQnfields (res)) ||
307 (sizeof (uint64_t) != PQfsize (res, 0)) ||
308 (sizeof (uint32_t) != PQfsize (res, 1)))
314 for (i = 0; i < cnt; i++)
316 expiration_time.abs_value_us =
317 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
318 type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
319 size = PQgetlength (res, i, 2);
320 path_len = PQgetlength (res, i, 3);
321 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
326 path_len %= sizeof (struct GNUNET_PeerIdentity);
327 path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, i, 3);
328 LOG (GNUNET_ERROR_TYPE_DEBUG,
329 "Found result of size %u bytes and type %u in database\n",
330 (unsigned int) size, (unsigned int) type);
332 iter (iter_cls, key, size, PQgetvalue (res, i, 2),
333 (enum GNUNET_BLOCK_Type) type,
338 LOG (GNUNET_ERROR_TYPE_DEBUG,
339 "Ending iteration (client error)\n");
350 * Delete the entry with the lowest expiration value
351 * from the datacache right now.
353 * @param cls closure (our `struct Plugin`)
354 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
357 postgres_plugin_del (void *cls)
359 struct Plugin *plugin = cls;
362 struct GNUNET_HashCode key;
365 res = PQexecPrepared (plugin->dbh,
367 0, NULL, NULL, NULL, 1);
369 GNUNET_POSTGRES_check_result (plugin->dbh,
375 LOG (GNUNET_ERROR_TYPE_DEBUG,
376 "Ending iteration (postgres error)\n");
379 if (0 == PQntuples (res))
382 LOG (GNUNET_ERROR_TYPE_DEBUG,
383 "Ending iteration (no more results)\n");
385 return GNUNET_SYSERR;
387 if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) ||
388 (sizeof (oid) != PQfsize (res, 1)) ||
389 (sizeof (struct GNUNET_HashCode) != PQgetlength (res, 0, 2)))
395 size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
396 oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
397 memcpy (&key, PQgetvalue (res, 0, 2), sizeof (struct GNUNET_HashCode));
400 GNUNET_POSTGRES_delete_by_rowid (plugin->dbh,
403 return GNUNET_SYSERR;
405 plugin->env->delete_notify (plugin->env->cls,
413 * Obtain a random key-value pair from the datacache.
415 * @param cls closure (our `struct Plugin`)
416 * @param iter maybe NULL (to just count)
417 * @param iter_cls closure for @a iter
418 * @return the number of results found, zero (datacache empty) or one
421 postgres_plugin_get_random (void *cls,
422 GNUNET_DATACACHE_Iterator iter,
425 struct Plugin *plugin = cls;
428 struct GNUNET_TIME_Absolute expiration_time;
430 unsigned int path_len;
431 const struct GNUNET_PeerIdentity *path;
432 const struct GNUNET_HashCode *key;
435 const char *paramValues[] = {
436 (const char *) &off_be,
438 int paramLengths[] = {
441 const int paramFormats[] = { 1 };
443 if (0 == plugin->num_items)
447 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
449 off_be = htonl (off);
451 PQexecPrepared (plugin->dbh, "get_random",
452 1, paramValues, paramLengths, paramFormats,
455 GNUNET_POSTGRES_check_result (plugin->dbh,
464 if (0 == PQntuples (res))
469 if ( (5 != PQnfields (res)) ||
470 (sizeof (uint64_t) != PQfsize (res, 0)) ||
471 (sizeof (uint32_t) != PQfsize (res, 1)) ||
472 (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
478 expiration_time.abs_value_us =
479 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, 0, 0));
480 type = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
481 size = PQgetlength (res, 0, 2);
482 path_len = PQgetlength (res, 0, 3);
483 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
488 path_len %= sizeof (struct GNUNET_PeerIdentity);
489 path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, 0, 3);
490 key = (const struct GNUNET_HashCode *) PQgetvalue (res, 0, 4);
491 LOG (GNUNET_ERROR_TYPE_DEBUG,
492 "Found random value with key %s of size %u bytes and type %u in database\n",
495 (unsigned int) type);
496 (void) iter (iter_cls,
499 PQgetvalue (res, 0, 2),
500 (enum GNUNET_BLOCK_Type) type,
510 * Entry point for the plugin.
512 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
513 * @return the plugin's closure (our `struct Plugin`)
516 libgnunet_plugin_datacache_postgres_init (void *cls)
518 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
519 struct GNUNET_DATACACHE_PluginFunctions *api;
520 struct Plugin *plugin;
522 plugin = GNUNET_new (struct Plugin);
525 if (GNUNET_OK != init_connection (plugin))
527 GNUNET_free (plugin);
531 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
533 api->get = &postgres_plugin_get;
534 api->put = &postgres_plugin_put;
535 api->del = &postgres_plugin_del;
536 api->get_random = &postgres_plugin_get_random;
537 LOG (GNUNET_ERROR_TYPE_INFO,
538 "Postgres datacache running\n");
544 * Exit point from the plugin.
546 * @param cls closure (our `struct Plugin`)
550 libgnunet_plugin_datacache_postgres_done (void *cls)
552 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
553 struct Plugin *plugin = api->cls;
555 PQfinish (plugin->dbh);
556 GNUNET_free (plugin);
563 /* end of plugin_datacache_postgres.c */