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_EXTRA_LOGGING
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, PGresult * ret, int expected_status,
65 const char *command, const char *args, int line)
69 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
71 "Postgres failed to allocate result for `%s:%s' at %d\n",
75 if (PQresultStatus (ret) != expected_status)
77 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
79 _("`%s:%s' failed at %s:%d with error: %s"), command, args,
80 __FILE__, line, PQerrorMessage (plugin->dbh));
89 * Run simple SQL statement (without results).
91 * @param plugin global context
92 * @param sql statement to run
93 * @param line code line for error reporting */
95 pq_exec (struct Plugin *plugin, const char *sql, int line)
99 ret = PQexec (plugin->dbh, sql);
101 check_result (plugin, ret, PGRES_COMMAND_OK, "PQexec", sql, line))
102 return GNUNET_SYSERR;
109 * Prepare SQL statement.
111 * @param plugin global context
112 * @param name name for the prepared SQL statement
113 * @param sql SQL code to prepare
114 * @param nparams number of parameters in sql
115 * @param line code line for error reporting
116 * @return GNUNET_OK on success
119 pq_prepare (struct Plugin *plugin, const char *name, const char *sql,
120 int nparms, int line)
124 ret = PQprepare (plugin->dbh, name, sql, nparms, NULL);
126 check_result (plugin, ret, PGRES_COMMAND_OK, "PQprepare", sql, line))
127 return GNUNET_SYSERR;
134 * @brief Get a database handle
136 * @param plugin global context
137 * @return GNUNET_OK on success, GNUNET_SYSERR on error
140 init_connection (struct Plugin *plugin)
145 /* Open database and precompile statements */
147 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
148 "datacache-postgres", "CONFIG",
151 plugin->dbh = PQconnectdb (conninfo == NULL ? "" : conninfo);
152 GNUNET_free_non_null (conninfo);
153 if (NULL == plugin->dbh)
155 /* FIXME: warn about out-of-memory? */
156 return GNUNET_SYSERR;
158 if (PQstatus (plugin->dbh) != CONNECTION_OK)
160 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "datacache-postgres",
161 _("Unable to initialize Postgres: %s"),
162 PQerrorMessage (plugin->dbh));
163 PQfinish (plugin->dbh);
165 return GNUNET_SYSERR;
169 "CREATE TEMPORARY TABLE gn090dc ("
170 " type INTEGER NOT NULL DEFAULT 0,"
171 " discard_time BIGINT NOT NULL DEFAULT 0,"
172 " key BYTEA NOT NULL DEFAULT '',"
173 " value BYTEA NOT NULL DEFAULT '')" "WITH OIDS");
174 if ((ret == NULL) || ((PQresultStatus (ret) != PGRES_COMMAND_OK) && (0 != strcmp ("42P07", /* duplicate table */
177 PG_DIAG_SQLSTATE)))))
179 (void) check_result (plugin, ret, PGRES_COMMAND_OK, "CREATE TABLE",
180 "gn090dc", __LINE__);
181 PQfinish (plugin->dbh);
183 return GNUNET_SYSERR;
185 if (PQresultStatus (ret) == PGRES_COMMAND_OK)
188 pq_exec (plugin, "CREATE INDEX idx_key ON gn090dc (key)", __LINE__)) ||
190 pq_exec (plugin, "CREATE INDEX idx_dt ON gn090dc (discard_time)",
194 PQfinish (plugin->dbh);
196 return GNUNET_SYSERR;
202 "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL");
204 check_result (plugin, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc",
207 PQfinish (plugin->dbh);
209 return GNUNET_SYSERR;
212 ret = PQexec (plugin->dbh, "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN");
214 check_result (plugin, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc",
217 PQfinish (plugin->dbh);
219 return GNUNET_SYSERR;
223 pq_prepare (plugin, "getkt",
224 "SELECT discard_time,type,value FROM gn090dc "
225 "WHERE key=$1 AND type=$2 ", 2, __LINE__)) ||
227 pq_prepare (plugin, "getk",
228 "SELECT discard_time,type,value FROM gn090dc "
229 "WHERE key=$1", 1, __LINE__)) ||
231 pq_prepare (plugin, "getm",
232 "SELECT length(value),oid,key FROM gn090dc "
233 "ORDER BY discard_time ASC LIMIT 1", 0, __LINE__)) ||
235 pq_prepare (plugin, "delrow", "DELETE FROM gn090dc WHERE oid=$1", 1,
238 pq_prepare (plugin, "put",
239 "INSERT INTO gn090dc (type, discard_time, key, value) "
240 "VALUES ($1, $2, $3, $4)", 4, __LINE__)))
242 PQfinish (plugin->dbh);
244 return GNUNET_SYSERR;
251 * Delete the row identified by the given rowid (qid
254 * @param plugin global context
255 * @param rowid which row to delete
256 * @return GNUNET_OK on success
259 delete_by_rowid (struct Plugin *plugin, uint32_t rowid)
261 uint32_t brow = htonl (rowid);
262 const char *paramValues[] = { (const char *) &brow };
263 int paramLengths[] = { sizeof (brow) };
264 const int paramFormats[] = { 1 };
268 PQexecPrepared (plugin->dbh, "delrow", 1, paramValues, paramLengths,
271 check_result (plugin, ret, PGRES_COMMAND_OK, "PQexecPrepared", "delrow",
274 return GNUNET_SYSERR;
282 * Store an item in the datastore.
284 * @param cls closure (our "struct Plugin")
285 * @param key key to store data under
286 * @param size number of bytes in data
287 * @param data data to store
288 * @param type type of the value
289 * @param discard_time when to discard the value in any case
290 * @return 0 on error, number of bytes used otherwise
293 postgres_plugin_put (void *cls, const GNUNET_HashCode * key, size_t size,
294 const char *data, enum GNUNET_BLOCK_Type type,
295 struct GNUNET_TIME_Absolute discard_time)
297 struct Plugin *plugin = cls;
299 uint32_t btype = htonl (type);
300 uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value__;
302 const char *paramValues[] = {
303 (const char *) &btype,
304 (const char *) &bexpi,
308 int paramLengths[] = {
311 sizeof (GNUNET_HashCode),
314 const int paramFormats[] = { 1, 1, 1, 1 };
317 PQexecPrepared (plugin->dbh, "put", 4, paramValues, paramLengths,
320 check_result (plugin, ret, PGRES_COMMAND_OK, "PQexecPrepared", "put",
322 return GNUNET_SYSERR;
324 return size + OVERHEAD;
329 * Iterate over the results for a particular key
332 * @param cls closure (our "struct Plugin")
334 * @param type entries of which type are relevant?
335 * @param iter maybe NULL (to just count)
336 * @param iter_cls closure for iter
337 * @return the number of results found
340 postgres_plugin_get (void *cls, const GNUNET_HashCode * key,
341 enum GNUNET_BLOCK_Type type,
342 GNUNET_DATACACHE_Iterator iter, void *iter_cls)
344 struct Plugin *plugin = cls;
345 uint32_t btype = htonl (type);
347 const char *paramValues[] = {
349 (const char *) &btype,
351 int paramLengths[] = {
352 sizeof (GNUNET_HashCode),
355 const int paramFormats[] = { 1, 1 };
356 struct GNUNET_TIME_Absolute expiration_time;
363 PQexecPrepared (plugin->dbh, (type == 0) ? "getk" : "getkt",
364 (type == 0) ? 1 : 2, paramValues, paramLengths,
367 check_result (plugin, res, PGRES_TUPLES_OK, "PQexecPrepared",
368 (type == 0) ? "getk" : "getkt", __LINE__))
371 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
372 "Ending iteration (postgres error)\n");
377 if (0 == (cnt = PQntuples (res)))
381 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
382 "Ending iteration (no more results)\n");
392 if ((3 != PQnfields (res)) || (sizeof (uint64_t) != PQfsize (res, 0)) ||
393 (sizeof (uint32_t) != PQfsize (res, 1)))
399 for (i = 0; i < cnt; i++)
401 expiration_time.abs_value =
402 GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
403 type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
404 size = PQgetlength (res, i, 2);
406 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
407 "Found result of size %u bytes and type %u in database\n",
408 (unsigned int) size, (unsigned int) type);
411 iter (iter_cls, expiration_time, key, size, PQgetvalue (res, i, 2),
412 (enum GNUNET_BLOCK_Type) type))
415 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
416 "Ending iteration (client error)\n");
428 * Delete the entry with the lowest expiration value
429 * from the datacache right now.
431 * @param cls closure (our "struct Plugin")
432 * @return GNUNET_OK on success, GNUNET_SYSERR on error
435 postgres_plugin_del (void *cls)
437 struct Plugin *plugin = cls;
443 res = PQexecPrepared (plugin->dbh, "getm", 0, NULL, NULL, NULL, 1);
445 check_result (plugin, res, PGRES_TUPLES_OK, "PQexecPrepared", "getm",
449 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
450 "Ending iteration (postgres error)\n");
454 if (0 == PQntuples (res))
458 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
459 "Ending iteration (no more results)\n");
462 return GNUNET_SYSERR;
464 if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) ||
465 (sizeof (oid) != PQfsize (res, 1)) ||
466 (sizeof (GNUNET_HashCode) != PQgetlength (res, 0, 2)))
472 size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
473 oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
474 memcpy (&key, PQgetvalue (res, 0, 2), sizeof (GNUNET_HashCode));
476 if (GNUNET_OK != delete_by_rowid (plugin, oid))
477 return GNUNET_SYSERR;
478 plugin->env->delete_notify (plugin->env->cls, &key, size + OVERHEAD);
484 * Entry point for the plugin.
486 * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
487 * @return the plugin's closure (our "struct Plugin")
490 libgnunet_plugin_datacache_postgres_init (void *cls)
492 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
493 struct GNUNET_DATACACHE_PluginFunctions *api;
494 struct Plugin *plugin;
496 plugin = GNUNET_malloc (sizeof (struct Plugin));
499 if (GNUNET_OK != init_connection (plugin))
501 GNUNET_free (plugin);
505 api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
507 api->get = &postgres_plugin_get;
508 api->put = &postgres_plugin_put;
509 api->del = &postgres_plugin_del;
510 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "datacache-postgres",
511 _("Postgres datacache running\n"));
517 * Exit point from the plugin.
519 * @param cls closure (our "struct Plugin")
523 libgnunet_plugin_datacache_postgres_done (void *cls)
525 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
526 struct Plugin *plugin = api->cls;
528 PQfinish (plugin->dbh);
529 GNUNET_free (plugin);
536 /* end of plugin_datacache_postgres.c */