- remove http
[oweals/gnunet.git] / src / datacache / plugin_datacache_postgres.c
1 /*
2      This file is part of GNUnet
3      (C) 2006, 2009, 2010, 2012 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_postgres_lib.h"
29 #include "gnunet_datacache_plugin.h"
30 #include <postgresql/libpq-fe.h>
31
32 #define DEBUG_POSTGRES GNUNET_EXTRA_LOGGING
33
34 /**
35  * Per-entry overhead estimate
36  */
37 #define OVERHEAD (sizeof(GNUNET_HashCode) + 24)
38
39 /**
40  * Context for all functions in this plugin.
41  */
42 struct Plugin
43 {
44   /**
45    * Our execution environment.
46    */
47   struct GNUNET_DATACACHE_PluginEnvironment *env;
48
49   /**
50    * Native Postgres database handle.
51    */
52   PGconn *dbh;
53
54 };
55
56
57 /**
58  * @brief Get a database handle
59  *
60  * @param plugin global context
61  * @return GNUNET_OK on success, GNUNET_SYSERR on error
62  */
63 static int
64 init_connection (struct Plugin *plugin)
65 {
66   PGresult *ret;
67
68   plugin->dbh = GNUNET_POSTGRES_connect (plugin->env->cfg,
69                                          "datacache-postgres");
70   if (NULL == plugin->dbh)
71     return GNUNET_SYSERR;
72   ret =
73       PQexec (plugin->dbh,
74               "CREATE TEMPORARY TABLE gn090dc ("
75               "  type INTEGER NOT NULL DEFAULT 0,"
76               "  discard_time BIGINT NOT NULL DEFAULT 0,"
77               "  key BYTEA NOT NULL DEFAULT '',"
78               "  value BYTEA NOT NULL DEFAULT '')" "WITH OIDS");
79   if ((ret == NULL) || ((PQresultStatus (ret) != PGRES_COMMAND_OK) && (0 != strcmp ("42P07",    /* duplicate table */
80                                                                                     PQresultErrorField
81                                                                                     (ret,
82                                                                                      PG_DIAG_SQLSTATE)))))
83   {
84     (void) GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "CREATE TABLE",
85                                          "gn090dc");
86     PQfinish (plugin->dbh);
87     plugin->dbh = NULL;
88     return GNUNET_SYSERR;
89   }
90   if (PQresultStatus (ret) == PGRES_COMMAND_OK)
91   {
92     if ((GNUNET_OK !=
93          GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_key ON gn090dc (key)")) ||
94         (GNUNET_OK !=
95          GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_dt ON gn090dc (discard_time)")))
96     {
97       PQclear (ret);
98       PQfinish (plugin->dbh);
99       plugin->dbh = NULL;
100       return GNUNET_SYSERR;
101     }
102   }
103   PQclear (ret);
104   ret =
105       PQexec (plugin->dbh,
106               "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL");
107   if (GNUNET_OK !=
108       GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc"))
109   {
110     PQfinish (plugin->dbh);
111     plugin->dbh = NULL;
112     return GNUNET_SYSERR;
113   }
114   PQclear (ret);
115   ret = PQexec (plugin->dbh, "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN");
116   if (GNUNET_OK !=
117       GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc"))
118   {
119     PQfinish (plugin->dbh);
120     plugin->dbh = NULL;
121     return GNUNET_SYSERR;
122   }
123   PQclear (ret);
124   if ((GNUNET_OK !=
125        GNUNET_POSTGRES_prepare (plugin->dbh, "getkt",
126                    "SELECT discard_time,type,value FROM gn090dc "
127                    "WHERE key=$1 AND type=$2 ", 2)) ||
128       (GNUNET_OK !=
129        GNUNET_POSTGRES_prepare (plugin->dbh, "getk",
130                    "SELECT discard_time,type,value FROM gn090dc "
131                    "WHERE key=$1", 1)) ||
132       (GNUNET_OK !=
133        GNUNET_POSTGRES_prepare (plugin->dbh, "getm",
134                    "SELECT length(value),oid,key FROM gn090dc "
135                    "ORDER BY discard_time ASC LIMIT 1", 0)) ||
136       (GNUNET_OK !=
137        GNUNET_POSTGRES_prepare (plugin->dbh, "delrow", "DELETE FROM gn090dc WHERE oid=$1", 1)) ||
138       (GNUNET_OK !=
139        GNUNET_POSTGRES_prepare (plugin->dbh, "put",
140                    "INSERT INTO gn090dc (type, discard_time, key, value) "
141                    "VALUES ($1, $2, $3, $4)", 4)))
142   {
143     PQfinish (plugin->dbh);
144     plugin->dbh = NULL;
145     return GNUNET_SYSERR;
146   }
147   return GNUNET_OK;
148 }
149
150
151 /**
152  * Store an item in the datastore.
153  *
154  * @param cls closure (our "struct Plugin")
155  * @param key key to store data under
156  * @param size number of bytes in data
157  * @param data data to store
158  * @param type type of the value
159  * @param discard_time when to discard the value in any case
160  * @return 0 on error, number of bytes used otherwise
161  */
162 static size_t
163 postgres_plugin_put (void *cls, const GNUNET_HashCode * key, size_t size,
164                      const char *data, enum GNUNET_BLOCK_Type type,
165                      struct GNUNET_TIME_Absolute discard_time)
166 {
167   struct Plugin *plugin = cls;
168   PGresult *ret;
169   uint32_t btype = htonl (type);
170   uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value__;
171
172   const char *paramValues[] = {
173     (const char *) &btype,
174     (const char *) &bexpi,
175     (const char *) key,
176     (const char *) data
177   };
178   int paramLengths[] = {
179     sizeof (btype),
180     sizeof (bexpi),
181     sizeof (GNUNET_HashCode),
182     size
183   };
184   const int paramFormats[] = { 1, 1, 1, 1 };
185
186   ret =
187       PQexecPrepared (plugin->dbh, "put", 4, paramValues, paramLengths,
188                       paramFormats, 1);
189   if (GNUNET_OK !=
190       GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "PQexecPrepared", "put"))
191     return GNUNET_SYSERR;
192   PQclear (ret);
193   return size + OVERHEAD;
194 }
195
196
197 /**
198  * Iterate over the results for a particular key
199  * in the datastore.
200  *
201  * @param cls closure (our "struct Plugin")
202  * @param key
203  * @param type entries of which type are relevant?
204  * @param iter maybe NULL (to just count)
205  * @param iter_cls closure for iter
206  * @return the number of results found
207  */
208 static unsigned int
209 postgres_plugin_get (void *cls, const GNUNET_HashCode * key,
210                      enum GNUNET_BLOCK_Type type,
211                      GNUNET_DATACACHE_Iterator iter, void *iter_cls)
212 {
213   struct Plugin *plugin = cls;
214   uint32_t btype = htonl (type);
215
216   const char *paramValues[] = {
217     (const char *) key,
218     (const char *) &btype,
219   };
220   int paramLengths[] = {
221     sizeof (GNUNET_HashCode),
222     sizeof (btype),
223   };
224   const int paramFormats[] = { 1, 1 };
225   struct GNUNET_TIME_Absolute expiration_time;
226   uint32_t size;
227   unsigned int cnt;
228   unsigned int i;
229   PGresult *res;
230
231   res =
232       PQexecPrepared (plugin->dbh, (type == 0) ? "getk" : "getkt",
233                       (type == 0) ? 1 : 2, paramValues, paramLengths,
234                       paramFormats, 1);
235   if (GNUNET_OK !=
236       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, "PQexecPrepared",
237                                     (type == 0) ? "getk" : "getkt"))
238   {
239 #if DEBUG_POSTGRES
240     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
241                      "Ending iteration (postgres error)\n");
242 #endif
243     return 0;
244   }
245
246   if (0 == (cnt = PQntuples (res)))
247   {
248     /* no result */
249 #if DEBUG_POSTGRES
250     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
251                      "Ending iteration (no more results)\n");
252 #endif
253     PQclear (res);
254     return 0;
255   }
256   if (iter == NULL)
257   {
258     PQclear (res);
259     return cnt;
260   }
261   if ((3 != PQnfields (res)) || (sizeof (uint64_t) != PQfsize (res, 0)) ||
262       (sizeof (uint32_t) != PQfsize (res, 1)))
263   {
264     GNUNET_break (0);
265     PQclear (res);
266     return 0;
267   }
268   for (i = 0; i < cnt; i++)
269   {
270     expiration_time.abs_value =
271         GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
272     type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
273     size = PQgetlength (res, i, 2);
274 #if DEBUG_POSTGRES
275     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
276                      "Found result of size %u bytes and type %u in database\n",
277                      (unsigned int) size, (unsigned int) type);
278 #endif
279     if (GNUNET_SYSERR ==
280         iter (iter_cls, expiration_time, key, size, PQgetvalue (res, i, 2),
281               (enum GNUNET_BLOCK_Type) type))
282     {
283 #if DEBUG_POSTGRES
284       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
285                        "Ending iteration (client error)\n");
286 #endif
287       PQclear (res);
288       return cnt;
289     }
290   }
291   PQclear (res);
292   return cnt;
293 }
294
295
296 /**
297  * Delete the entry with the lowest expiration value
298  * from the datacache right now.
299  *
300  * @param cls closure (our "struct Plugin")
301  * @return GNUNET_OK on success, GNUNET_SYSERR on error
302  */
303 static int
304 postgres_plugin_del (void *cls)
305 {
306   struct Plugin *plugin = cls;
307   uint32_t size;
308   uint32_t oid;
309   GNUNET_HashCode key;
310   PGresult *res;
311
312   res = PQexecPrepared (plugin->dbh, "getm", 0, NULL, NULL, NULL, 1);
313   if (GNUNET_OK !=
314       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, "PQexecPrepared", "getm"))
315   {
316 #if DEBUG_POSTGRES
317     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
318                      "Ending iteration (postgres error)\n");
319 #endif
320     return 0;
321   }
322   if (0 == PQntuples (res))
323   {
324     /* no result */
325 #if DEBUG_POSTGRES
326     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datacache-postgres",
327                      "Ending iteration (no more results)\n");
328 #endif
329     PQclear (res);
330     return GNUNET_SYSERR;
331   }
332   if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) ||
333       (sizeof (oid) != PQfsize (res, 1)) ||
334       (sizeof (GNUNET_HashCode) != PQgetlength (res, 0, 2)))
335   {
336     GNUNET_break (0);
337     PQclear (res);
338     return 0;
339   }
340   size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
341   oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
342   memcpy (&key, PQgetvalue (res, 0, 2), sizeof (GNUNET_HashCode));
343   PQclear (res);
344   if (GNUNET_OK != GNUNET_POSTGRES_delete_by_rowid (plugin->dbh, "delrow", oid))
345     return GNUNET_SYSERR;
346   plugin->env->delete_notify (plugin->env->cls, &key, size + OVERHEAD);
347   return GNUNET_OK;
348 }
349
350
351 /**
352  * Entry point for the plugin.
353  *
354  * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
355  * @return the plugin's closure (our "struct Plugin")
356  */
357 void *
358 libgnunet_plugin_datacache_postgres_init (void *cls)
359 {
360   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
361   struct GNUNET_DATACACHE_PluginFunctions *api;
362   struct Plugin *plugin;
363
364   plugin = GNUNET_malloc (sizeof (struct Plugin));
365   plugin->env = env;
366
367   if (GNUNET_OK != init_connection (plugin))
368   {
369     GNUNET_free (plugin);
370     return NULL;
371   }
372
373   api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
374   api->cls = plugin;
375   api->get = &postgres_plugin_get;
376   api->put = &postgres_plugin_put;
377   api->del = &postgres_plugin_del;
378   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "datacache-postgres",
379                    _("Postgres datacache running\n"));
380   return api;
381 }
382
383
384 /**
385  * Exit point from the plugin.
386  *
387  * @param cls closure (our "struct Plugin")
388  * @return NULL
389  */
390 void *
391 libgnunet_plugin_datacache_postgres_done (void *cls)
392 {
393   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
394   struct Plugin *plugin = api->cls;
395
396   PQfinish (plugin->dbh);
397   GNUNET_free (plugin);
398   GNUNET_free (api);
399   return NULL;
400 }
401
402
403
404 /* end of plugin_datacache_postgres.c */