2 This file is part of GNUnet
3 (C) 2006, 2009, 2010 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_datacache_plugin.h"
29 #include <postgresql/libpq-fe.h>
31 #define DEBUG_POSTGRES GNUNET_NO
34 * Per-entry overhead estimate
36 #define OVERHEAD (sizeof(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.
57 * Check if the result obtained from Postgres has
58 * the desired status code. If not, log an error, clear the
59 * result and return GNUNET_SYSERR.
61 * @return GNUNET_OK if the result is acceptable
64 check_result (struct Plugin *plugin,
67 const char *command, const char *args, int line)
71 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
73 "Postgres failed to allocate result for `%s:%s' at %d\n",
77 if (PQresultStatus (ret) != expected_status)
79 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
81 _("`%s:%s' failed at %s:%d with error: %s"),
82 command, args, __FILE__, line,
83 PQerrorMessage (plugin->dbh));
92 * Run simple SQL statement (without results).
95 pq_exec (struct Plugin *plugin, const char *sql, int line)
99 ret = PQexec (plugin->dbh, sql);
100 if (GNUNET_OK != check_result (plugin,
101 ret, PGRES_COMMAND_OK, "PQexec", sql, line))
102 return GNUNET_SYSERR;
109 * Prepare SQL statement.
112 pq_prepare (struct Plugin *plugin,
113 const char *name, const char *sql, int nparms, int line)
117 ret = PQprepare (plugin->dbh, name, sql, nparms, NULL);
119 check_result (plugin, ret, PGRES_COMMAND_OK, "PQprepare", sql, line))
120 return GNUNET_SYSERR;
127 * @brief Get a database handle
128 * @return GNUNET_OK on success, GNUNET_SYSERR on error
131 init_connection (struct Plugin *plugin)
136 /* Open database and precompile statements */
138 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
139 "datacache-postgres",
140 "CONFIG", &conninfo))
142 plugin->dbh = PQconnectdb (conninfo == NULL ? "" : conninfo);
143 GNUNET_free_non_null (conninfo);
144 if (NULL == plugin->dbh)
146 /* FIXME: warn about out-of-memory? */
147 return GNUNET_SYSERR;
149 if (PQstatus (plugin->dbh) != CONNECTION_OK)
151 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
152 "datacache-postgres",
153 _("Unable to initialize Postgres: %s"),
154 PQerrorMessage (plugin->dbh));
155 PQfinish (plugin->dbh);
157 return GNUNET_SYSERR;
159 ret = PQexec (plugin->dbh,
160 "CREATE TEMPORARY TABLE gn090dc ("
161 " type INTEGER NOT NULL DEFAULT 0,"
162 " discard_time BIGINT NOT NULL DEFAULT 0,"
163 " key BYTEA NOT NULL DEFAULT '',"
164 " value BYTEA NOT NULL DEFAULT '')" "WITH OIDS");
165 if ((ret == NULL) || ((PQresultStatus (ret) != PGRES_COMMAND_OK) && (0 != strcmp ("42P07", /* duplicate table */
168 PG_DIAG_SQLSTATE)))))
170 (void) check_result (plugin,
171 ret, PGRES_COMMAND_OK, "CREATE TABLE", "gn090dc",
173 PQfinish (plugin->dbh);
175 return GNUNET_SYSERR;
177 if (PQresultStatus (ret) == PGRES_COMMAND_OK)
180 pq_exec (plugin, "CREATE INDEX idx_key ON gn090dc (key)", __LINE__)) ||
182 pq_exec (plugin, "CREATE INDEX idx_dt ON gn090dc (discard_time)",
186 PQfinish (plugin->dbh);
188 return GNUNET_SYSERR;
193 ret = PQexec (plugin->dbh,
194 "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL");
196 check_result (plugin,
197 ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc", __LINE__))
199 PQfinish (plugin->dbh);
201 return GNUNET_SYSERR;
204 ret = PQexec (plugin->dbh, "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN");
206 check_result (plugin,
207 ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc", __LINE__))
209 PQfinish (plugin->dbh);
211 return GNUNET_SYSERR;
218 "SELECT discard_time,type,value FROM gn090dc "
219 "WHERE key=$1 AND type=$2 ",
225 "SELECT discard_time,type,value FROM gn090dc "
232 "SELECT length(value),oid,key FROM gn090dc "
233 "ORDER BY discard_time ASC LIMIT 1",
239 "DELETE FROM gn090dc WHERE oid=$1",
245 "INSERT INTO gn090dc (type, discard_time, key, value) "
246 "VALUES ($1, $2, $3, $4)", 4, __LINE__)))
248 PQfinish (plugin->dbh);
250 return GNUNET_SYSERR;
257 * Delete the row identified by the given rowid (qid
260 * @return GNUNET_OK on success
263 delete_by_rowid (struct Plugin *plugin, uint32_t rowid)
265 uint32_t brow = htonl (rowid);
266 const char *paramValues[] = { (const char *) &brow };
267 int paramLengths[] = { sizeof (brow) };
268 const int paramFormats[] = { 1 };
271 ret = PQexecPrepared (plugin->dbh,
273 1, paramValues, paramLengths, paramFormats, 1);
275 check_result (plugin,
276 ret, PGRES_COMMAND_OK, "PQexecPrepared", "delrow",
279 return GNUNET_SYSERR;
287 * Store an item in the datastore.
289 * @param cls closure (our "struct Plugin")
290 * @param key key to store data under
291 * @param size number of bytes in data
292 * @param data data to store
293 * @param type type of the value
294 * @param discard_time when to discard the value in any case
295 * @return 0 on error, number of bytes used otherwise
298 postgres_plugin_put (void *cls,
299 const GNUNET_HashCode * key,
302 enum GNUNET_BLOCK_Type type,
303 struct GNUNET_TIME_Absolute discard_time)
305 struct Plugin *plugin = cls;
307 uint32_t btype = htonl (type);
308 uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value__;
310 const char *paramValues[] = {
311 (const char *) &btype,
312 (const char *) &bexpi,
316 int paramLengths[] = {
319 sizeof (GNUNET_HashCode),
322 const int paramFormats[] = { 1, 1, 1, 1 };
324 ret = PQexecPrepared (plugin->dbh,
325 "put", 4, paramValues, paramLengths, paramFormats, 1);
326 if (GNUNET_OK != check_result (plugin, ret,
328 "PQexecPrepared", "put", __LINE__))
329 return GNUNET_SYSERR;
331 return size + OVERHEAD;
336 * Iterate over the results for a particular key
339 * @param cls closure (our "struct Plugin")
341 * @param type entries of which type are relevant?
342 * @param iter maybe NULL (to just count)
343 * @param iter_cls closure for iter
344 * @return the number of results found
347 postgres_plugin_get (void *cls,
348 const GNUNET_HashCode * key,
349 enum GNUNET_BLOCK_Type type,
350 GNUNET_DATACACHE_Iterator iter, void *iter_cls)
352 struct Plugin *plugin = cls;
353 uint32_t btype = htonl (type);
355 const char *paramValues[] = {
357 (const char *) &btype,
359 int paramLengths[] = {
360 sizeof (GNUNET_HashCode),
363 const int paramFormats[] = { 1, 1 };
364 struct GNUNET_TIME_Absolute expiration_time;
371 res = PQexecPrepared (plugin->dbh,
372 (type == 0) ? "getk" : "getkt",
374 paramValues, paramLengths, paramFormats, 1);
375 if (GNUNET_OK != check_result (plugin,
379 (type == 0) ? "getk" : "getkt", __LINE__))
382 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
383 "datacache-postgres",
384 "Ending iteration (postgres error)\n");
389 if (0 == (cnt = PQntuples (res)))
393 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
394 "datacache-postgres",
395 "Ending iteration (no more results)\n");
405 if ((3 != PQnfields (res)) ||
406 (sizeof (uint64_t) != PQfsize (res, 0)) ||
407 (sizeof (uint32_t) != PQfsize (res, 1)))
413 for (i = 0; i < cnt; i++)
415 expiration_time.abs_value =
416 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
417 type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
418 size = PQgetlength (res, i, 2);
420 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
421 "datacache-postgres",
422 "Found result of size %u bytes and type %u in database\n",
423 (unsigned int) size, (unsigned int) type);
428 key, size, PQgetvalue (res, i, 2), (enum GNUNET_BLOCK_Type) type))
431 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
432 "datacache-postgres",
433 "Ending iteration (client error)\n");
445 * Delete the entry with the lowest expiration value
446 * from the datacache right now.
448 * @param cls closure (our "struct Plugin")
449 * @return GNUNET_OK on success, GNUNET_SYSERR on error
452 postgres_plugin_del (void *cls)
454 struct Plugin *plugin = cls;
460 res = PQexecPrepared (plugin->dbh, "getm", 0, NULL, NULL, NULL, 1);
461 if (GNUNET_OK != check_result (plugin,
464 "PQexecPrepared", "getm", __LINE__))
467 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
468 "datacache-postgres",
469 "Ending iteration (postgres error)\n");
473 if (0 == PQntuples (res))
477 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
478 "datacache-postgres",
479 "Ending iteration (no more results)\n");
482 return GNUNET_SYSERR;
484 if ((3 != PQnfields (res)) ||
485 (sizeof (size) != PQfsize (res, 0)) ||
486 (sizeof (oid) != PQfsize (res, 1)) ||
487 (sizeof (GNUNET_HashCode) != PQgetlength (res, 0, 2)))
493 size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
494 oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
495 memcpy (&key, PQgetvalue (res, 0, 2), sizeof (GNUNET_HashCode));
497 if (GNUNET_OK != delete_by_rowid (plugin, oid))
498 return GNUNET_SYSERR;
499 plugin->env->delete_notify (plugin->env->cls, &key, size + OVERHEAD);
505 * Entry point for the plugin.
507 * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
508 * @return the plugin's closure (our "struct Plugin")
511 libgnunet_plugin_datacache_postgres_init (void *cls)
513 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
514 struct GNUNET_DATACACHE_PluginFunctions *api;
515 struct Plugin *plugin;
517 plugin = GNUNET_malloc (sizeof (struct Plugin));
520 if (GNUNET_OK != init_connection (plugin))
522 GNUNET_free (plugin);
526 api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
528 api->get = &postgres_plugin_get;
529 api->put = &postgres_plugin_put;
530 api->del = &postgres_plugin_del;
531 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
532 "datacache-postgres", _("Postgres datacache running\n"));
538 * Exit point from the plugin.
540 * @param cls closure (our "struct Plugin")
544 libgnunet_plugin_datacache_postgres_done (void *cls)
546 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
547 struct Plugin *plugin = api->cls;
549 PQfinish (plugin->dbh);
550 GNUNET_free (plugin);
557 /* end of plugin_datacache_postgres.c */