-fix dht flags
[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 LOG(kind,...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
33
34 /**
35  * Per-entry overhead estimate
36  */
37 #define OVERHEAD (sizeof(struct 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 struct 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 (struct 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 struct 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 (struct 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     LOG (GNUNET_ERROR_TYPE_DEBUG,
240          "Ending iteration (postgres error)\n");
241     return 0;
242   }
243
244   if (0 == (cnt = PQntuples (res)))
245   {
246     /* no result */
247     LOG (GNUNET_ERROR_TYPE_DEBUG, 
248          "Ending iteration (no more results)\n");
249     PQclear (res);
250     return 0;
251   }
252   if (iter == NULL)
253   {
254     PQclear (res);
255     return cnt;
256   }
257   if ((3 != PQnfields (res)) || (sizeof (uint64_t) != PQfsize (res, 0)) ||
258       (sizeof (uint32_t) != PQfsize (res, 1)))
259   {
260     GNUNET_break (0);
261     PQclear (res);
262     return 0;
263   }
264   for (i = 0; i < cnt; i++)
265   {
266     expiration_time.abs_value =
267         GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
268     type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
269     size = PQgetlength (res, i, 2);
270     LOG (GNUNET_ERROR_TYPE_DEBUG, 
271          "Found result of size %u bytes and type %u in database\n",
272          (unsigned int) size, (unsigned int) type);
273     if (GNUNET_SYSERR ==
274         iter (iter_cls, expiration_time, key, size, PQgetvalue (res, i, 2),
275               (enum GNUNET_BLOCK_Type) type))
276     {
277       LOG (GNUNET_ERROR_TYPE_DEBUG, 
278            "Ending iteration (client error)\n");
279       PQclear (res);
280       return cnt;
281     }
282   }
283   PQclear (res);
284   return cnt;
285 }
286
287
288 /**
289  * Delete the entry with the lowest expiration value
290  * from the datacache right now.
291  *
292  * @param cls closure (our "struct Plugin")
293  * @return GNUNET_OK on success, GNUNET_SYSERR on error
294  */
295 static int
296 postgres_plugin_del (void *cls)
297 {
298   struct Plugin *plugin = cls;
299   uint32_t size;
300   uint32_t oid;
301   struct GNUNET_HashCode key;
302   PGresult *res;
303
304   res = PQexecPrepared (plugin->dbh, "getm", 0, NULL, NULL, NULL, 1);
305   if (GNUNET_OK !=
306       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, "PQexecPrepared", "getm"))
307   {
308     LOG (GNUNET_ERROR_TYPE_DEBUG, 
309          "Ending iteration (postgres error)\n");
310     return 0;
311   }
312   if (0 == PQntuples (res))
313   {
314     /* no result */
315     LOG (GNUNET_ERROR_TYPE_DEBUG, 
316          "Ending iteration (no more results)\n");
317     PQclear (res);
318     return GNUNET_SYSERR;
319   }
320   if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) ||
321       (sizeof (oid) != PQfsize (res, 1)) ||
322       (sizeof (struct GNUNET_HashCode) != PQgetlength (res, 0, 2)))
323   {
324     GNUNET_break (0);
325     PQclear (res);
326     return 0;
327   }
328   size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
329   oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
330   memcpy (&key, PQgetvalue (res, 0, 2), sizeof (struct GNUNET_HashCode));
331   PQclear (res);
332   if (GNUNET_OK != GNUNET_POSTGRES_delete_by_rowid (plugin->dbh, "delrow", oid))
333     return GNUNET_SYSERR;
334   plugin->env->delete_notify (plugin->env->cls, &key, size + OVERHEAD);
335   return GNUNET_OK;
336 }
337
338
339 /**
340  * Entry point for the plugin.
341  *
342  * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
343  * @return the plugin's closure (our "struct Plugin")
344  */
345 void *
346 libgnunet_plugin_datacache_postgres_init (void *cls)
347 {
348   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
349   struct GNUNET_DATACACHE_PluginFunctions *api;
350   struct Plugin *plugin;
351
352   plugin = GNUNET_malloc (sizeof (struct Plugin));
353   plugin->env = env;
354
355   if (GNUNET_OK != init_connection (plugin))
356   {
357     GNUNET_free (plugin);
358     return NULL;
359   }
360
361   api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
362   api->cls = plugin;
363   api->get = &postgres_plugin_get;
364   api->put = &postgres_plugin_put;
365   api->del = &postgres_plugin_del;
366   LOG (GNUNET_ERROR_TYPE_INFO, 
367        _("Postgres datacache running\n"));
368   return api;
369 }
370
371
372 /**
373  * Exit point from the plugin.
374  *
375  * @param cls closure (our "struct Plugin")
376  * @return NULL
377  */
378 void *
379 libgnunet_plugin_datacache_postgres_done (void *cls)
380 {
381   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
382   struct Plugin *plugin = api->cls;
383
384   PQfinish (plugin->dbh);
385   GNUNET_free (plugin);
386   GNUNET_free (api);
387   return NULL;
388 }
389
390
391
392 /* end of plugin_datacache_postgres.c */