2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2010, 2012, 2015, 2017 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_pq_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)
69 struct GNUNET_PQ_ExecuteStatement es[] = {
70 GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS gn090dc ("
71 " type INTEGER NOT NULL,"
72 " discard_time BIGINT NOT NULL,"
73 " key BYTEA NOT NULL,"
74 " value BYTEA NOT NULL,"
75 " path BYTEA DEFAULT NULL)"
77 GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS idx_key ON gn090dc (key)"),
78 GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS idx_dt ON gn090dc (discard_time)"),
79 GNUNET_PQ_make_execute ("ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL"),
80 GNUNET_PQ_make_execute ("ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN"),
81 GNUNET_PQ_EXECUTE_STATEMENT_END
83 struct GNUNET_PQ_PreparedStatement ps[] = {
84 GNUNET_PQ_make_prepare ("getkt",
85 "SELECT discard_time,type,value,path FROM gn090dc "
86 "WHERE key=$1 AND type=$2",
88 GNUNET_PQ_make_prepare ("getk",
89 "SELECT discard_time,type,value,path FROM gn090dc "
92 GNUNET_PQ_make_prepare ("getm",
93 "SELECT length(value) AS len,oid,key FROM gn090dc "
94 "ORDER BY discard_time ASC LIMIT 1",
96 GNUNET_PQ_make_prepare ("get_random",
97 "SELECT discard_time,type,value,path,key FROM gn090dc "
98 "ORDER BY key ASC LIMIT 1 OFFSET $1",
100 GNUNET_PQ_make_prepare ("get_closest",
101 "SELECT discard_time,type,value,path,key FROM gn090dc "
102 "WHERE key>=$1 ORDER BY key ASC LIMIT $2",
104 GNUNET_PQ_make_prepare ("delrow",
105 "DELETE FROM gn090dc WHERE oid=$1",
107 GNUNET_PQ_make_prepare ("put",
108 "INSERT INTO gn090dc (type, discard_time, key, value, path) "
109 "VALUES ($1, $2, $3, $4, $5)",
111 GNUNET_PQ_PREPARED_STATEMENT_END
114 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
115 "datacache-postgres");
116 if (NULL == plugin->dbh)
117 return GNUNET_SYSERR;
119 GNUNET_PQ_exec_statements (plugin->dbh,
122 PQfinish (plugin->dbh);
124 return GNUNET_SYSERR;
128 GNUNET_PQ_prepare_statements (plugin->dbh,
131 PQfinish (plugin->dbh);
133 return GNUNET_SYSERR;
140 * Store an item in the datastore.
142 * @param cls closure (our `struct Plugin`)
143 * @param key key to store @a data under
144 * @param data_size number of bytes in @a data
145 * @param data data to store
146 * @param type type of the value
147 * @param discard_time when to discard the value in any case
148 * @param path_info_len number of entries in @a path_info
149 * @param path_info a path through the network
150 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
153 postgres_plugin_put (void *cls,
154 const struct GNUNET_HashCode *key,
157 enum GNUNET_BLOCK_Type type,
158 struct GNUNET_TIME_Absolute discard_time,
159 unsigned int path_info_len,
160 const struct GNUNET_PeerIdentity *path_info)
162 struct Plugin *plugin = cls;
163 uint32_t type32 = (uint32_t) type;
164 struct GNUNET_PQ_QueryParam params[] = {
165 GNUNET_PQ_query_param_uint32 (&type32),
166 GNUNET_PQ_query_param_absolute_time (&discard_time),
167 GNUNET_PQ_query_param_auto_from_type (key),
168 GNUNET_PQ_query_param_fixed_size (data, data_size),
169 GNUNET_PQ_query_param_fixed_size (path_info,
170 path_info_len * sizeof (struct GNUNET_PeerIdentity)),
171 GNUNET_PQ_query_param_end
173 enum GNUNET_DB_QueryStatus ret;
175 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
181 return data_size + OVERHEAD;
186 * Closure for #handle_results.
188 struct HandleResultContext
192 * Function to call on each result, may be NULL.
194 GNUNET_DATACACHE_Iterator iter;
197 * Closure for @e iter.
204 const struct GNUNET_HashCode *key;
209 * Function to be called with the results of a SELECT statement
210 * that has returned @a num_results results. Parse the result
211 * and call the callback given in @a cls
213 * @param cls closure of type `struct HandleResultContext`
214 * @param result the postgres result
215 * @param num_result the number of results in @a result
218 handle_results (void *cls,
220 unsigned int num_results)
222 struct HandleResultContext *hrc = cls;
224 for (unsigned int i=0;i<num_results;i++)
226 struct GNUNET_TIME_Absolute expiration_time;
230 struct GNUNET_PeerIdentity *path;
232 struct GNUNET_PQ_ResultSpec rs[] = {
233 GNUNET_PQ_result_spec_absolute_time ("discard_time",
235 GNUNET_PQ_result_spec_uint32 ("type",
237 GNUNET_PQ_result_spec_variable_size ("value",
240 GNUNET_PQ_result_spec_variable_size ("path",
243 GNUNET_PQ_result_spec_end
247 GNUNET_PQ_extract_result (result,
254 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
259 path_len %= sizeof (struct GNUNET_PeerIdentity);
260 LOG (GNUNET_ERROR_TYPE_DEBUG,
261 "Found result of size %u bytes and type %u in database\n",
262 (unsigned int) data_size,
263 (unsigned int) type);
264 if ( (NULL != hrc->iter) &&
266 hrc->iter (hrc->iter_cls,
270 (enum GNUNET_BLOCK_Type) type,
275 LOG (GNUNET_ERROR_TYPE_DEBUG,
276 "Ending iteration (client error)\n");
277 GNUNET_PQ_cleanup_result (rs);
280 GNUNET_PQ_cleanup_result (rs);
286 * Iterate over the results for a particular key
289 * @param cls closure (our `struct Plugin`)
290 * @param key key to look for
291 * @param type entries of which type are relevant?
292 * @param iter maybe NULL (to just count)
293 * @param iter_cls closure for @a iter
294 * @return the number of results found
297 postgres_plugin_get (void *cls,
298 const struct GNUNET_HashCode *key,
299 enum GNUNET_BLOCK_Type type,
300 GNUNET_DATACACHE_Iterator iter,
303 struct Plugin *plugin = cls;
304 uint32_t type32 = (uint32_t) type;
305 struct GNUNET_PQ_QueryParam paramk[] = {
306 GNUNET_PQ_query_param_auto_from_type (key),
307 GNUNET_PQ_query_param_end
309 struct GNUNET_PQ_QueryParam paramkt[] = {
310 GNUNET_PQ_query_param_auto_from_type (key),
311 GNUNET_PQ_query_param_uint32 (&type32),
312 GNUNET_PQ_query_param_end
314 enum GNUNET_DB_QueryStatus res;
315 struct HandleResultContext hr_ctx;
318 hr_ctx.iter_cls = iter_cls;
320 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
321 (0 == type) ? "getk" : "getkt",
322 (0 == type) ? paramk : paramkt,
332 * Delete the entry with the lowest expiration value
333 * from the datacache right now.
335 * @param cls closure (our `struct Plugin`)
336 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
339 postgres_plugin_del (void *cls)
341 struct Plugin *plugin = cls;
342 struct GNUNET_PQ_QueryParam pempty[] = {
343 GNUNET_PQ_query_param_end
347 struct GNUNET_HashCode key;
348 struct GNUNET_PQ_ResultSpec rs[] = {
349 GNUNET_PQ_result_spec_uint32 ("len",
351 GNUNET_PQ_result_spec_uint32 ("oid",
353 GNUNET_PQ_result_spec_auto_from_type ("key",
355 GNUNET_PQ_result_spec_end
357 enum GNUNET_DB_QueryStatus res;
358 struct GNUNET_PQ_QueryParam dparam[] = {
359 GNUNET_PQ_query_param_uint32 (&oid),
360 GNUNET_PQ_query_param_end
363 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
368 return GNUNET_SYSERR;
369 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
372 LOG (GNUNET_ERROR_TYPE_DEBUG,
373 "Ending iteration (no more results)\n");
376 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
381 GNUNET_PQ_cleanup_result (rs);
382 return GNUNET_SYSERR;
385 plugin->env->delete_notify (plugin->env->cls,
388 GNUNET_PQ_cleanup_result (rs);
394 * Obtain a random key-value pair from the datacache.
396 * @param cls closure (our `struct Plugin`)
397 * @param iter maybe NULL (to just count)
398 * @param iter_cls closure for @a iter
399 * @return the number of results found, zero (datacache empty) or one
402 postgres_plugin_get_random (void *cls,
403 GNUNET_DATACACHE_Iterator iter,
406 struct Plugin *plugin = cls;
408 struct GNUNET_TIME_Absolute expiration_time;
412 struct GNUNET_PeerIdentity *path;
413 struct GNUNET_HashCode key;
415 enum GNUNET_DB_QueryStatus res;
416 struct GNUNET_PQ_QueryParam params[] = {
417 GNUNET_PQ_query_param_uint32 (&off),
418 GNUNET_PQ_query_param_end
420 struct GNUNET_PQ_ResultSpec rs[] = {
421 GNUNET_PQ_result_spec_absolute_time ("discard_time",
423 GNUNET_PQ_result_spec_uint32 ("type",
425 GNUNET_PQ_result_spec_variable_size ("value",
428 GNUNET_PQ_result_spec_variable_size ("path",
431 GNUNET_PQ_result_spec_auto_from_type ("key",
433 GNUNET_PQ_result_spec_end
436 if (0 == plugin->num_items)
440 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
442 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
451 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
456 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
461 path_len %= sizeof (struct GNUNET_PeerIdentity);
462 LOG (GNUNET_ERROR_TYPE_DEBUG,
463 "Found random value with key %s of size %u bytes and type %u in database\n",
465 (unsigned int) data_size,
466 (unsigned int) type);
467 (void) iter (iter_cls,
471 (enum GNUNET_BLOCK_Type) type,
475 GNUNET_PQ_cleanup_result (rs);
481 * Closure for #extract_result_cb.
483 struct ExtractResultContext
486 * Function to call for each result found.
488 GNUNET_DATACACHE_Iterator iter;
491 * Closure for @e iter.
499 * Function to be called with the results of a SELECT statement
500 * that has returned @a num_results results. Calls the `iter`
501 * from @a cls for each result.
503 * @param cls closure with the `struct ExtractResultContext`
504 * @param result the postgres result
505 * @param num_result the number of results in @a result
508 extract_result_cb (void *cls,
510 unsigned int num_results)
512 struct ExtractResultContext *erc = cls;
514 if (NULL == erc->iter)
516 for (unsigned int i=0;i<num_results;i++)
518 struct GNUNET_TIME_Absolute expiration_time;
522 struct GNUNET_PeerIdentity *path;
524 struct GNUNET_HashCode key;
525 struct GNUNET_PQ_ResultSpec rs[] = {
526 GNUNET_PQ_result_spec_absolute_time ("",
528 GNUNET_PQ_result_spec_uint32 ("type",
530 GNUNET_PQ_result_spec_variable_size ("value",
533 GNUNET_PQ_result_spec_variable_size ("path",
536 GNUNET_PQ_result_spec_auto_from_type ("key",
538 GNUNET_PQ_result_spec_end
542 GNUNET_PQ_extract_result (result,
549 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
554 path_len %= sizeof (struct GNUNET_PeerIdentity);
555 LOG (GNUNET_ERROR_TYPE_DEBUG,
556 "Found result of size %u bytes and type %u in database\n",
557 (unsigned int) data_size,
558 (unsigned int) type);
560 erc->iter (erc->iter_cls,
564 (enum GNUNET_BLOCK_Type) type,
569 LOG (GNUNET_ERROR_TYPE_DEBUG,
570 "Ending iteration (client error)\n");
571 GNUNET_PQ_cleanup_result (rs);
574 GNUNET_PQ_cleanup_result (rs);
580 * Iterate over the results that are "close" to a particular key in
581 * the datacache. "close" is defined as numerically larger than @a
582 * key (when interpreted as a circular address space), with small
585 * @param cls closure (internal context for the plugin)
586 * @param key area of the keyspace to look into
587 * @param num_results number of results that should be returned to @a iter
588 * @param iter maybe NULL (to just count)
589 * @param iter_cls closure for @a iter
590 * @return the number of results found
593 postgres_plugin_get_closest (void *cls,
594 const struct GNUNET_HashCode *key,
595 unsigned int num_results,
596 GNUNET_DATACACHE_Iterator iter,
599 struct Plugin *plugin = cls;
600 uint32_t num_results32 = (uint32_t) num_results;
601 struct GNUNET_PQ_QueryParam params[] = {
602 GNUNET_PQ_query_param_auto_from_type (key),
603 GNUNET_PQ_query_param_uint32 (&num_results32),
604 GNUNET_PQ_query_param_end
606 enum GNUNET_DB_QueryStatus res;
607 struct ExtractResultContext erc;
610 erc.iter_cls = iter_cls;
611 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
618 LOG (GNUNET_ERROR_TYPE_DEBUG,
619 "Ending iteration (postgres error)\n");
622 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
625 LOG (GNUNET_ERROR_TYPE_DEBUG,
626 "Ending iteration (no more results)\n");
634 * Entry point for the plugin.
636 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
637 * @return the plugin's closure (our `struct Plugin`)
640 libgnunet_plugin_datacache_postgres_init (void *cls)
642 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
643 struct GNUNET_DATACACHE_PluginFunctions *api;
644 struct Plugin *plugin;
646 plugin = GNUNET_new (struct Plugin);
649 if (GNUNET_OK != init_connection (plugin))
651 GNUNET_free (plugin);
655 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
657 api->get = &postgres_plugin_get;
658 api->put = &postgres_plugin_put;
659 api->del = &postgres_plugin_del;
660 api->get_random = &postgres_plugin_get_random;
661 api->get_closest = &postgres_plugin_get_closest;
662 LOG (GNUNET_ERROR_TYPE_INFO,
663 "Postgres datacache running\n");
669 * Exit point from the plugin.
671 * @param cls closure (our `struct Plugin`)
675 libgnunet_plugin_datacache_postgres_done (void *cls)
677 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
678 struct Plugin *plugin = api->cls;
680 PQfinish (plugin->dbh);
681 GNUNET_free (plugin);
688 /* end of plugin_datacache_postgres.c */