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