Merge remote-tracking branch 'origin/master' into credentials
[oweals/gnunet.git] / src / datacache / plugin_datacache_postgres.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2006, 2009, 2010, 2012, 2015, 2017 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, 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_pq_lib.h"
29 #include "gnunet_datacache_plugin.h"
30
31 #define LOG(kind,...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
32
33 /**
34  * Per-entry overhead estimate
35  */
36 #define OVERHEAD (sizeof(struct 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    * Number of key-value pairs in the database.
55    */
56   unsigned int num_items;
57 };
58
59
60 /**
61  * @brief Get a database handle
62  *
63  * @param plugin global context
64  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
65  */
66 static int
67 init_connection (struct Plugin *plugin)
68 {
69   struct GNUNET_PQ_ExecuteStatement es[] = {
70     GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS gn090dc ("
71                             "  type INTEGER NOT NULL,"
72                             "  discard_time BIGINT NOT NULL,"
73                             "  key BYTEA NOT NULL,"
74                             "  value BYTEA NOT NULL,"
75                             "  path BYTEA DEFAULT NULL)"
76                             "WITH OIDS"),
77     GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS idx_key ON gn090dc (key)"),
78     GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS idx_dt ON gn090dc (discard_time)"),
79     GNUNET_PQ_make_execute ("ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL"),
80     GNUNET_PQ_make_execute ("ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN"),
81     GNUNET_PQ_EXECUTE_STATEMENT_END
82   };
83   struct GNUNET_PQ_PreparedStatement ps[] = {
84     GNUNET_PQ_make_prepare ("getkt",
85                             "SELECT discard_time,type,value,path FROM gn090dc "
86                             "WHERE key=$1 AND type=$2",
87                             2),
88     GNUNET_PQ_make_prepare ("getk",
89                             "SELECT discard_time,type,value,path FROM gn090dc "
90                             "WHERE key=$1",
91                             1),
92     GNUNET_PQ_make_prepare ("getm",
93                             "SELECT length(value) AS len,oid,key FROM gn090dc "
94                             "ORDER BY discard_time ASC LIMIT 1",
95                             0),
96     GNUNET_PQ_make_prepare ("get_random",
97                             "SELECT discard_time,type,value,path,key FROM gn090dc "
98                             "ORDER BY key ASC LIMIT 1 OFFSET $1",
99                             1),
100     GNUNET_PQ_make_prepare ("get_closest",
101                             "SELECT discard_time,type,value,path,key FROM gn090dc "
102                             "WHERE key>=$1 ORDER BY key ASC LIMIT $2",
103                             1),
104     GNUNET_PQ_make_prepare ("delrow",
105                             "DELETE FROM gn090dc WHERE oid=$1",
106                             1),
107     GNUNET_PQ_make_prepare ("put",
108                             "INSERT INTO gn090dc (type, discard_time, key, value, path) "
109                             "VALUES ($1, $2, $3, $4, $5)",
110                             5),
111     GNUNET_PQ_PREPARED_STATEMENT_END
112   };
113
114   plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
115                                             "datacache-postgres");
116   if (NULL == plugin->dbh)
117     return GNUNET_SYSERR;
118   if (GNUNET_OK !=
119       GNUNET_PQ_exec_statements (plugin->dbh,
120                                  es))
121   {
122     PQfinish (plugin->dbh);
123     plugin->dbh = NULL;
124     return GNUNET_SYSERR;
125   }
126
127   if (GNUNET_OK !=
128       GNUNET_PQ_prepare_statements (plugin->dbh,
129                                     ps))
130   {
131     PQfinish (plugin->dbh);
132     plugin->dbh = NULL;
133     return GNUNET_SYSERR;
134   }
135   return GNUNET_OK;
136 }
137
138
139 /**
140  * Store an item in the datastore.
141  *
142  * @param cls closure (our `struct Plugin`)
143  * @param key key to store @a data under
144  * @param data_size number of bytes in @a data
145  * @param data data to store
146  * @param type type of the value
147  * @param discard_time when to discard the value in any case
148  * @param path_info_len number of entries in @a path_info
149  * @param path_info a path through the network
150  * @return 0 if duplicate, -1 on error, number of bytes used otherwise
151  */
152 static ssize_t
153 postgres_plugin_put (void *cls,
154                      const struct GNUNET_HashCode *key,
155                      size_t data_size,
156                      const char *data,
157                      enum GNUNET_BLOCK_Type type,
158                      struct GNUNET_TIME_Absolute discard_time,
159                      unsigned int path_info_len,
160                      const struct GNUNET_PeerIdentity *path_info)
161 {
162   struct Plugin *plugin = cls;
163   uint32_t type32 = (uint32_t) type;
164   struct GNUNET_PQ_QueryParam params[] = {
165     GNUNET_PQ_query_param_uint32 (&type32),
166     GNUNET_PQ_query_param_absolute_time (&discard_time),
167     GNUNET_PQ_query_param_auto_from_type (key),
168     GNUNET_PQ_query_param_fixed_size (data, data_size),
169     GNUNET_PQ_query_param_fixed_size (path_info,
170                                       path_info_len * sizeof (struct GNUNET_PeerIdentity)),
171     GNUNET_PQ_query_param_end
172   };
173   enum GNUNET_DB_QueryStatus ret;
174
175   ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
176                                             "put",
177                                             params);
178   if (0 > ret)
179     return -1;
180   plugin->num_items++;
181   return data_size + OVERHEAD;
182 }
183
184
185 /**
186  * Closure for #handle_results.
187  */
188 struct HandleResultContext
189 {
190
191   /**
192    * Function to call on each result, may be NULL.
193    */
194   GNUNET_DATACACHE_Iterator iter;
195
196   /**
197    * Closure for @e iter.
198    */
199   void *iter_cls;
200
201   /**
202    * Key used.
203    */
204   const struct GNUNET_HashCode *key;
205 };
206
207
208 /**
209  * Function to be called with the results of a SELECT statement
210  * that has returned @a num_results results.  Parse the result
211  * and call the callback given in @a cls
212  *
213  * @param cls closure of type `struct HandleResultContext`
214  * @param result the postgres result
215  * @param num_result the number of results in @a result
216  */
217 static void
218 handle_results (void *cls,
219                 PGresult *result,
220                 unsigned int num_results)
221 {
222   struct HandleResultContext *hrc = cls;
223
224   for (unsigned int i=0;i<num_results;i++)
225   {
226     struct GNUNET_TIME_Absolute expiration_time;
227     uint32_t type;
228     void *data;
229     size_t data_size;
230     struct GNUNET_PeerIdentity *path;
231     size_t path_len;
232     struct GNUNET_PQ_ResultSpec rs[] = {
233       GNUNET_PQ_result_spec_absolute_time ("discard_time",
234                                            &expiration_time),
235       GNUNET_PQ_result_spec_uint32 ("type",
236                                     &type),
237       GNUNET_PQ_result_spec_variable_size ("value",
238                                            &data,
239                                            &data_size),
240       GNUNET_PQ_result_spec_variable_size ("path",
241                                            (void **) &path,
242                                            &path_len),
243       GNUNET_PQ_result_spec_end
244     };
245
246     if (GNUNET_YES !=
247         GNUNET_PQ_extract_result (result,
248                                   rs,
249                                   i))
250     {
251       GNUNET_break (0);
252       return;
253     }
254     if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
255     {
256       GNUNET_break (0);
257       path_len = 0;
258     }
259     path_len %= sizeof (struct GNUNET_PeerIdentity);
260     LOG (GNUNET_ERROR_TYPE_DEBUG,
261          "Found result of size %u bytes and type %u in database\n",
262          (unsigned int) data_size,
263          (unsigned int) type);
264     if ( (NULL != hrc->iter) &&
265          (GNUNET_SYSERR ==
266           hrc->iter (hrc->iter_cls,
267                      hrc->key,
268                      data_size,
269                      data,
270                      (enum GNUNET_BLOCK_Type) type,
271                      expiration_time,
272                      path_len,
273                      path)) )
274     {
275       LOG (GNUNET_ERROR_TYPE_DEBUG,
276            "Ending iteration (client error)\n");
277       GNUNET_PQ_cleanup_result (rs);
278       return;
279     }
280     GNUNET_PQ_cleanup_result (rs);
281   }
282 }
283
284
285 /**
286  * Iterate over the results for a particular key
287  * in the datastore.
288  *
289  * @param cls closure (our `struct Plugin`)
290  * @param key key to look for
291  * @param type entries of which type are relevant?
292  * @param iter maybe NULL (to just count)
293  * @param iter_cls closure for @a iter
294  * @return the number of results found
295  */
296 static unsigned int
297 postgres_plugin_get (void *cls,
298                      const struct GNUNET_HashCode *key,
299                      enum GNUNET_BLOCK_Type type,
300                      GNUNET_DATACACHE_Iterator iter,
301                      void *iter_cls)
302 {
303   struct Plugin *plugin = cls;
304   uint32_t type32 = (uint32_t) type;
305   struct GNUNET_PQ_QueryParam paramk[] = {
306     GNUNET_PQ_query_param_auto_from_type (key),
307     GNUNET_PQ_query_param_end
308   };
309   struct GNUNET_PQ_QueryParam paramkt[] = {
310     GNUNET_PQ_query_param_auto_from_type (key),
311     GNUNET_PQ_query_param_uint32 (&type32),
312     GNUNET_PQ_query_param_end
313   };
314   enum GNUNET_DB_QueryStatus res;
315   struct HandleResultContext hr_ctx;
316
317   hr_ctx.iter = iter;
318   hr_ctx.iter_cls = iter_cls;
319   hr_ctx.key = key;
320   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
321                                               (0 == type) ? "getk" : "getkt",
322                                               (0 == type) ? paramk : paramkt,
323                                               &handle_results,
324                                               &hr_ctx);
325   if (res < 0)
326     return 0;
327   return res;
328 }
329
330
331 /**
332  * Delete the entry with the lowest expiration value
333  * from the datacache right now.
334  *
335  * @param cls closure (our `struct Plugin`)
336  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
337  */
338 static int
339 postgres_plugin_del (void *cls)
340 {
341   struct Plugin *plugin = cls;
342   struct GNUNET_PQ_QueryParam pempty[] = {
343     GNUNET_PQ_query_param_end
344   };
345   uint32_t size;
346   uint32_t oid;
347   struct GNUNET_HashCode key;
348   struct GNUNET_PQ_ResultSpec rs[] = {
349     GNUNET_PQ_result_spec_uint32 ("len",
350                                   &size),
351     GNUNET_PQ_result_spec_uint32 ("oid",
352                                   &oid),
353     GNUNET_PQ_result_spec_auto_from_type ("key",
354                                           &key),
355     GNUNET_PQ_result_spec_end
356   };
357   enum GNUNET_DB_QueryStatus res;
358   struct GNUNET_PQ_QueryParam dparam[] = {
359     GNUNET_PQ_query_param_uint32 (&oid),
360     GNUNET_PQ_query_param_end
361   };
362
363   res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
364                                                   "getm",
365                                                   pempty,
366                                                   rs);
367   if (0 > res)
368     return GNUNET_SYSERR;
369   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
370   {
371     /* no result */
372     LOG (GNUNET_ERROR_TYPE_DEBUG,
373          "Ending iteration (no more results)\n");
374     return 0;
375   }
376   res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
377                                             "delrow",
378                                             dparam);
379   if (0 > res)
380   {
381     GNUNET_PQ_cleanup_result (rs);
382     return GNUNET_SYSERR;
383   }
384   plugin->num_items--;
385   plugin->env->delete_notify (plugin->env->cls,
386                               &key,
387                               size + OVERHEAD);
388   GNUNET_PQ_cleanup_result (rs);
389   return GNUNET_OK;
390 }
391
392
393 /**
394  * Obtain a random key-value pair from the datacache.
395  *
396  * @param cls closure (our `struct Plugin`)
397  * @param iter maybe NULL (to just count)
398  * @param iter_cls closure for @a iter
399  * @return the number of results found, zero (datacache empty) or one
400  */
401 static unsigned int
402 postgres_plugin_get_random (void *cls,
403                             GNUNET_DATACACHE_Iterator iter,
404                             void *iter_cls)
405 {
406   struct Plugin *plugin = cls;
407   uint32_t off;
408   struct GNUNET_TIME_Absolute expiration_time;
409   size_t data_size;
410   void *data;
411   size_t path_len;
412   struct GNUNET_PeerIdentity *path;
413   struct GNUNET_HashCode key;
414   uint32_t type;
415   enum GNUNET_DB_QueryStatus res;
416   struct GNUNET_PQ_QueryParam params[] = {
417     GNUNET_PQ_query_param_uint32 (&off),
418     GNUNET_PQ_query_param_end
419   };
420   struct GNUNET_PQ_ResultSpec rs[] = {
421     GNUNET_PQ_result_spec_absolute_time ("discard_time",
422                                          &expiration_time),
423     GNUNET_PQ_result_spec_uint32 ("type",
424                                   &type),
425     GNUNET_PQ_result_spec_variable_size ("value",
426                                          &data,
427                                          &data_size),
428     GNUNET_PQ_result_spec_variable_size ("path",
429                                          (void **) &path,
430                                          &path_len),
431     GNUNET_PQ_result_spec_auto_from_type ("key",
432                                           &key),
433     GNUNET_PQ_result_spec_end
434   };
435
436   if (0 == plugin->num_items)
437     return 0;
438   if (NULL == iter)
439     return 1;
440   off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
441                                   plugin->num_items);
442   res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
443                                                   "get_random",
444                                                   params,
445                                                   rs);
446   if (0 > res)
447   {
448     GNUNET_break (0);
449     return 0;
450   }
451   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
452   {
453     GNUNET_break (0);
454     return 0;
455   }
456   if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
457   {
458     GNUNET_break (0);
459     path_len = 0;
460   }
461   path_len %= sizeof (struct GNUNET_PeerIdentity);
462   LOG (GNUNET_ERROR_TYPE_DEBUG,
463        "Found random value with key %s of size %u bytes and type %u in database\n",
464        GNUNET_h2s (&key),
465        (unsigned int) data_size,
466        (unsigned int) type);
467   (void) iter (iter_cls,
468                &key,
469                data_size,
470                data,
471                (enum GNUNET_BLOCK_Type) type,
472                expiration_time,
473                path_len,
474                path);
475   GNUNET_PQ_cleanup_result (rs);
476   return 1;
477 }
478
479
480 /**
481  * Closure for #extract_result_cb.
482  */
483 struct ExtractResultContext
484 {
485   /**
486    * Function to call for each result found.
487    */
488   GNUNET_DATACACHE_Iterator iter;
489
490   /**
491    * Closure for @e iter.
492    */
493   void *iter_cls;
494
495 };
496
497
498 /**
499  * Function to be called with the results of a SELECT statement
500  * that has returned @a num_results results.  Calls the `iter`
501  * from @a cls for each result.
502  *
503  * @param cls closure with the `struct ExtractResultContext`
504  * @param result the postgres result
505  * @param num_result the number of results in @a result
506  */
507 static void
508 extract_result_cb (void *cls,
509                    PGresult *result,
510                    unsigned int num_results)
511 {
512   struct ExtractResultContext *erc = cls;
513
514   if (NULL == erc->iter)
515     return;
516   for (unsigned int i=0;i<num_results;i++)
517   {
518     struct GNUNET_TIME_Absolute expiration_time;
519     uint32_t type;
520     void *data;
521     size_t data_size;
522     struct GNUNET_PeerIdentity *path;
523     size_t path_len;
524     struct GNUNET_HashCode key;
525     struct GNUNET_PQ_ResultSpec rs[] = {
526       GNUNET_PQ_result_spec_absolute_time ("",
527                                            &expiration_time),
528       GNUNET_PQ_result_spec_uint32 ("type",
529                                     &type),
530       GNUNET_PQ_result_spec_variable_size ("value",
531                                            &data,
532                                            &data_size),
533       GNUNET_PQ_result_spec_variable_size ("path",
534                                            (void **) &path,
535                                            &path_len),
536       GNUNET_PQ_result_spec_auto_from_type ("key",
537                                             &key),
538       GNUNET_PQ_result_spec_end
539     };
540
541     if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
542     {
543       GNUNET_break (0);
544       path_len = 0;
545     }
546     path_len %= sizeof (struct GNUNET_PeerIdentity);
547     LOG (GNUNET_ERROR_TYPE_DEBUG,
548          "Found result of size %u bytes and type %u in database\n",
549          (unsigned int) data_size,
550          (unsigned int) type);
551     if (GNUNET_SYSERR ==
552         erc->iter (erc->iter_cls,
553                    &key,
554                    data_size,
555                    data,
556                    (enum GNUNET_BLOCK_Type) type,
557                    expiration_time,
558                    path_len,
559                    path))
560     {
561       LOG (GNUNET_ERROR_TYPE_DEBUG,
562            "Ending iteration (client error)\n");
563       GNUNET_PQ_cleanup_result (rs);
564       break;
565     }
566     GNUNET_PQ_cleanup_result (rs);
567   }
568 }
569
570
571 /**
572  * Iterate over the results that are "close" to a particular key in
573  * the datacache.  "close" is defined as numerically larger than @a
574  * key (when interpreted as a circular address space), with small
575  * distance.
576  *
577  * @param cls closure (internal context for the plugin)
578  * @param key area of the keyspace to look into
579  * @param num_results number of results that should be returned to @a iter
580  * @param iter maybe NULL (to just count)
581  * @param iter_cls closure for @a iter
582  * @return the number of results found
583  */
584 static unsigned int
585 postgres_plugin_get_closest (void *cls,
586                              const struct GNUNET_HashCode *key,
587                              unsigned int num_results,
588                              GNUNET_DATACACHE_Iterator iter,
589                              void *iter_cls)
590 {
591   struct Plugin *plugin = cls;
592   uint32_t num_results32 = (uint32_t) num_results;
593   struct GNUNET_PQ_QueryParam params[] = {
594     GNUNET_PQ_query_param_auto_from_type (key),
595     GNUNET_PQ_query_param_uint32 (&num_results32),
596     GNUNET_PQ_query_param_end
597   };
598   enum GNUNET_DB_QueryStatus res;
599   struct ExtractResultContext erc;
600
601   erc.iter = iter;
602   erc.iter_cls = iter_cls;
603   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
604                                               "get_closest",
605                                               params,
606                                               &extract_result_cb,
607                                               &erc);
608   if (0 > res)
609   {
610     LOG (GNUNET_ERROR_TYPE_DEBUG,
611          "Ending iteration (postgres error)\n");
612     return 0;
613   }
614   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
615   {
616     /* no result */
617     LOG (GNUNET_ERROR_TYPE_DEBUG,
618          "Ending iteration (no more results)\n");
619     return 0;
620   }
621   return res;
622 }
623
624
625 /**
626  * Entry point for the plugin.
627  *
628  * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
629  * @return the plugin's closure (our `struct Plugin`)
630  */
631 void *
632 libgnunet_plugin_datacache_postgres_init (void *cls)
633 {
634   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
635   struct GNUNET_DATACACHE_PluginFunctions *api;
636   struct Plugin *plugin;
637
638   plugin = GNUNET_new (struct Plugin);
639   plugin->env = env;
640
641   if (GNUNET_OK != init_connection (plugin))
642   {
643     GNUNET_free (plugin);
644     return NULL;
645   }
646
647   api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
648   api->cls = plugin;
649   api->get = &postgres_plugin_get;
650   api->put = &postgres_plugin_put;
651   api->del = &postgres_plugin_del;
652   api->get_random = &postgres_plugin_get_random;
653   api->get_closest = &postgres_plugin_get_closest;
654   LOG (GNUNET_ERROR_TYPE_INFO,
655        "Postgres datacache running\n");
656   return api;
657 }
658
659
660 /**
661  * Exit point from the plugin.
662  *
663  * @param cls closure (our `struct Plugin`)
664  * @return NULL
665  */
666 void *
667 libgnunet_plugin_datacache_postgres_done (void *cls)
668 {
669   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
670   struct Plugin *plugin = api->cls;
671
672   PQfinish (plugin->dbh);
673   GNUNET_free (plugin);
674   GNUNET_free (api);
675   return NULL;
676 }
677
678
679
680 /* end of plugin_datacache_postgres.c */