fdc377b8fb005a558683e84bf1e532e985215ded
[oweals/gnunet.git] / src / datacache / plugin_datacache_postgres.c
1 /*
2      This file is part of GNUnet
3      (C) 2006, 2009, 2010 Christian Grothoff (and other contributing authors)
4
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.
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      General Public License for more details.
14
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.
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_datacache_plugin.h"
29 #include <postgresql/libpq-fe.h>
30
31 #define DEBUG_POSTGRES GNUNET_NO
32
33 /**
34  * Per-entry overhead estimate
35  */
36 #define OVERHEAD (sizeof(GNUNET_HashCode) + 24)
37
38 /**
39  * Context for all functions in this plugin.
40  */
41 struct Plugin
42 {
43   /**
44    * Our execution environment.
45    */
46   struct GNUNET_DATACACHE_PluginEnvironment *env;
47
48   /**
49    * Native Postgres database handle.
50    */
51   PGconn *dbh;
52
53 };
54
55
56 /**
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.
60  * 
61  * @return GNUNET_OK if the result is acceptable
62  */
63 static int
64 check_result (struct Plugin *plugin,
65               PGresult * ret,
66               int expected_status,
67               const char *command, const char *args, int line)
68 {
69   if (ret == NULL)
70   {
71     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
72                      "datastore-postgres",
73                      "Postgres failed to allocate result for `%s:%s' at %d\n",
74                      command, args, line);
75     return GNUNET_SYSERR;
76   }
77   if (PQresultStatus (ret) != expected_status)
78   {
79     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
80                      "datastore-postgres",
81                      _("`%s:%s' failed at %s:%d with error: %s"),
82                      command, args, __FILE__, line,
83                      PQerrorMessage (plugin->dbh));
84     PQclear (ret);
85     return GNUNET_SYSERR;
86   }
87   return GNUNET_OK;
88 }
89
90
91 /**
92  * Run simple SQL statement (without results).
93  */
94 static int
95 pq_exec (struct Plugin *plugin, const char *sql, int line)
96 {
97   PGresult *ret;
98
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;
103   PQclear (ret);
104   return GNUNET_OK;
105 }
106
107
108 /**
109  * Prepare SQL statement.
110  */
111 static int
112 pq_prepare (struct Plugin *plugin,
113             const char *name, const char *sql, int nparms, int line)
114 {
115   PGresult *ret;
116
117   ret = PQprepare (plugin->dbh, name, sql, nparms, NULL);
118   if (GNUNET_OK !=
119       check_result (plugin, ret, PGRES_COMMAND_OK, "PQprepare", sql, line))
120     return GNUNET_SYSERR;
121   PQclear (ret);
122   return GNUNET_OK;
123 }
124
125
126 /**
127  * @brief Get a database handle
128  * @return GNUNET_OK on success, GNUNET_SYSERR on error
129  */
130 static int
131 init_connection (struct Plugin *plugin)
132 {
133   char *conninfo;
134   PGresult *ret;
135
136   /* Open database and precompile statements */
137   if (GNUNET_OK !=
138       GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
139                                              "datacache-postgres",
140                                              "CONFIG", &conninfo))
141     conninfo = NULL;
142   plugin->dbh = PQconnectdb (conninfo == NULL ? "" : conninfo);
143   GNUNET_free_non_null (conninfo);
144   if (NULL == plugin->dbh)
145   {
146     /* FIXME: warn about out-of-memory? */
147     return GNUNET_SYSERR;
148   }
149   if (PQstatus (plugin->dbh) != CONNECTION_OK)
150   {
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);
156     plugin->dbh = NULL;
157     return GNUNET_SYSERR;
158   }
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 */
166                                                                                     PQresultErrorField
167                                                                                     (ret,
168                                                                                      PG_DIAG_SQLSTATE)))))
169   {
170     (void) check_result (plugin,
171                          ret, PGRES_COMMAND_OK, "CREATE TABLE", "gn090dc",
172                          __LINE__);
173     PQfinish (plugin->dbh);
174     plugin->dbh = NULL;
175     return GNUNET_SYSERR;
176   }
177   if (PQresultStatus (ret) == PGRES_COMMAND_OK)
178   {
179     if ((GNUNET_OK !=
180          pq_exec (plugin, "CREATE INDEX idx_key ON gn090dc (key)", __LINE__)) ||
181         (GNUNET_OK !=
182          pq_exec (plugin, "CREATE INDEX idx_dt ON gn090dc (discard_time)",
183                   __LINE__)))
184     {
185       PQclear (ret);
186       PQfinish (plugin->dbh);
187       plugin->dbh = NULL;
188       return GNUNET_SYSERR;
189     }
190   }
191   PQclear (ret);
192 #if 1
193   ret = PQexec (plugin->dbh,
194                 "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL");
195   if (GNUNET_OK !=
196       check_result (plugin,
197                     ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc", __LINE__))
198   {
199     PQfinish (plugin->dbh);
200     plugin->dbh = NULL;
201     return GNUNET_SYSERR;
202   }
203   PQclear (ret);
204   ret = PQexec (plugin->dbh, "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN");
205   if (GNUNET_OK !=
206       check_result (plugin,
207                     ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc", __LINE__))
208   {
209     PQfinish (plugin->dbh);
210     plugin->dbh = NULL;
211     return GNUNET_SYSERR;
212   }
213   PQclear (ret);
214 #endif
215   if ((GNUNET_OK !=
216        pq_prepare (plugin,
217                    "getkt",
218                    "SELECT discard_time,type,value FROM gn090dc "
219                    "WHERE key=$1 AND type=$2 ",
220                    2,
221                    __LINE__)) ||
222       (GNUNET_OK !=
223        pq_prepare (plugin,
224                    "getk",
225                    "SELECT discard_time,type,value FROM gn090dc "
226                    "WHERE key=$1",
227                    1,
228                    __LINE__)) ||
229       (GNUNET_OK !=
230        pq_prepare (plugin,
231                    "getm",
232                    "SELECT length(value),oid,key FROM gn090dc "
233                    "ORDER BY discard_time ASC LIMIT 1",
234                    0,
235                    __LINE__)) ||
236       (GNUNET_OK !=
237        pq_prepare (plugin,
238                    "delrow",
239                    "DELETE FROM gn090dc WHERE oid=$1",
240                    1,
241                    __LINE__)) ||
242       (GNUNET_OK !=
243        pq_prepare (plugin,
244                    "put",
245                    "INSERT INTO gn090dc (type, discard_time, key, value) "
246                    "VALUES ($1, $2, $3, $4)", 4, __LINE__)))
247   {
248     PQfinish (plugin->dbh);
249     plugin->dbh = NULL;
250     return GNUNET_SYSERR;
251   }
252   return GNUNET_OK;
253 }
254
255
256 /**
257  * Delete the row identified by the given rowid (qid
258  * in postgres).
259  *
260  * @return GNUNET_OK on success
261  */
262 static int
263 delete_by_rowid (struct Plugin *plugin, uint32_t rowid)
264 {
265   uint32_t brow = htonl (rowid);
266   const char *paramValues[] = { (const char *) &brow };
267   int paramLengths[] = { sizeof (brow) };
268   const int paramFormats[] = { 1 };
269   PGresult *ret;
270
271   ret = PQexecPrepared (plugin->dbh,
272                         "delrow",
273                         1, paramValues, paramLengths, paramFormats, 1);
274   if (GNUNET_OK !=
275       check_result (plugin,
276                     ret, PGRES_COMMAND_OK, "PQexecPrepared", "delrow",
277                     __LINE__))
278   {
279     return GNUNET_SYSERR;
280   }
281   PQclear (ret);
282   return GNUNET_OK;
283 }
284
285
286 /**
287  * Store an item in the datastore.
288  *
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
296  */
297 static size_t
298 postgres_plugin_put (void *cls,
299                      const GNUNET_HashCode * key,
300                      size_t size,
301                      const char *data,
302                      enum GNUNET_BLOCK_Type type,
303                      struct GNUNET_TIME_Absolute discard_time)
304 {
305   struct Plugin *plugin = cls;
306   PGresult *ret;
307   uint32_t btype = htonl (type);
308   uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value__;
309
310   const char *paramValues[] = {
311     (const char *) &btype,
312     (const char *) &bexpi,
313     (const char *) key,
314     (const char *) data
315   };
316   int paramLengths[] = {
317     sizeof (btype),
318     sizeof (bexpi),
319     sizeof (GNUNET_HashCode),
320     size
321   };
322   const int paramFormats[] = { 1, 1, 1, 1 };
323
324   ret = PQexecPrepared (plugin->dbh,
325                         "put", 4, paramValues, paramLengths, paramFormats, 1);
326   if (GNUNET_OK != check_result (plugin, ret,
327                                  PGRES_COMMAND_OK,
328                                  "PQexecPrepared", "put", __LINE__))
329     return GNUNET_SYSERR;
330   PQclear (ret);
331   return size + OVERHEAD;
332 }
333
334
335 /**
336  * Iterate over the results for a particular key
337  * in the datastore.
338  *
339  * @param cls closure (our "struct Plugin")
340  * @param key
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
345  */
346 static unsigned int
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)
351 {
352   struct Plugin *plugin = cls;
353   uint32_t btype = htonl (type);
354
355   const char *paramValues[] = {
356     (const char *) key,
357     (const char *) &btype,
358   };
359   int paramLengths[] = {
360     sizeof (GNUNET_HashCode),
361     sizeof (btype),
362   };
363   const int paramFormats[] = { 1, 1 };
364   struct GNUNET_TIME_Absolute expiration_time;
365   uint32_t size;
366   unsigned int cnt;
367   unsigned int i;
368   PGresult *res;
369
370   cnt = 0;
371   res = PQexecPrepared (plugin->dbh,
372                         (type == 0) ? "getk" : "getkt",
373                         (type == 0) ? 1 : 2,
374                         paramValues, paramLengths, paramFormats, 1);
375   if (GNUNET_OK != check_result (plugin,
376                                  res,
377                                  PGRES_TUPLES_OK,
378                                  "PQexecPrepared",
379                                  (type == 0) ? "getk" : "getkt", __LINE__))
380   {
381 #if DEBUG_POSTGRES
382     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
383                      "datacache-postgres",
384                      "Ending iteration (postgres error)\n");
385 #endif
386     return 0;
387   }
388
389   if (0 == (cnt = PQntuples (res)))
390   {
391     /* no result */
392 #if DEBUG_POSTGRES
393     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
394                      "datacache-postgres",
395                      "Ending iteration (no more results)\n");
396 #endif
397     PQclear (res);
398     return 0;
399   }
400   if (iter == NULL)
401   {
402     PQclear (res);
403     return cnt;
404   }
405   if ((3 != PQnfields (res)) ||
406       (sizeof (uint64_t) != PQfsize (res, 0)) ||
407       (sizeof (uint32_t) != PQfsize (res, 1)))
408   {
409     GNUNET_break (0);
410     PQclear (res);
411     return 0;
412   }
413   for (i = 0; i < cnt; i++)
414   {
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);
419 #if DEBUG_POSTGRES
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);
424 #endif
425     if (GNUNET_SYSERR ==
426         iter (iter_cls,
427               expiration_time,
428               key, size, PQgetvalue (res, i, 2), (enum GNUNET_BLOCK_Type) type))
429     {
430 #if DEBUG_POSTGRES
431       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
432                        "datacache-postgres",
433                        "Ending iteration (client error)\n");
434 #endif
435       PQclear (res);
436       return cnt;
437     }
438   }
439   PQclear (res);
440   return cnt;
441 }
442
443
444 /**
445  * Delete the entry with the lowest expiration value
446  * from the datacache right now.
447  * 
448  * @param cls closure (our "struct Plugin")
449  * @return GNUNET_OK on success, GNUNET_SYSERR on error
450  */
451 static int
452 postgres_plugin_del (void *cls)
453 {
454   struct Plugin *plugin = cls;
455   uint32_t size;
456   uint32_t oid;
457   GNUNET_HashCode key;
458   PGresult *res;
459
460   res = PQexecPrepared (plugin->dbh, "getm", 0, NULL, NULL, NULL, 1);
461   if (GNUNET_OK != check_result (plugin,
462                                  res,
463                                  PGRES_TUPLES_OK,
464                                  "PQexecPrepared", "getm", __LINE__))
465   {
466 #if DEBUG_POSTGRES
467     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
468                      "datacache-postgres",
469                      "Ending iteration (postgres error)\n");
470 #endif
471     return 0;
472   }
473   if (0 == PQntuples (res))
474   {
475     /* no result */
476 #if DEBUG_POSTGRES
477     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
478                      "datacache-postgres",
479                      "Ending iteration (no more results)\n");
480 #endif
481     PQclear (res);
482     return GNUNET_SYSERR;
483   }
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)))
488   {
489     GNUNET_break (0);
490     PQclear (res);
491     return 0;
492   }
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));
496   PQclear (res);
497   if (GNUNET_OK != delete_by_rowid (plugin, oid))
498     return GNUNET_SYSERR;
499   plugin->env->delete_notify (plugin->env->cls, &key, size + OVERHEAD);
500   return GNUNET_OK;
501 }
502
503
504 /**
505  * Entry point for the plugin.
506  *
507  * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
508  * @return the plugin's closure (our "struct Plugin")
509  */
510 void *
511 libgnunet_plugin_datacache_postgres_init (void *cls)
512 {
513   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
514   struct GNUNET_DATACACHE_PluginFunctions *api;
515   struct Plugin *plugin;
516
517   plugin = GNUNET_malloc (sizeof (struct Plugin));
518   plugin->env = env;
519
520   if (GNUNET_OK != init_connection (plugin))
521   {
522     GNUNET_free (plugin);
523     return NULL;
524   }
525
526   api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
527   api->cls = plugin;
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"));
533   return api;
534 }
535
536
537 /**
538  * Exit point from the plugin.
539  *
540  * @param cls closure (our "struct Plugin")
541  * @return NULL
542  */
543 void *
544 libgnunet_plugin_datacache_postgres_done (void *cls)
545 {
546   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
547   struct Plugin *plugin = api->cls;
548
549   PQfinish (plugin->dbh);
550   GNUNET_free (plugin);
551   GNUNET_free (api);
552   return NULL;
553 }
554
555
556
557 /* end of plugin_datacache_postgres.c */