aboutsummaryrefslogtreecommitdiff
path: root/src/datacache/plugin_datacache_postgres.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/datacache/plugin_datacache_postgres.c')
-rw-r--r--src/datacache/plugin_datacache_postgres.c711
1 files changed, 0 insertions, 711 deletions
diff --git a/src/datacache/plugin_datacache_postgres.c b/src/datacache/plugin_datacache_postgres.c
deleted file mode 100644
index 6613ae928..000000000
--- a/src/datacache/plugin_datacache_postgres.c
+++ /dev/null
@@ -1,711 +0,0 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2010, 2012, 2015, 2017, 2018 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero 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.
9
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.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datacache/plugin_datacache_postgres.c
23 * @brief postgres for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_pq_lib.h"
29#include "gnunet_datacache_plugin.h"
30
31#define LOG(kind, ...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
32
33/**
34 * Per-entry overhead estimate
35 */
36#define OVERHEAD (sizeof(struct GNUNET_HashCode) + 24)
37
38/**
39 * Context for all functions in this plugin.
40 */
41struct Plugin
42{
43 /**
44 * Our execution environment.
45 */
46 struct GNUNET_DATACACHE_PluginEnvironment *env;
47
48 /**
49 * Native Postgres database handle.
50 */
51 struct GNUNET_PQ_Context *dbh;
52
53 /**
54 * Number of key-value pairs in the database.
55 */
56 unsigned int num_items;
57};
58
59
60/**
61 * @brief Get a database handle
62 *
63 * @param plugin global context
64 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
65 */
66static int
67init_connection (struct Plugin *plugin)
68{
69 struct GNUNET_PQ_ExecuteStatement es[] = {
70 GNUNET_PQ_make_try_execute ("CREATE TEMPORARY SEQUENCE IF NOT EXISTS gn011dc_oid_seq"),
71 GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS gn011dc ("
72 " oid OID NOT NULL DEFAULT nextval('gn011dc_oid_seq'),"
73 " type INTEGER NOT NULL,"
74 " prox INTEGER NOT NULL,"
75 " discard_time BIGINT NOT NULL,"
76 " key BYTEA NOT NULL,"
77 " value BYTEA NOT NULL,"
78 " path BYTEA DEFAULT NULL)"),
79 GNUNET_PQ_make_try_execute (
80 "ALTER SEQUENCE gnu011dc_oid_seq OWNED BY gn011dc.oid"),
81 GNUNET_PQ_make_try_execute (
82 "CREATE INDEX IF NOT EXISTS idx_oid ON gn011dc (oid)"),
83 GNUNET_PQ_make_try_execute (
84 "CREATE INDEX IF NOT EXISTS idx_key ON gn011dc (key)"),
85 GNUNET_PQ_make_try_execute (
86 "CREATE INDEX IF NOT EXISTS idx_dt ON gn011dc (discard_time)"),
87 GNUNET_PQ_make_execute (
88 "ALTER TABLE gn011dc ALTER value SET STORAGE EXTERNAL"),
89 GNUNET_PQ_make_execute ("ALTER TABLE gn011dc ALTER key SET STORAGE PLAIN"),
90 GNUNET_PQ_EXECUTE_STATEMENT_END
91 };
92 struct GNUNET_PQ_PreparedStatement ps[] = {
93 GNUNET_PQ_make_prepare ("getkt",
94 "SELECT discard_time,type,value,path FROM gn011dc "
95 "WHERE key=$1 AND type=$2 AND discard_time >= $3",
96 3),
97 GNUNET_PQ_make_prepare ("getk",
98 "SELECT discard_time,type,value,path FROM gn011dc "
99 "WHERE key=$1 AND discard_time >= $2",
100 2),
101 GNUNET_PQ_make_prepare ("getex",
102 "SELECT length(value) AS len,oid,key FROM gn011dc"
103 " WHERE discard_time < $1"
104 " ORDER BY discard_time ASC LIMIT 1",
105 1),
106 GNUNET_PQ_make_prepare ("getm",
107 "SELECT length(value) AS len,oid,key FROM gn011dc"
108 " ORDER BY prox ASC, discard_time ASC LIMIT 1",
109 0),
110 GNUNET_PQ_make_prepare ("get_random",
111 "SELECT discard_time,type,value,path,key FROM gn011dc"
112 " WHERE discard_time >= $1"
113 " ORDER BY key ASC LIMIT 1 OFFSET $2",
114 2),
115 GNUNET_PQ_make_prepare ("get_closest",
116 "SELECT discard_time,type,value,path,key FROM gn011dc "
117 "WHERE key>=$1 AND discard_time >= $2 ORDER BY key ASC LIMIT $3",
118 3),
119 GNUNET_PQ_make_prepare ("delrow",
120 "DELETE FROM gn011dc WHERE oid=$1",
121 1),
122 GNUNET_PQ_make_prepare ("put",
123 "INSERT INTO gn011dc (type, prox, discard_time, key, value, path) "
124 "VALUES ($1, $2, $3, $4, $5, $6)",
125 6),
126 GNUNET_PQ_PREPARED_STATEMENT_END
127 };
128
129 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
130 "datacache-postgres",
131 NULL,
132 es,
133 ps);
134 if (NULL == plugin->dbh)
135 return GNUNET_SYSERR;
136 return GNUNET_OK;
137}
138
139
140/**
141 * Store an item in the datastore.
142 *
143 * @param cls closure (our `struct Plugin`)
144 * @param key key to store @a data under
145 * @param prox proximity of @a key to my PID
146 * @param data_size number of bytes in @a data
147 * @param data data to store
148 * @param type type of the value
149 * @param discard_time when to discard the value in any case
150 * @param path_info_len number of entries in @a path_info
151 * @param path_info a path through the network
152 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
153 */
154static ssize_t
155postgres_plugin_put (void *cls,
156 const struct GNUNET_HashCode *key,
157 uint32_t prox,
158 size_t data_size,
159 const char *data,
160 enum GNUNET_BLOCK_Type type,
161 struct GNUNET_TIME_Absolute discard_time,
162 unsigned int path_info_len,
163 const struct GNUNET_PeerIdentity *path_info)
164{
165 struct Plugin *plugin = cls;
166 uint32_t type32 = (uint32_t) type;
167 struct GNUNET_PQ_QueryParam params[] = {
168 GNUNET_PQ_query_param_uint32 (&type32),
169 GNUNET_PQ_query_param_uint32 (&prox),
170 GNUNET_PQ_query_param_absolute_time (&discard_time),
171 GNUNET_PQ_query_param_auto_from_type (key),
172 GNUNET_PQ_query_param_fixed_size (data, data_size),
173 GNUNET_PQ_query_param_fixed_size (path_info,
174 path_info_len * sizeof(struct
175 GNUNET_PeerIdentity)),
176 GNUNET_PQ_query_param_end
177 };
178 enum GNUNET_DB_QueryStatus ret;
179
180 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
181 "put",
182 params);
183 if (0 > ret)
184 return -1;
185 plugin->num_items++;
186 return data_size + OVERHEAD;
187}
188
189
190/**
191 * Closure for #handle_results.
192 */
193struct HandleResultContext
194{
195 /**
196 * Function to call on each result, may be NULL.
197 */
198 GNUNET_DATACACHE_Iterator iter;
199
200 /**
201 * Closure for @e iter.
202 */
203 void *iter_cls;
204
205 /**
206 * Key used.
207 */
208 const struct GNUNET_HashCode *key;
209};
210
211
212/**
213 * Function to be called with the results of a SELECT statement
214 * that has returned @a num_results results. Parse the result
215 * and call the callback given in @a cls
216 *
217 * @param cls closure of type `struct HandleResultContext`
218 * @param result the postgres result
219 * @param num_result the number of results in @a result
220 */
221static void
222handle_results (void *cls,
223 PGresult *result,
224 unsigned int num_results)
225{
226 struct HandleResultContext *hrc = cls;
227
228 for (unsigned int i = 0; i < num_results; i++)
229 {
230 struct GNUNET_TIME_Absolute expiration_time;
231 uint32_t type;
232 void *data;
233 size_t data_size;
234 struct GNUNET_PeerIdentity *path;
235 size_t path_len;
236 struct GNUNET_PQ_ResultSpec rs[] = {
237 GNUNET_PQ_result_spec_absolute_time ("discard_time",
238 &expiration_time),
239 GNUNET_PQ_result_spec_uint32 ("type",
240 &type),
241 GNUNET_PQ_result_spec_variable_size ("value",
242 &data,
243 &data_size),
244 GNUNET_PQ_result_spec_variable_size ("path",
245 (void **) &path,
246 &path_len),
247 GNUNET_PQ_result_spec_end
248 };
249
250 if (GNUNET_YES !=
251 GNUNET_PQ_extract_result (result,
252 rs,
253 i))
254 {
255 GNUNET_break (0);
256 return;
257 }
258 if (0 != (path_len % sizeof(struct GNUNET_PeerIdentity)))
259 {
260 GNUNET_break (0);
261 path_len = 0;
262 }
263 path_len %= sizeof(struct GNUNET_PeerIdentity);
264 LOG (GNUNET_ERROR_TYPE_DEBUG,
265 "Found result of size %u bytes and type %u in database\n",
266 (unsigned int) data_size,
267 (unsigned int) type);
268 if ((NULL != hrc->iter) &&
269 (GNUNET_SYSERR ==
270 hrc->iter (hrc->iter_cls,
271 hrc->key,
272 data_size,
273 data,
274 (enum GNUNET_BLOCK_Type) type,
275 expiration_time,
276 path_len,
277 path)))
278 {
279 LOG (GNUNET_ERROR_TYPE_DEBUG,
280 "Ending iteration (client error)\n");
281 GNUNET_PQ_cleanup_result (rs);
282 return;
283 }
284 GNUNET_PQ_cleanup_result (rs);
285 }
286}
287
288
289/**
290 * Iterate over the results for a particular key
291 * in the datastore.
292 *
293 * @param cls closure (our `struct Plugin`)
294 * @param key key to look for
295 * @param type entries of which type are relevant?
296 * @param iter maybe NULL (to just count)
297 * @param iter_cls closure for @a iter
298 * @return the number of results found
299 */
300static unsigned int
301postgres_plugin_get (void *cls,
302 const struct GNUNET_HashCode *key,
303 enum GNUNET_BLOCK_Type type,
304 GNUNET_DATACACHE_Iterator iter,
305 void *iter_cls)
306{
307 struct Plugin *plugin = cls;
308 uint32_t type32 = (uint32_t) type;
309 struct GNUNET_TIME_Absolute now = { 0 };
310 struct GNUNET_PQ_QueryParam paramk[] = {
311 GNUNET_PQ_query_param_auto_from_type (key),
312 GNUNET_PQ_query_param_absolute_time (&now),
313 GNUNET_PQ_query_param_end
314 };
315 struct GNUNET_PQ_QueryParam paramkt[] = {
316 GNUNET_PQ_query_param_auto_from_type (key),
317 GNUNET_PQ_query_param_uint32 (&type32),
318 GNUNET_PQ_query_param_absolute_time (&now),
319 GNUNET_PQ_query_param_end
320 };
321 enum GNUNET_DB_QueryStatus res;
322 struct HandleResultContext hr_ctx;
323
324 now = GNUNET_TIME_absolute_get ();
325 hr_ctx.iter = iter;
326 hr_ctx.iter_cls = iter_cls;
327 hr_ctx.key = key;
328 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
329 (0 == type) ? "getk" : "getkt",
330 (0 == type) ? paramk : paramkt,
331 &handle_results,
332 &hr_ctx);
333 if (res < 0)
334 return 0;
335 return res;
336}
337
338
339/**
340 * Delete the entry with the lowest expiration value
341 * from the datacache right now.
342 *
343 * @param cls closure (our `struct Plugin`)
344 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
345 */
346static int
347postgres_plugin_del (void *cls)
348{
349 struct Plugin *plugin = cls;
350 struct GNUNET_PQ_QueryParam pempty[] = {
351 GNUNET_PQ_query_param_end
352 };
353 uint32_t size;
354 uint32_t oid;
355 struct GNUNET_HashCode key;
356 struct GNUNET_PQ_ResultSpec rs[] = {
357 GNUNET_PQ_result_spec_uint32 ("len",
358 &size),
359 GNUNET_PQ_result_spec_uint32 ("oid",
360 &oid),
361 GNUNET_PQ_result_spec_auto_from_type ("key",
362 &key),
363 GNUNET_PQ_result_spec_end
364 };
365 enum GNUNET_DB_QueryStatus res;
366 struct GNUNET_PQ_QueryParam dparam[] = {
367 GNUNET_PQ_query_param_uint32 (&oid),
368 GNUNET_PQ_query_param_end
369 };
370 struct GNUNET_TIME_Absolute now;
371 struct GNUNET_PQ_QueryParam xparam[] = {
372 GNUNET_PQ_query_param_absolute_time (&now),
373 GNUNET_PQ_query_param_end
374 };
375
376 now = GNUNET_TIME_absolute_get ();
377 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
378 "getex",
379 xparam,
380 rs);
381 if (0 >= res)
382 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
383 "getm",
384 pempty,
385 rs);
386 if (0 > res)
387 return GNUNET_SYSERR;
388 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
389 {
390 /* no result */
391 LOG (GNUNET_ERROR_TYPE_DEBUG,
392 "Ending iteration (no more results)\n");
393 return 0;
394 }
395 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
396 "delrow",
397 dparam);
398 if (0 > res)
399 {
400 GNUNET_PQ_cleanup_result (rs);
401 return GNUNET_SYSERR;
402 }
403 plugin->num_items--;
404 plugin->env->delete_notify (plugin->env->cls,
405 &key,
406 size + OVERHEAD);
407 GNUNET_PQ_cleanup_result (rs);
408 return GNUNET_OK;
409}
410
411
412/**
413 * Obtain a random key-value pair from the datacache.
414 *
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
419 */
420static unsigned int
421postgres_plugin_get_random (void *cls,
422 GNUNET_DATACACHE_Iterator iter,
423 void *iter_cls)
424{
425 struct Plugin *plugin = cls;
426 uint32_t off;
427 struct GNUNET_TIME_Absolute now = { 0 };
428 struct GNUNET_TIME_Absolute expiration_time;
429 size_t data_size;
430 void *data;
431 size_t path_len;
432 struct GNUNET_PeerIdentity *path;
433 struct GNUNET_HashCode key;
434 uint32_t type;
435 enum GNUNET_DB_QueryStatus res;
436 struct GNUNET_PQ_QueryParam params[] = {
437 GNUNET_PQ_query_param_absolute_time (&now),
438 GNUNET_PQ_query_param_uint32 (&off),
439 GNUNET_PQ_query_param_end
440 };
441 struct GNUNET_PQ_ResultSpec rs[] = {
442 GNUNET_PQ_result_spec_absolute_time ("discard_time",
443 &expiration_time),
444 GNUNET_PQ_result_spec_uint32 ("type",
445 &type),
446 GNUNET_PQ_result_spec_variable_size ("value",
447 &data,
448 &data_size),
449 GNUNET_PQ_result_spec_variable_size ("path",
450 (void **) &path,
451 &path_len),
452 GNUNET_PQ_result_spec_auto_from_type ("key",
453 &key),
454 GNUNET_PQ_result_spec_end
455 };
456
457 if (0 == plugin->num_items)
458 return 0;
459 if (NULL == iter)
460 return 1;
461 now = GNUNET_TIME_absolute_get ();
462 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
463 plugin->num_items);
464 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
465 "get_random",
466 params,
467 rs);
468 if (0 > res)
469 {
470 GNUNET_break (0);
471 return 0;
472 }
473 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
474 {
475 GNUNET_break (0);
476 return 0;
477 }
478 if (0 != (path_len % sizeof(struct GNUNET_PeerIdentity)))
479 {
480 GNUNET_break (0);
481 path_len = 0;
482 }
483 path_len %= sizeof(struct GNUNET_PeerIdentity);
484 LOG (GNUNET_ERROR_TYPE_DEBUG,
485 "Found random value with key %s of size %u bytes and type %u in database\n",
486 GNUNET_h2s (&key),
487 (unsigned int) data_size,
488 (unsigned int) type);
489 (void) iter (iter_cls,
490 &key,
491 data_size,
492 data,
493 (enum GNUNET_BLOCK_Type) type,
494 expiration_time,
495 path_len,
496 path);
497 GNUNET_PQ_cleanup_result (rs);
498 return 1;
499}
500
501
502/**
503 * Closure for #extract_result_cb.
504 */
505struct ExtractResultContext
506{
507 /**
508 * Function to call for each result found.
509 */
510 GNUNET_DATACACHE_Iterator iter;
511
512 /**
513 * Closure for @e iter.
514 */
515 void *iter_cls;
516};
517
518
519/**
520 * Function to be called with the results of a SELECT statement
521 * that has returned @a num_results results. Calls the `iter`
522 * from @a cls for each result.
523 *
524 * @param cls closure with the `struct ExtractResultContext`
525 * @param result the postgres result
526 * @param num_result the number of results in @a result
527 */
528static void
529extract_result_cb (void *cls,
530 PGresult *result,
531 unsigned int num_results)
532{
533 struct ExtractResultContext *erc = cls;
534
535 if (NULL == erc->iter)
536 return;
537 for (unsigned int i = 0; i < num_results; i++)
538 {
539 struct GNUNET_TIME_Absolute expiration_time;
540 uint32_t type;
541 void *data;
542 size_t data_size;
543 struct GNUNET_PeerIdentity *path;
544 size_t path_len;
545 struct GNUNET_HashCode key;
546 struct GNUNET_PQ_ResultSpec rs[] = {
547 GNUNET_PQ_result_spec_absolute_time ("",
548 &expiration_time),
549 GNUNET_PQ_result_spec_uint32 ("type",
550 &type),
551 GNUNET_PQ_result_spec_variable_size ("value",
552 &data,
553 &data_size),
554 GNUNET_PQ_result_spec_variable_size ("path",
555 (void **) &path,
556 &path_len),
557 GNUNET_PQ_result_spec_auto_from_type ("key",
558 &key),
559 GNUNET_PQ_result_spec_end
560 };
561
562 if (GNUNET_YES !=
563 GNUNET_PQ_extract_result (result,
564 rs,
565 i))
566 {
567 GNUNET_break (0);
568 return;
569 }
570 if (0 != (path_len % sizeof(struct GNUNET_PeerIdentity)))
571 {
572 GNUNET_break (0);
573 path_len = 0;
574 }
575 path_len %= sizeof(struct GNUNET_PeerIdentity);
576 LOG (GNUNET_ERROR_TYPE_DEBUG,
577 "Found result of size %u bytes and type %u in database\n",
578 (unsigned int) data_size,
579 (unsigned int) type);
580 if (GNUNET_SYSERR ==
581 erc->iter (erc->iter_cls,
582 &key,
583 data_size,
584 data,
585 (enum GNUNET_BLOCK_Type) type,
586 expiration_time,
587 path_len,
588 path))
589 {
590 LOG (GNUNET_ERROR_TYPE_DEBUG,
591 "Ending iteration (client error)\n");
592 GNUNET_PQ_cleanup_result (rs);
593 break;
594 }
595 GNUNET_PQ_cleanup_result (rs);
596 }
597}
598
599
600/**
601 * Iterate over the results that are "close" to a particular key in
602 * the datacache. "close" is defined as numerically larger than @a
603 * key (when interpreted as a circular address space), with small
604 * distance.
605 *
606 * @param cls closure (internal context for the plugin)
607 * @param key area of the keyspace to look into
608 * @param num_results number of results that should be returned to @a iter
609 * @param iter maybe NULL (to just count)
610 * @param iter_cls closure for @a iter
611 * @return the number of results found
612 */
613static unsigned int
614postgres_plugin_get_closest (void *cls,
615 const struct GNUNET_HashCode *key,
616 unsigned int num_results,
617 GNUNET_DATACACHE_Iterator iter,
618 void *iter_cls)
619{
620 struct Plugin *plugin = cls;
621 uint32_t num_results32 = (uint32_t) num_results;
622 struct GNUNET_TIME_Absolute now;
623 struct GNUNET_PQ_QueryParam params[] = {
624 GNUNET_PQ_query_param_auto_from_type (key),
625 GNUNET_PQ_query_param_absolute_time (&now),
626 GNUNET_PQ_query_param_uint32 (&num_results32),
627 GNUNET_PQ_query_param_end
628 };
629 enum GNUNET_DB_QueryStatus res;
630 struct ExtractResultContext erc;
631
632 erc.iter = iter;
633 erc.iter_cls = iter_cls;
634 now = GNUNET_TIME_absolute_get ();
635 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
636 "get_closest",
637 params,
638 &extract_result_cb,
639 &erc);
640 if (0 > res)
641 {
642 LOG (GNUNET_ERROR_TYPE_DEBUG,
643 "Ending iteration (postgres error)\n");
644 return 0;
645 }
646 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
647 {
648 /* no result */
649 LOG (GNUNET_ERROR_TYPE_DEBUG,
650 "Ending iteration (no more results)\n");
651 return 0;
652 }
653 return res;
654}
655
656
657/**
658 * Entry point for the plugin.
659 *
660 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
661 * @return the plugin's closure (our `struct Plugin`)
662 */
663void *
664libgnunet_plugin_datacache_postgres_init (void *cls)
665{
666 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
667 struct GNUNET_DATACACHE_PluginFunctions *api;
668 struct Plugin *plugin;
669
670 plugin = GNUNET_new (struct Plugin);
671 plugin->env = env;
672
673 if (GNUNET_OK != init_connection (plugin))
674 {
675 GNUNET_free (plugin);
676 return NULL;
677 }
678
679 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
680 api->cls = plugin;
681 api->get = &postgres_plugin_get;
682 api->put = &postgres_plugin_put;
683 api->del = &postgres_plugin_del;
684 api->get_random = &postgres_plugin_get_random;
685 api->get_closest = &postgres_plugin_get_closest;
686 LOG (GNUNET_ERROR_TYPE_INFO,
687 "Postgres datacache running\n");
688 return api;
689}
690
691
692/**
693 * Exit point from the plugin.
694 *
695 * @param cls closure (our `struct Plugin`)
696 * @return NULL
697 */
698void *
699libgnunet_plugin_datacache_postgres_done (void *cls)
700{
701 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
702 struct Plugin *plugin = api->cls;
703
704 GNUNET_PQ_disconnect (plugin->dbh);
705 GNUNET_free (plugin);
706 GNUNET_free (api);
707 return NULL;
708}
709
710
711/* end of plugin_datacache_postgres.c */