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 it
6 under the terms of the GNU General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your 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 Affero General Public License for more details.
17 * @file datacache/plugin_datacache_postgres.c
18 * @brief postgres for an implementation of a database backend for the datacache
19 * @author Christian Grothoff
22 #include "gnunet_util_lib.h"
23 #include "gnunet_pq_lib.h"
24 #include "gnunet_datacache_plugin.h"
26 #define LOG(kind,...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
29 * Per-entry overhead estimate
31 #define OVERHEAD (sizeof(struct GNUNET_HashCode) + 24)
34 * Context for all functions in this plugin.
39 * Our execution environment.
41 struct GNUNET_DATACACHE_PluginEnvironment *env;
44 * Native Postgres database handle.
49 * Number of key-value pairs in the database.
51 unsigned int num_items;
56 * @brief Get a database handle
58 * @param plugin global context
59 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
62 init_connection (struct Plugin *plugin)
64 struct GNUNET_PQ_ExecuteStatement es[] = {
65 GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS gn090dc ("
66 " type INTEGER NOT NULL,"
67 " discard_time BIGINT NOT NULL,"
68 " key BYTEA NOT NULL,"
69 " value BYTEA NOT NULL,"
70 " path BYTEA DEFAULT NULL)"
72 GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS idx_key ON gn090dc (key)"),
73 GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS idx_dt ON gn090dc (discard_time)"),
74 GNUNET_PQ_make_execute ("ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL"),
75 GNUNET_PQ_make_execute ("ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN"),
76 GNUNET_PQ_EXECUTE_STATEMENT_END
78 struct GNUNET_PQ_PreparedStatement ps[] = {
79 GNUNET_PQ_make_prepare ("getkt",
80 "SELECT discard_time,type,value,path FROM gn090dc "
81 "WHERE key=$1 AND type=$2",
83 GNUNET_PQ_make_prepare ("getk",
84 "SELECT discard_time,type,value,path FROM gn090dc "
87 GNUNET_PQ_make_prepare ("getm",
88 "SELECT length(value) AS len,oid,key FROM gn090dc "
89 "ORDER BY discard_time ASC LIMIT 1",
91 GNUNET_PQ_make_prepare ("get_random",
92 "SELECT discard_time,type,value,path,key FROM gn090dc "
93 "ORDER BY key ASC LIMIT 1 OFFSET $1",
95 GNUNET_PQ_make_prepare ("get_closest",
96 "SELECT discard_time,type,value,path,key FROM gn090dc "
97 "WHERE key>=$1 ORDER BY key ASC LIMIT $2",
99 GNUNET_PQ_make_prepare ("delrow",
100 "DELETE FROM gn090dc WHERE oid=$1",
102 GNUNET_PQ_make_prepare ("put",
103 "INSERT INTO gn090dc (type, discard_time, key, value, path) "
104 "VALUES ($1, $2, $3, $4, $5)",
106 GNUNET_PQ_PREPARED_STATEMENT_END
109 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
110 "datacache-postgres");
111 if (NULL == plugin->dbh)
112 return GNUNET_SYSERR;
114 GNUNET_PQ_exec_statements (plugin->dbh,
117 PQfinish (plugin->dbh);
119 return GNUNET_SYSERR;
123 GNUNET_PQ_prepare_statements (plugin->dbh,
126 PQfinish (plugin->dbh);
128 return GNUNET_SYSERR;
135 * Store an item in the datastore.
137 * @param cls closure (our `struct Plugin`)
138 * @param key key to store @a data under
139 * @param am_closest are we the closest peer?
140 * @param data_size number of bytes in @a data
141 * @param data data to store
142 * @param type type of the value
143 * @param discard_time when to discard the value in any case
144 * @param path_info_len number of entries in @a path_info
145 * @param path_info a path through the network
146 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
149 postgres_plugin_put (void *cls,
150 const struct GNUNET_HashCode *key,
154 enum GNUNET_BLOCK_Type type,
155 struct GNUNET_TIME_Absolute discard_time,
156 unsigned int path_info_len,
157 const struct GNUNET_PeerIdentity *path_info)
159 struct Plugin *plugin = cls;
160 uint32_t type32 = (uint32_t) type;
161 struct GNUNET_PQ_QueryParam params[] = {
162 GNUNET_PQ_query_param_uint32 (&type32),
163 GNUNET_PQ_query_param_absolute_time (&discard_time),
164 GNUNET_PQ_query_param_auto_from_type (key),
165 GNUNET_PQ_query_param_fixed_size (data, data_size),
166 GNUNET_PQ_query_param_fixed_size (path_info,
167 path_info_len * sizeof (struct GNUNET_PeerIdentity)),
168 GNUNET_PQ_query_param_end
170 enum GNUNET_DB_QueryStatus ret;
172 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
178 return data_size + OVERHEAD;
183 * Closure for #handle_results.
185 struct HandleResultContext
189 * Function to call on each result, may be NULL.
191 GNUNET_DATACACHE_Iterator iter;
194 * Closure for @e iter.
201 const struct GNUNET_HashCode *key;
206 * Function to be called with the results of a SELECT statement
207 * that has returned @a num_results results. Parse the result
208 * and call the callback given in @a cls
210 * @param cls closure of type `struct HandleResultContext`
211 * @param result the postgres result
212 * @param num_result the number of results in @a result
215 handle_results (void *cls,
217 unsigned int num_results)
219 struct HandleResultContext *hrc = cls;
221 for (unsigned int i=0;i<num_results;i++)
223 struct GNUNET_TIME_Absolute expiration_time;
227 struct GNUNET_PeerIdentity *path;
229 struct GNUNET_PQ_ResultSpec rs[] = {
230 GNUNET_PQ_result_spec_absolute_time ("discard_time",
232 GNUNET_PQ_result_spec_uint32 ("type",
234 GNUNET_PQ_result_spec_variable_size ("value",
237 GNUNET_PQ_result_spec_variable_size ("path",
240 GNUNET_PQ_result_spec_end
244 GNUNET_PQ_extract_result (result,
251 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
256 path_len %= sizeof (struct GNUNET_PeerIdentity);
257 LOG (GNUNET_ERROR_TYPE_DEBUG,
258 "Found result of size %u bytes and type %u in database\n",
259 (unsigned int) data_size,
260 (unsigned int) type);
261 if ( (NULL != hrc->iter) &&
263 hrc->iter (hrc->iter_cls,
267 (enum GNUNET_BLOCK_Type) type,
272 LOG (GNUNET_ERROR_TYPE_DEBUG,
273 "Ending iteration (client error)\n");
274 GNUNET_PQ_cleanup_result (rs);
277 GNUNET_PQ_cleanup_result (rs);
283 * Iterate over the results for a particular key
286 * @param cls closure (our `struct Plugin`)
287 * @param key key to look for
288 * @param type entries of which type are relevant?
289 * @param iter maybe NULL (to just count)
290 * @param iter_cls closure for @a iter
291 * @return the number of results found
294 postgres_plugin_get (void *cls,
295 const struct GNUNET_HashCode *key,
296 enum GNUNET_BLOCK_Type type,
297 GNUNET_DATACACHE_Iterator iter,
300 struct Plugin *plugin = cls;
301 uint32_t type32 = (uint32_t) type;
302 struct GNUNET_PQ_QueryParam paramk[] = {
303 GNUNET_PQ_query_param_auto_from_type (key),
304 GNUNET_PQ_query_param_end
306 struct GNUNET_PQ_QueryParam paramkt[] = {
307 GNUNET_PQ_query_param_auto_from_type (key),
308 GNUNET_PQ_query_param_uint32 (&type32),
309 GNUNET_PQ_query_param_end
311 enum GNUNET_DB_QueryStatus res;
312 struct HandleResultContext hr_ctx;
315 hr_ctx.iter_cls = iter_cls;
317 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
318 (0 == type) ? "getk" : "getkt",
319 (0 == type) ? paramk : paramkt,
329 * Delete the entry with the lowest expiration value
330 * from the datacache right now.
332 * @param cls closure (our `struct Plugin`)
333 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
336 postgres_plugin_del (void *cls)
338 struct Plugin *plugin = cls;
339 struct GNUNET_PQ_QueryParam pempty[] = {
340 GNUNET_PQ_query_param_end
344 struct GNUNET_HashCode key;
345 struct GNUNET_PQ_ResultSpec rs[] = {
346 GNUNET_PQ_result_spec_uint32 ("len",
348 GNUNET_PQ_result_spec_uint32 ("oid",
350 GNUNET_PQ_result_spec_auto_from_type ("key",
352 GNUNET_PQ_result_spec_end
354 enum GNUNET_DB_QueryStatus res;
355 struct GNUNET_PQ_QueryParam dparam[] = {
356 GNUNET_PQ_query_param_uint32 (&oid),
357 GNUNET_PQ_query_param_end
360 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
365 return GNUNET_SYSERR;
366 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
369 LOG (GNUNET_ERROR_TYPE_DEBUG,
370 "Ending iteration (no more results)\n");
373 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
378 GNUNET_PQ_cleanup_result (rs);
379 return GNUNET_SYSERR;
382 plugin->env->delete_notify (plugin->env->cls,
385 GNUNET_PQ_cleanup_result (rs);
391 * Obtain a random key-value pair from the datacache.
393 * @param cls closure (our `struct Plugin`)
394 * @param iter maybe NULL (to just count)
395 * @param iter_cls closure for @a iter
396 * @return the number of results found, zero (datacache empty) or one
399 postgres_plugin_get_random (void *cls,
400 GNUNET_DATACACHE_Iterator iter,
403 struct Plugin *plugin = cls;
405 struct GNUNET_TIME_Absolute expiration_time;
409 struct GNUNET_PeerIdentity *path;
410 struct GNUNET_HashCode key;
412 enum GNUNET_DB_QueryStatus res;
413 struct GNUNET_PQ_QueryParam params[] = {
414 GNUNET_PQ_query_param_uint32 (&off),
415 GNUNET_PQ_query_param_end
417 struct GNUNET_PQ_ResultSpec rs[] = {
418 GNUNET_PQ_result_spec_absolute_time ("discard_time",
420 GNUNET_PQ_result_spec_uint32 ("type",
422 GNUNET_PQ_result_spec_variable_size ("value",
425 GNUNET_PQ_result_spec_variable_size ("path",
428 GNUNET_PQ_result_spec_auto_from_type ("key",
430 GNUNET_PQ_result_spec_end
433 if (0 == plugin->num_items)
437 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
439 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
448 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
453 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
458 path_len %= sizeof (struct GNUNET_PeerIdentity);
459 LOG (GNUNET_ERROR_TYPE_DEBUG,
460 "Found random value with key %s of size %u bytes and type %u in database\n",
462 (unsigned int) data_size,
463 (unsigned int) type);
464 (void) iter (iter_cls,
468 (enum GNUNET_BLOCK_Type) type,
472 GNUNET_PQ_cleanup_result (rs);
478 * Closure for #extract_result_cb.
480 struct ExtractResultContext
483 * Function to call for each result found.
485 GNUNET_DATACACHE_Iterator iter;
488 * Closure for @e iter.
496 * Function to be called with the results of a SELECT statement
497 * that has returned @a num_results results. Calls the `iter`
498 * from @a cls for each result.
500 * @param cls closure with the `struct ExtractResultContext`
501 * @param result the postgres result
502 * @param num_result the number of results in @a result
505 extract_result_cb (void *cls,
507 unsigned int num_results)
509 struct ExtractResultContext *erc = cls;
511 if (NULL == erc->iter)
513 for (unsigned int i=0;i<num_results;i++)
515 struct GNUNET_TIME_Absolute expiration_time;
519 struct GNUNET_PeerIdentity *path;
521 struct GNUNET_HashCode key;
522 struct GNUNET_PQ_ResultSpec rs[] = {
523 GNUNET_PQ_result_spec_absolute_time ("",
525 GNUNET_PQ_result_spec_uint32 ("type",
527 GNUNET_PQ_result_spec_variable_size ("value",
530 GNUNET_PQ_result_spec_variable_size ("path",
533 GNUNET_PQ_result_spec_auto_from_type ("key",
535 GNUNET_PQ_result_spec_end
539 GNUNET_PQ_extract_result (result,
546 if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
551 path_len %= sizeof (struct GNUNET_PeerIdentity);
552 LOG (GNUNET_ERROR_TYPE_DEBUG,
553 "Found result of size %u bytes and type %u in database\n",
554 (unsigned int) data_size,
555 (unsigned int) type);
557 erc->iter (erc->iter_cls,
561 (enum GNUNET_BLOCK_Type) type,
566 LOG (GNUNET_ERROR_TYPE_DEBUG,
567 "Ending iteration (client error)\n");
568 GNUNET_PQ_cleanup_result (rs);
571 GNUNET_PQ_cleanup_result (rs);
577 * Iterate over the results that are "close" to a particular key in
578 * the datacache. "close" is defined as numerically larger than @a
579 * key (when interpreted as a circular address space), with small
582 * @param cls closure (internal context for the plugin)
583 * @param key area of the keyspace to look into
584 * @param num_results number of results that should be returned to @a iter
585 * @param iter maybe NULL (to just count)
586 * @param iter_cls closure for @a iter
587 * @return the number of results found
590 postgres_plugin_get_closest (void *cls,
591 const struct GNUNET_HashCode *key,
592 unsigned int num_results,
593 GNUNET_DATACACHE_Iterator iter,
596 struct Plugin *plugin = cls;
597 uint32_t num_results32 = (uint32_t) num_results;
598 struct GNUNET_PQ_QueryParam params[] = {
599 GNUNET_PQ_query_param_auto_from_type (key),
600 GNUNET_PQ_query_param_uint32 (&num_results32),
601 GNUNET_PQ_query_param_end
603 enum GNUNET_DB_QueryStatus res;
604 struct ExtractResultContext erc;
607 erc.iter_cls = iter_cls;
608 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
615 LOG (GNUNET_ERROR_TYPE_DEBUG,
616 "Ending iteration (postgres error)\n");
619 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
622 LOG (GNUNET_ERROR_TYPE_DEBUG,
623 "Ending iteration (no more results)\n");
631 * Entry point for the plugin.
633 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
634 * @return the plugin's closure (our `struct Plugin`)
637 libgnunet_plugin_datacache_postgres_init (void *cls)
639 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
640 struct GNUNET_DATACACHE_PluginFunctions *api;
641 struct Plugin *plugin;
643 plugin = GNUNET_new (struct Plugin);
646 if (GNUNET_OK != init_connection (plugin))
648 GNUNET_free (plugin);
652 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
654 api->get = &postgres_plugin_get;
655 api->put = &postgres_plugin_put;
656 api->del = &postgres_plugin_del;
657 api->get_random = &postgres_plugin_get_random;
658 api->get_closest = &postgres_plugin_get_closest;
659 LOG (GNUNET_ERROR_TYPE_INFO,
660 "Postgres datacache running\n");
666 * Exit point from the plugin.
668 * @param cls closure (our `struct Plugin`)
672 libgnunet_plugin_datacache_postgres_done (void *cls)
674 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
675 struct Plugin *plugin = api->cls;
677 PQfinish (plugin->dbh);
678 GNUNET_free (plugin);
685 /* end of plugin_datacache_postgres.c */