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