2 This file is part of GNUnet
3 Copyright (C) 2009-2016 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 datastore/plugin_datastore_postgres.c
23 * @brief postgres-based datastore backend
24 * @author Christian Grothoff
28 #include "gnunet_datastore_plugin.h"
29 #include "gnunet_postgres_lib.h"
30 #include "gnunet_pq_lib.h"
34 * After how many ms "busy" should a DB operation fail for good?
35 * A low value makes sure that we are more responsive to requests
36 * (especially PUTs). A high value guarantees a higher success
37 * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
39 * The default value of 1s should ensure that users do not experience
40 * huge latencies while at the same time allowing operations to succeed
41 * with reasonable probability.
43 #define BUSY_TIMEOUT GNUNET_TIME_UNIT_SECONDS
47 * Context for all functions in this plugin.
52 * Our execution environment.
54 struct GNUNET_DATASTORE_PluginEnvironment *env;
57 * Native Postgres database handle.
65 * @brief Get a database handle
67 * @param plugin global context
68 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
71 init_connection (struct Plugin *plugin)
75 plugin->dbh = GNUNET_POSTGRES_connect (plugin->env->cfg, "datastore-postgres");
76 if (NULL == plugin->dbh)
79 /* FIXME: PostgreSQL does not have unsigned integers! This is ok for the type column because
80 * we only test equality on it and can cast it to/from uint32_t. For repl, prio, and anonLevel
81 * we do math or inequality tests, so we can't handle the entire range of uint32_t.
82 * This will also cause problems for expiration times after 294247-01-10-04:00:54 UTC.
83 * PostgreSQL also recommends against using WITH OIDS.
87 "CREATE TABLE IF NOT EXISTS gn090 ("
88 " repl INTEGER NOT NULL DEFAULT 0,"
89 " type INTEGER NOT NULL DEFAULT 0,"
90 " prio INTEGER NOT NULL DEFAULT 0,"
91 " anonLevel INTEGER NOT NULL DEFAULT 0,"
92 " expire BIGINT NOT NULL DEFAULT 0,"
93 " rvalue BIGINT NOT NULL DEFAULT 0,"
94 " hash BYTEA NOT NULL DEFAULT '',"
95 " vhash BYTEA NOT NULL DEFAULT '',"
96 " value BYTEA NOT NULL DEFAULT '')"
99 ((PQresultStatus (ret) != PGRES_COMMAND_OK) &&
100 (0 != strcmp ("42P07", /* duplicate table */
103 PG_DIAG_SQLSTATE)))))
105 (void) GNUNET_POSTGRES_check_result (plugin->dbh,
110 PQfinish (plugin->dbh);
112 return GNUNET_SYSERR;
115 if (PQresultStatus (ret) == PGRES_COMMAND_OK)
118 GNUNET_POSTGRES_exec (plugin->dbh,
119 "CREATE INDEX IF NOT EXISTS idx_hash ON gn090 (hash)")) ||
121 GNUNET_POSTGRES_exec (plugin->dbh,
122 "CREATE INDEX IF NOT EXISTS idx_prio ON gn090 (prio)")) ||
124 GNUNET_POSTGRES_exec (plugin->dbh,
125 "CREATE INDEX IF NOT EXISTS idx_expire ON gn090 (expire)")) ||
127 GNUNET_POSTGRES_exec (plugin->dbh,
128 "CREATE INDEX IF NOT EXISTS idx_prio_anon ON gn090 (prio,anonLevel)")) ||
130 GNUNET_POSTGRES_exec (plugin->dbh,
131 "CREATE INDEX IF NOT EXISTS idx_prio_hash_anon ON gn090 (prio,hash,anonLevel)")) ||
133 GNUNET_POSTGRES_exec (plugin->dbh,
134 "CREATE INDEX IF NOT EXISTS idx_repl_rvalue ON gn090 (repl,rvalue)")) ||
136 GNUNET_POSTGRES_exec (plugin->dbh,
137 "CREATE INDEX IF NOT EXISTS idx_expire_hash ON gn090 (expire,hash)")))
140 PQfinish (plugin->dbh);
142 return GNUNET_SYSERR;
149 "ALTER TABLE gn090 ALTER value SET STORAGE EXTERNAL");
151 GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090"))
153 PQfinish (plugin->dbh);
155 return GNUNET_SYSERR;
158 ret = PQexec (plugin->dbh, "ALTER TABLE gn090 ALTER hash SET STORAGE PLAIN");
160 GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090"))
162 PQfinish (plugin->dbh);
164 return GNUNET_SYSERR;
167 ret = PQexec (plugin->dbh, "ALTER TABLE gn090 ALTER vhash SET STORAGE PLAIN");
169 GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090"))
171 PQfinish (plugin->dbh);
173 return GNUNET_SYSERR;
176 #define RESULT_COLUMNS "repl, type, prio, anonLevel, expire, hash, value, oid"
178 GNUNET_POSTGRES_prepare (plugin->dbh, "get",
179 "SELECT " RESULT_COLUMNS " FROM gn090 "
180 "WHERE oid >= $1::bigint AND "
181 "(rvalue >= $2 OR 0 = $3::smallint) AND "
182 "(hash = $4 OR 0 = $5::smallint) AND "
183 "(type = $6 OR 0 = $7::smallint) "
184 "ORDER BY oid ASC LIMIT 1", 7)) ||
186 GNUNET_POSTGRES_prepare (plugin->dbh, "put",
187 "INSERT INTO gn090 (repl, type, prio, anonLevel, expire, rvalue, hash, vhash, value) "
188 "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", 9)) ||
190 GNUNET_POSTGRES_prepare (plugin->dbh, "update",
192 "SET prio = prio + $1, "
194 "expire = GREATEST(expire, $3) "
195 "WHERE hash = $4 AND vhash = $5", 5)) ||
197 GNUNET_POSTGRES_prepare (plugin->dbh, "decrepl",
198 "UPDATE gn090 SET repl = GREATEST (repl - 1, 0) "
199 "WHERE oid = $1", 1)) ||
201 GNUNET_POSTGRES_prepare (plugin->dbh, "select_non_anonymous",
202 "SELECT " RESULT_COLUMNS " FROM gn090 "
203 "WHERE anonLevel = 0 AND type = $1 AND oid >= $2::bigint "
204 "ORDER BY oid ASC LIMIT 1",
207 GNUNET_POSTGRES_prepare (plugin->dbh, "select_expiration_order",
208 "(SELECT " RESULT_COLUMNS " FROM gn090 "
209 "WHERE expire < $1 ORDER BY prio ASC LIMIT 1) "
211 "(SELECT " RESULT_COLUMNS " FROM gn090 "
212 "ORDER BY prio ASC LIMIT 1) "
213 "ORDER BY expire ASC LIMIT 1",
216 GNUNET_POSTGRES_prepare (plugin->dbh, "select_replication_order",
217 "SELECT " RESULT_COLUMNS " FROM gn090 "
218 "ORDER BY repl DESC,RANDOM() LIMIT 1", 0)) ||
220 GNUNET_POSTGRES_prepare (plugin->dbh, "delrow", "DELETE FROM gn090 " "WHERE oid=$1", 1)) ||
222 GNUNET_POSTGRES_prepare (plugin->dbh, "remove", "DELETE FROM gn090 "
223 "WHERE hash = $1 AND "
226 GNUNET_POSTGRES_prepare (plugin->dbh, "get_keys", "SELECT hash FROM gn090", 0)))
228 PQfinish (plugin->dbh);
230 return GNUNET_SYSERR;
237 * Get an estimate of how much space the database is
240 * @param cls our `struct Plugin *`
241 * @return number of bytes used on disk
244 postgres_plugin_estimate_size (void *cls, unsigned long long *estimate)
246 struct Plugin *plugin = cls;
247 unsigned long long total;
250 if (NULL == estimate)
253 PQexecParams (plugin->dbh,
254 "SELECT SUM(LENGTH(value))+256*COUNT(*) FROM gn090", 0,
255 NULL, NULL, NULL, NULL, 1);
257 GNUNET_POSTGRES_check_result (plugin->dbh,
266 if ((PQntuples (ret) != 1) || (PQnfields (ret) != 1) )
273 if (PQgetlength (ret, 0, 0) != sizeof (unsigned long long))
275 GNUNET_break (0 == PQgetlength (ret, 0, 0));
280 total = GNUNET_ntohll (*(const unsigned long long *) PQgetvalue (ret, 0, 0));
287 * Store an item in the datastore.
289 * @param cls closure with the `struct Plugin`
290 * @param key key for the item
291 * @param absent true if the key was not found in the bloom filter
292 * @param size number of bytes in data
293 * @param data content stored
294 * @param type type of the content
295 * @param priority priority of the content
296 * @param anonymity anonymity-level for the content
297 * @param replication replication-level for the content
298 * @param expiration expiration time for the content
299 * @param cont continuation called with success or failure status
300 * @param cont_cls continuation closure
303 postgres_plugin_put (void *cls,
304 const struct GNUNET_HashCode *key,
308 enum GNUNET_BLOCK_Type type,
311 uint32_t replication,
312 struct GNUNET_TIME_Absolute expiration,
316 struct Plugin *plugin = cls;
317 struct GNUNET_HashCode vhash;
320 GNUNET_CRYPTO_hash (data,
326 struct GNUNET_PQ_QueryParam params[] = {
327 GNUNET_PQ_query_param_uint32 (&priority),
328 GNUNET_PQ_query_param_uint32 (&replication),
329 GNUNET_PQ_query_param_absolute_time (&expiration),
330 GNUNET_PQ_query_param_auto_from_type (key),
331 GNUNET_PQ_query_param_auto_from_type (&vhash),
332 GNUNET_PQ_query_param_end
334 ret = GNUNET_PQ_exec_prepared (plugin->dbh,
338 GNUNET_POSTGRES_check_result (plugin->dbh,
348 _("Postgress exec failure"));
351 /* What an awful API, this function really does return a string */
352 bool affected = 0 != strcmp ("0", PQcmdTuples (ret));
365 uint32_t utype = type;
366 uint64_t rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
368 struct GNUNET_PQ_QueryParam params[] = {
369 GNUNET_PQ_query_param_uint32 (&replication),
370 GNUNET_PQ_query_param_uint32 (&utype),
371 GNUNET_PQ_query_param_uint32 (&priority),
372 GNUNET_PQ_query_param_uint32 (&anonymity),
373 GNUNET_PQ_query_param_absolute_time (&expiration),
374 GNUNET_PQ_query_param_uint64 (&rvalue),
375 GNUNET_PQ_query_param_auto_from_type (key),
376 GNUNET_PQ_query_param_auto_from_type (&vhash),
377 GNUNET_PQ_query_param_fixed_size (data, size),
378 GNUNET_PQ_query_param_end
381 ret = GNUNET_PQ_exec_prepared (plugin->dbh,
385 GNUNET_POSTGRES_check_result (plugin->dbh,
388 "PQexecPrepared", "put"))
390 cont (cont_cls, key, size,
392 _("Postgress exec failure"));
396 plugin->env->duc (plugin->env->cls,
397 size + GNUNET_DATASTORE_ENTRY_OVERHEAD);
398 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
399 "datastore-postgres",
400 "Stored %u bytes in database\n",
401 (unsigned int) size);
402 cont (cont_cls, key, size, GNUNET_OK, NULL);
407 * Function invoked to process the result and call the processor.
409 * @param plugin global plugin data
410 * @param proc function to call the value (once only).
411 * @param proc_cls closure for proc
412 * @param res result from exec
413 * @param filename filename for error messages
414 * @param line line number for error messages
417 process_result (struct Plugin *plugin,
418 PluginDatumProcessor proc,
421 const char *filename, int line)
427 uint32_t replication;
431 struct GNUNET_TIME_Absolute expiration_time;
432 struct GNUNET_HashCode key;
433 struct GNUNET_PQ_ResultSpec rs[] = {
434 GNUNET_PQ_result_spec_uint32 ("repl", &replication),
435 GNUNET_PQ_result_spec_uint32 ("type", &utype),
436 GNUNET_PQ_result_spec_uint32 ("prio", &priority),
437 GNUNET_PQ_result_spec_uint32 ("anonLevel", &anonymity),
438 GNUNET_PQ_result_spec_absolute_time ("expire", &expiration_time),
439 GNUNET_PQ_result_spec_auto_from_type ("hash", &key),
440 GNUNET_PQ_result_spec_variable_size ("value", &data, &size),
441 GNUNET_PQ_result_spec_uint32 ("oid", &rowid),
442 GNUNET_PQ_result_spec_end
446 GNUNET_POSTGRES_check_result_ (plugin->dbh,
453 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
454 "datastore-postgres",
455 "Ending iteration (postgres error)\n");
456 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
460 if (0 == PQntuples (res))
463 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
464 "datastore-postgres",
465 "Ending iteration (no more results)\n");
466 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
470 if (1 != PQntuples (res))
473 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
478 GNUNET_PQ_extract_result (res,
484 GNUNET_POSTGRES_delete_by_rowid (plugin->dbh,
487 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
491 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
492 "datastore-postgres",
493 "Found result of size %u bytes and type %u in database\n",
495 (unsigned int) utype);
496 iret = proc (proc_cls,
500 (enum GNUNET_BLOCK_Type) utype,
507 if (iret == GNUNET_NO)
509 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
510 "Processor asked for item %u to be removed.\n",
511 (unsigned int) rowid);
513 GNUNET_POSTGRES_delete_by_rowid (plugin->dbh,
517 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
518 "datastore-postgres",
519 "Deleting %u bytes from database\n",
520 (unsigned int) size);
521 plugin->env->duc (plugin->env->cls,
522 - (size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
523 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
524 "datastore-postgres",
525 "Deleted %u bytes from database\n",
526 (unsigned int) size);
533 * Get one of the results for a particular key in the datastore.
535 * @param cls closure with the 'struct Plugin'
536 * @param next_uid return the result with lowest uid >= next_uid
537 * @param random if true, return a random result instead of using next_uid
538 * @param key maybe NULL (to match all entries)
539 * @param type entries of which type are relevant?
540 * Use 0 for any type.
541 * @param proc function to call on the matching value;
542 * will be called with NULL if nothing matches
543 * @param proc_cls closure for @a proc
546 postgres_plugin_get_key (void *cls,
549 const struct GNUNET_HashCode *key,
550 enum GNUNET_BLOCK_Type type,
551 PluginDatumProcessor proc,
554 struct Plugin *plugin = cls;
555 uint32_t utype = type;
556 uint16_t use_rvalue = random;
557 uint16_t use_key = NULL != key;
558 uint16_t use_type = GNUNET_BLOCK_TYPE_ANY != type;
560 struct GNUNET_PQ_QueryParam params[] = {
561 GNUNET_PQ_query_param_uint64 (&next_uid),
562 GNUNET_PQ_query_param_uint64 (&rvalue),
563 GNUNET_PQ_query_param_uint16 (&use_rvalue),
564 GNUNET_PQ_query_param_auto_from_type (key),
565 GNUNET_PQ_query_param_uint16 (&use_key),
566 GNUNET_PQ_query_param_uint32 (&utype),
567 GNUNET_PQ_query_param_uint16 (&use_type),
568 GNUNET_PQ_query_param_end
574 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
581 ret = GNUNET_PQ_exec_prepared (plugin->dbh,
584 process_result (plugin,
593 * Select a subset of the items in the datastore and call
594 * the given iterator for each of them.
596 * @param cls our `struct Plugin *`
597 * @param next_uid return the result with lowest uid >= next_uid
598 * @param type entries of which type should be considered?
599 * Must not be zero (ANY).
600 * @param proc function to call on the matching value;
601 * will be called with NULL if no value matches
602 * @param proc_cls closure for @a proc
605 postgres_plugin_get_zero_anonymity (void *cls,
607 enum GNUNET_BLOCK_Type type,
608 PluginDatumProcessor proc,
611 struct Plugin *plugin = cls;
612 uint32_t utype = type;
613 struct GNUNET_PQ_QueryParam params[] = {
614 GNUNET_PQ_query_param_uint32 (&utype),
615 GNUNET_PQ_query_param_uint64 (&next_uid),
616 GNUNET_PQ_query_param_end
620 ret = GNUNET_PQ_exec_prepared (plugin->dbh,
621 "select_non_anonymous",
624 process_result (plugin,
632 * Context for #repl_iter() function.
640 struct Plugin *plugin;
643 * Function to call for the result (or the NULL).
645 PluginDatumProcessor proc;
648 * Closure for @e proc.
655 * Wrapper for the iterator for 'sqlite_plugin_replication_get'.
656 * Decrements the replication counter and calls the original
659 * @param cls closure with the `struct ReplCtx *`
660 * @param key key for the content
661 * @param size number of bytes in @a data
662 * @param data content stored
663 * @param type type of the content
664 * @param priority priority of the content
665 * @param anonymity anonymity-level for the content
666 * @param replication replication-level for the content
667 * @param expiration expiration time for the content
668 * @param uid unique identifier for the datum;
669 * maybe 0 if no unique identifier is available
670 * @return #GNUNET_SYSERR to abort the iteration,
671 * #GNUNET_OK to continue
672 * (continue on call to "next", of course),
673 * #GNUNET_NO to delete the item and continue (if supported)
676 repl_proc (void *cls,
677 const struct GNUNET_HashCode *key,
680 enum GNUNET_BLOCK_Type type,
683 uint32_t replication,
684 struct GNUNET_TIME_Absolute expiration,
687 struct ReplCtx *rc = cls;
688 struct Plugin *plugin = rc->plugin;
690 uint32_t oid = (uint32_t) uid;
691 struct GNUNET_PQ_QueryParam params[] = {
692 GNUNET_PQ_query_param_uint32 (&oid),
693 GNUNET_PQ_query_param_end
697 ret = rc->proc (rc->proc_cls,
709 qret = GNUNET_PQ_exec_prepared (plugin->dbh,
713 GNUNET_POSTGRES_check_result (plugin->dbh,
718 return GNUNET_SYSERR;
725 * Get a random item for replication. Returns a single, not expired,
726 * random item from those with the highest replication counters. The
727 * item's replication counter is decremented by one IF it was positive
728 * before. Call @a proc with all values ZERO or NULL if the datastore
731 * @param cls closure with the `struct Plugin`
732 * @param proc function to call the value (once only).
733 * @param proc_cls closure for @a proc
736 postgres_plugin_get_replication (void *cls,
737 PluginDatumProcessor proc,
740 struct Plugin *plugin = cls;
746 rc.proc_cls = proc_cls;
747 ret = PQexecPrepared (plugin->dbh,
748 "select_replication_order", 0, NULL, NULL,
750 process_result (plugin,
759 * Get a random item for expiration. Call @a proc with all values
760 * ZERO or NULL if the datastore is empty.
762 * @param cls closure with the `struct Plugin`
763 * @param proc function to call the value (once only).
764 * @param proc_cls closure for @a proc
767 postgres_plugin_get_expiration (void *cls,
768 PluginDatumProcessor proc,
771 struct Plugin *plugin = cls;
772 struct GNUNET_TIME_Absolute now;
773 struct GNUNET_PQ_QueryParam params[] = {
774 GNUNET_PQ_query_param_absolute_time (&now),
775 GNUNET_PQ_query_param_end
779 now = GNUNET_TIME_absolute_get ();
780 ret = GNUNET_PQ_exec_prepared (plugin->dbh,
781 "select_expiration_order",
783 process_result (plugin,
791 * Get all of the keys in the datastore.
793 * @param cls closure with the `struct Plugin *`
794 * @param proc function to call on each key
795 * @param proc_cls closure for @a proc
798 postgres_plugin_get_keys (void *cls,
799 PluginKeyProcessor proc,
802 struct Plugin *plugin = cls;
805 struct GNUNET_HashCode key;
808 res = PQexecPrepared (plugin->dbh,
810 0, NULL, NULL, NULL, 1);
811 ret = PQntuples (res);
814 if (sizeof (struct GNUNET_HashCode) !=
815 PQgetlength (res, i, 0))
818 PQgetvalue (res, i, 0),
819 sizeof (struct GNUNET_HashCode));
820 proc (proc_cls, &key, 1);
824 proc (proc_cls, NULL, 0);
831 * @param cls closure with the `struct Plugin *`
834 postgres_plugin_drop (void *cls)
836 struct Plugin *plugin = cls;
839 GNUNET_POSTGRES_exec (plugin->dbh,
841 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
843 _("Failed to drop table from database.\n"));
848 * Remove a particular key in the datastore.
851 * @param key key for the content
852 * @param size number of bytes in data
853 * @param data content stored
854 * @param cont continuation called with success or failure status
855 * @param cont_cls continuation closure for @a cont
858 postgres_plugin_remove_key (void *cls,
859 const struct GNUNET_HashCode *key,
862 PluginRemoveCont cont,
865 struct Plugin *plugin = cls;
867 struct GNUNET_PQ_QueryParam params[] = {
868 GNUNET_PQ_query_param_auto_from_type (key),
869 GNUNET_PQ_query_param_fixed_size (data, size),
870 GNUNET_PQ_query_param_end
872 ret = GNUNET_PQ_exec_prepared (plugin->dbh,
876 GNUNET_POSTGRES_check_result (plugin->dbh,
886 _("Postgress exec failure"));
889 /* What an awful API, this function really does return a string */
890 bool affected = 0 != strcmp ("0", PQcmdTuples (ret));
901 plugin->env->duc (plugin->env->cls,
902 - (size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
903 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
904 "datastore-postgres",
905 "Deleted %u bytes from database\n",
906 (unsigned int) size);
916 * Entry point for the plugin.
918 * @param cls the `struct GNUNET_DATASTORE_PluginEnvironment*`
919 * @return our `struct Plugin *`
922 libgnunet_plugin_datastore_postgres_init (void *cls)
924 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
925 struct GNUNET_DATASTORE_PluginFunctions *api;
926 struct Plugin *plugin;
928 plugin = GNUNET_new (struct Plugin);
930 if (GNUNET_OK != init_connection (plugin))
932 GNUNET_free (plugin);
935 api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
937 api->estimate_size = &postgres_plugin_estimate_size;
938 api->put = &postgres_plugin_put;
939 api->get_key = &postgres_plugin_get_key;
940 api->get_replication = &postgres_plugin_get_replication;
941 api->get_expiration = &postgres_plugin_get_expiration;
942 api->get_zero_anonymity = &postgres_plugin_get_zero_anonymity;
943 api->get_keys = &postgres_plugin_get_keys;
944 api->drop = &postgres_plugin_drop;
945 api->remove_key = &postgres_plugin_remove_key;
946 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
947 "datastore-postgres",
948 _("Postgres database running\n"));
954 * Exit point from the plugin.
956 * @param cls our `struct Plugin *`
957 * @return always NULL
960 libgnunet_plugin_datastore_postgres_done (void *cls)
962 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
963 struct Plugin *plugin = api->cls;
965 PQfinish (plugin->dbh);
966 GNUNET_free (plugin);
971 /* end of plugin_datastore_postgres.c */