mysql hacking
[oweals/gnunet.git] / src / datastore / plugin_datastore_mysql.c
1 /*
2      This file is part of GNUnet
3      (C) 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 datastore/plugin_datastore_mysql.c
23  * @brief mysql-based datastore backend
24  * @author Igor Wronsky
25  * @author Christian Grothoff
26  *
27  * NOTE: This db module does NOT work with mysql prior to 4.1 since
28  * it uses prepared statements.  MySQL 5.0.46 promises to fix a bug
29  * in MyISAM that is causing us grief.  At the time of this writing,
30  * that version is yet to be released.  In anticipation, the code
31  * will use MyISAM with 5.0.46 (and higher).  If you run such a
32  * version, please run "make check" to verify that the MySQL bug
33  * was actually fixed in your version (and if not, change the
34  * code below to use MyISAM for gn071).
35  *
36  * HIGHLIGHTS
37  *
38  * Pros
39  * + On up-to-date hardware where mysql can be used comfortably, this
40  *   module will have better performance than the other db choices
41  *   (according to our tests).
42  * + Its often possible to recover the mysql database from internal
43  *   inconsistencies. The other db choices do not support repair!
44  * Cons
45  * - Memory usage (Comment: "I have 1G and it never caused me trouble")
46  * - Manual setup
47  *
48  * MANUAL SETUP INSTRUCTIONS
49  *
50  * 1) in /etc/gnunet.conf, set
51  *    <pre>
52  *
53  *     sqstore = "sqstore_mysql"
54  *
55  *    </pre>
56  * 2) Then access mysql as root,
57  *    <pre>
58  *
59  *    $ mysql -u root -p
60  *
61  *    </pre>
62  *    and do the following. [You should replace $USER with the username
63  *    that will be running the gnunetd process].
64  *    <pre>
65  *
66       CREATE DATABASE gnunet;
67       GRANT select,insert,update,delete,create,alter,drop,create temporary tables
68          ON gnunet.* TO $USER@localhost;
69       SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
70       FLUSH PRIVILEGES;
71  *
72  *    </pre>
73  * 3) In the $HOME directory of $USER, create a ".my.cnf" file
74  *    with the following lines
75  *    <pre>
76
77       [client]
78       user=$USER
79       password=$the_password_you_like
80
81  *    </pre>
82  *
83  * Thats it. Note that .my.cnf file is a security risk unless its on
84  * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
85  * link. Even greater security risk can be achieved by setting no
86  * password for $USER.  Luckily $USER has only priviledges to mess
87  * up GNUnet's tables, nothing else (unless you give him more,
88  * of course).<p>
89  *
90  * 4) Still, perhaps you should briefly try if the DB connection
91  *    works. First, login as $USER. Then use,
92  *
93  *    <pre>
94  *    $ mysql -u $USER -p $the_password_you_like
95  *    mysql> use gnunet;
96  *    </pre>
97  *
98  *    If you get the message &quot;Database changed&quot; it probably works.
99  *
100  *    [If you get &quot;ERROR 2002: Can't connect to local MySQL server
101  *     through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
102  *     &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
103  *     so there may be some additional trouble depending on your mysql setup.]
104  *
105  * REPAIRING TABLES
106  *
107  * - Its probably healthy to check your tables for inconsistencies
108  *   every now and then.
109  * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
110  *   databases have been corrupted.
111  * - The tables can be verified/fixed in two ways;
112  *   1) by running mysqlcheck -A, or
113  *   2) by executing (inside of mysql using the GNUnet database):
114  *   mysql> REPAIR TABLE gn080;
115  *   mysql> REPAIR TABLE gn072;
116  *
117  * PROBLEMS?
118  *
119  * If you have problems related to the mysql module, your best
120  * friend is probably the mysql manual. The first thing to check
121  * is that mysql is basically operational, that you can connect
122  * to it, create tables, issue queries etc.
123  *
124  * TODO:
125  * - implement GET
126  * - remove 'size' field in gn080.
127  * - use FOREIGN KEY for 'uid/vkey'
128  * - consistent naming of uid/vkey
129  */
130
131 #include "platform.h"
132 #include "plugin_datastore.h"
133 #include "gnunet_util_lib.h"
134 #include <mysql/mysql.h>
135
136 #define DEBUG_MYSQL GNUNET_NO
137
138 #define MAX_DATUM_SIZE 65536
139
140 /**
141  * Maximum number of supported parameters for a prepared
142  * statement.  Increase if needed.
143  */
144 #define MAX_PARAM 16
145
146 /**
147  * Die with an error message that indicates
148  * a failure of the command 'cmd' with the message given
149  * by strerror(errno).
150  */
151 #define DIE_MYSQL(cmd, dbh) do { GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); abort(); } while(0);
152
153 /**
154  * Log an error message at log-level 'level' that indicates
155  * a failure of the command 'cmd' on file 'filename'
156  * with the message given by strerror(errno).
157  */
158 #define LOG_MYSQL(level, cmd, dbh) do { GNUNET_log(level, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0);
159
160
161 /* warning, slighly crazy mysql statements ahead.  Essentially, MySQL does not handle
162    "OR" very well, so we need to use UNION instead.  And UNION does not
163    automatically apply a LIMIT on the outermost clause, so we need to
164    repeat ourselves quite a bit.  All hail the performance gods (and thanks
165    to #mysql on freenode) */
166 #define SELECT_IT_LOW_PRIORITY "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio = ? AND vkey > ?) "\
167                                "ORDER BY prio ASC,vkey ASC LIMIT 1) "\
168                                "UNION "\
169                                "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio > ? AND vkey != ?)"\
170                                "ORDER BY prio ASC,vkey ASC LIMIT 1)"\
171                                "ORDER BY prio ASC,vkey ASC LIMIT 1"
172
173 #define SELECT_IT_NON_ANONYMOUS "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio = ? AND vkey < ?)"\
174                                 " AND anonLevel=0 ORDER BY prio DESC,vkey DESC LIMIT 1) "\
175                                 "UNION "\
176                                 "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio < ? AND vkey != ?)"\
177                                 " AND anonLevel=0 ORDER BY prio DESC,vkey DESC LIMIT 1) "\
178                                 "ORDER BY prio DESC,vkey DESC LIMIT 1"
179
180 #define SELECT_IT_EXPIRATION_TIME "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire = ? AND vkey > ?) "\
181                                   "ORDER BY expire ASC,vkey ASC LIMIT 1) "\
182                                   "UNION "\
183                                   "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire > ? AND vkey != ?) "\
184                                   "ORDER BY expire ASC,vkey ASC LIMIT 1)"\
185                                   "ORDER BY expire ASC,vkey ASC LIMIT 1"
186
187
188 #define SELECT_IT_MIGRATION_ORDER "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire = ? AND vkey < ?)"\
189                                   " AND expire > ? AND type!=3"\
190                                   " ORDER BY expire DESC,vkey DESC LIMIT 1) "\
191                                   "UNION "\
192                                   "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire < ? AND vkey != ?)"\
193                                   " AND expire > ? AND type!=3"\
194                                   " ORDER BY expire DESC,vkey DESC LIMIT 1)"\
195                                   "ORDER BY expire DESC,vkey DESC LIMIT 1"
196
197 #define SELECT_SIZE "SELECT sum(size) FROM gn080"
198
199
200 struct GNUNET_MysqlStatementHandle
201 {
202   struct GNUNET_MysqlStatementHandle *next;
203
204   struct GNUNET_MysqlStatementHandle *prev;
205
206   char *query;
207
208   MYSQL_STMT *statement;
209
210   int valid;
211
212 };
213
214 /**
215  * Context for the universal iterator.
216  */
217 struct NextRequestClosure;
218
219 /**
220  * Type of a function that will prepare
221  * the next iteration.
222  *
223  * @param cls closure
224  * @param nc the next context; NULL for the last
225  *         call which gives the callback a chance to
226  *         clean up the closure
227  * @return GNUNET_OK on success, GNUNET_NO if there are
228  *         no more values, GNUNET_SYSERR on error
229  */
230 typedef int (*PrepareFunction)(void *cls,
231                                struct NextRequestClosure *nc);
232
233
234 struct NextRequestClosure
235 {
236   struct Plugin *plugin;
237
238   struct GNUNET_TIME_Absolute now;
239
240   /**
241    * Function to call to prepare the next
242    * iteration.
243    */
244   PrepareFunction prep;
245
246   /**
247    * Closure for prep.
248    */
249   void *prep_cls;
250
251   MYSQL_BIND rbind[7];
252
253   unsigned int type;
254   
255   unsigned int iter_select;
256
257   PluginIterator dviter;
258
259   void *dviter_cls;
260
261   unsigned int last_prio;
262
263   unsigned long long last_expire;
264
265   unsigned long long last_vkey;
266
267   int end_it;
268 };
269
270
271 /**
272  * Context for all functions in this plugin.
273  */
274 struct Plugin 
275 {
276   /**
277    * Our execution environment.
278    */
279   struct GNUNET_DATASTORE_PluginEnvironment *env;
280
281   MYSQL *dbf;
282   
283   struct GNUNET_MysqlStatementHandle *shead;
284
285   struct GNUNET_MysqlStatementHandle *stail;
286
287   /**
288    * Filename of "my.cnf" (msyql configuration).
289    */
290   char *cnffile;
291
292   /**
293    * Closure of the 'next_task' (must be freed if 'next_task' is cancelled).
294    */
295   struct NextRequestClosure *next_task_nc;
296
297   /**
298    * Pending task with scheduler for running the next request.
299    */
300   GNUNET_SCHEDULER_TaskIdentifier next_task;
301
302   /**
303    * Statements dealing with gn072 table 
304    */
305 #define SELECT_VALUE "SELECT value FROM gn072 WHERE vkey=?"
306   struct GNUNET_MysqlStatementHandle *select_value;
307
308 #define DELETE_VALUE "DELETE FROM gn072 WHERE vkey=?"
309   struct GNUNET_MysqlStatementHandle *delete_value;
310
311 #define INSERT_VALUE "INSERT INTO gn072 (value) VALUES (?)"
312   struct GNUNET_MysqlStatementHandle *insert_value;
313
314   /**
315    * Statements dealing with gn080 table 
316    */
317 #define INSERT_ENTRY "INSERT INTO gn080 (size,type,prio,anonLevel,expire,hash,vhash,vkey) VALUES (?,?,?,?,?,?,?,?)"
318   struct GNUNET_MysqlStatementHandle *insert_entry;
319   
320 #define DELETE_ENTRY_BY_VKEY "DELETE FROM gn080 WHERE vkey=?"
321   struct GNUNET_MysqlStatementHandle *delete_entry_by_vkey;
322   
323 #define SELECT_ENTRY_BY_HASH "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vkey) WHERE hash=? AND vkey > ? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
324   struct GNUNET_MysqlStatementHandle *select_entry_by_hash;
325   
326 #define SELECT_ENTRY_BY_HASH_AND_VHASH "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=? AND vkey > ? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
327   struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_vhash;
328
329 #define SELECT_ENTRY_BY_HASH_AND_TYPE "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vkey) WHERE hash=? AND vkey > ? AND type=? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
330   struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_type;
331   
332 #define SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=? AND vkey > ? AND type=? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
333   struct GNUNET_MysqlStatementHandle *select_entry_by_hash_vhash_and_type;
334   
335 #define COUNT_ENTRY_BY_HASH "SELECT count(*) FROM gn080 FORCE INDEX (hash) WHERE hash=?"
336   struct GNUNET_MysqlStatementHandle *count_entry_by_hash;
337
338 #define COUNT_ENTRY_BY_HASH_AND_VHASH "SELECT count(*) FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=?"
339   struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_vhash;
340
341 #define COUNT_ENTRY_BY_HASH_AND_TYPE "SELECT count(*) FROM gn080 FORCE INDEX (hash) WHERE hash=? AND type=?"
342   struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_type;
343
344 #define COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT count(*) FROM gn080 FORCE INDEX (hash_vhash) WHERE hash=? AND vhash=? AND type=?"
345   struct GNUNET_MysqlStatementHandle *count_entry_by_hash_vhash_and_type;
346
347 #define UPDATE_ENTRY "UPDATE gn080 SET prio=prio+?,expire=IF(expire>=?,expire,?) WHERE vkey=?"
348   struct GNUNET_MysqlStatementHandle *update_entry;
349
350   struct GNUNET_MysqlStatementHandle *iter[4];
351
352   //static unsigned int stat_size;
353
354   /**
355    * Size of the mysql database on disk.
356    */
357   unsigned long long content_size;
358
359 };
360
361
362 /**
363  * Obtain the location of ".my.cnf".
364  * @return NULL on error
365  */
366 static char *
367 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg)
368 {
369   char *cnffile;
370   char *home_dir;
371   struct stat st;
372 #ifndef WINDOWS
373   struct passwd *pw;
374 #endif
375
376 #ifndef WINDOWS
377   pw = getpwuid (getuid ());
378   if (!pw)
379     {
380       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 
381                            "getpwuid");
382       return NULL;
383     }
384   if (GNUNET_YES ==
385       GNUNET_CONFIGURATION_have_value (cfg,
386                                        "MYSQL", "CONFIG"))
387     {
388       GNUNET_CONFIGURATION_get_value_filename (cfg,
389                                                "MYSQL", "CONFIG", &cnffile);
390     }
391   else
392     {
393       home_dir = GNUNET_strdup (pw->pw_dir);
394 #else
395       home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
396       plibc_conv_to_win_path ("~/", home_dir);
397 #endif
398       GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
399       GNUNET_free (home_dir);
400     }
401   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
402               _("Trying to use file `%s' for MySQL configuration.\n"),
403               cnffile);
404   if ((0 != STAT (cnffile, &st)) ||
405       (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
406     {
407       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
408                   _("Could not access file `%s': %s\n"), cnffile,
409                   STRERROR (errno));
410       GNUNET_free (cnffile);
411       return NULL;
412     }
413   return cnffile;
414 }
415
416
417
418 /**
419  * Free a prepared statement.
420  */
421 static void
422 prepared_statement_destroy (struct Plugin *plugin, 
423                             struct GNUNET_MysqlStatementHandle
424                             *s)
425 {
426   GNUNET_CONTAINER_DLL_remove (plugin->shead,
427                                plugin->stail,
428                                s);
429   if (s->valid)
430     mysql_stmt_close (s->statement);
431   GNUNET_free (s->query);
432   GNUNET_free (s);
433 }
434
435
436 /**
437  * Close database connection and all prepared statements (we got a DB
438  * disconnect error).
439  */
440 static int
441 iclose (struct Plugin *plugin)
442 {
443   struct GNUNET_MysqlStatementHandle *spos;
444
445   spos = plugin->shead;
446   while (NULL != plugin->shead)
447     prepared_statement_destroy (plugin,
448                                 plugin->shead);
449   if (plugin->dbf != NULL)
450     {
451       mysql_close (plugin->dbf);
452       plugin->dbf = NULL;
453     }
454   return GNUNET_OK;
455 }
456
457
458 /**
459  * Open the connection with the database (and initialize
460  * our default options).
461  *
462  * @return GNUNET_OK on success
463  */
464 static int
465 iopen (struct Plugin *ret)
466 {
467   char *mysql_dbname;
468   char *mysql_server;
469   char *mysql_user;
470   char *mysql_password;
471   unsigned long long mysql_port;
472   my_bool reconnect;
473   unsigned int timeout;
474
475   ret->dbf = mysql_init (NULL);
476   if (ret->dbf == NULL)
477     return GNUNET_SYSERR;
478   if (ret->cnffile != NULL)
479     mysql_options (ret->dbf, MYSQL_READ_DEFAULT_FILE, ret->cnffile);
480   mysql_options (ret->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
481   reconnect = 0;
482   mysql_options (ret->dbf, MYSQL_OPT_RECONNECT, &reconnect);
483   mysql_options (ret->dbf,
484                  MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
485   timeout = 60; /* in seconds */
486   mysql_options (ret->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
487   mysql_options (ret->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
488   mysql_dbname = NULL;
489   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
490                                                      "MYSQL", "DATABASE"))
491     GNUNET_assert (GNUNET_OK == 
492                    GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
493                                                           "MYSQL", "DATABASE", 
494                                                           &mysql_dbname));
495   else
496     mysql_dbname = GNUNET_strdup ("gnunet");
497   mysql_user = NULL;
498   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
499                                                      "MYSQL", "USER"))
500     {
501       GNUNET_break (GNUNET_OK == 
502                     GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
503                                                            "MYSQL", "USER", 
504                                                            &mysql_user));
505     }
506   mysql_password = NULL;
507   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
508                                                      "MYSQL", "PASSWORD"))
509     {
510       GNUNET_break (GNUNET_OK ==
511                     GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
512                                                            "MYSQL", "PASSWORD",
513                                                            &mysql_password));
514     }
515   mysql_server = NULL;
516   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
517                                                      "MYSQL", "HOST"))
518     {
519       GNUNET_break (GNUNET_OK == 
520                     GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
521                                                            "MYSQL", "HOST", 
522                                                            &mysql_server));
523     }
524   mysql_port = 0;
525   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
526                                                      "MYSQL", "PORT"))
527     {
528       GNUNET_break (GNUNET_OK ==
529                     GNUNET_CONFIGURATION_get_value_number (ret->env->cfg, "MYSQL",
530                                                            "PORT", &mysql_port));
531     }
532
533   GNUNET_assert (mysql_dbname != NULL);
534   mysql_real_connect (ret->dbf, mysql_server, mysql_user, mysql_password,
535                       mysql_dbname, (unsigned int) mysql_port, NULL, 0);
536   GNUNET_free (mysql_dbname);
537   if (mysql_error (ret->dbf)[0])
538     {
539       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
540                  "mysql_real_connect", ret);
541       return GNUNET_SYSERR;
542     }
543   return GNUNET_OK;
544 }
545
546
547 /**
548  * Run the given MySQL statement.
549  *
550  * @return GNUNET_OK on success, GNUNET_SYSERR on error
551  */
552 static int
553 run_statement (struct Plugin *plugin,
554                const char *statement)
555 {
556   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
557     return GNUNET_SYSERR;
558   mysql_query (plugin->dbf, statement);
559   if (mysql_error (plugin->dbf)[0])
560     {
561       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
562                  "mysql_query", plugin);
563       iclose (plugin);
564       return GNUNET_SYSERR;
565     }
566   return GNUNET_OK;
567 }
568
569
570 #if 0
571 /**
572  * Run the given MySQL SELECT statement.  The statement
573  * must have only a single result (one column, one row).
574  *
575  * @return result on success, NULL on error
576  */
577 static char *
578 run_statement_select (struct Plugin *plugin,
579                       const char *statement)
580 {
581   MYSQL_RES *sql_res;
582   MYSQL_ROW sql_row;
583   char *ret;
584   
585   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
586     return NULL;
587   mysql_query (plugin->dbf, statement);
588   if ((mysql_error (plugin->dbf)[0]) ||
589       (!(sql_res = mysql_use_result (plugin->dbf))) ||
590       (!(sql_row = mysql_fetch_row (sql_res))))
591     {
592       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
593                  "mysql_query", plugin);
594       return NULL;
595     }
596   if ((mysql_num_fields (sql_res) != 1) || (sql_row[0] == NULL))
597     {
598       GNUNET_break (mysql_num_fields (sql_res) == 1);
599       if (sql_res != NULL)
600         mysql_free_result (sql_res);
601       return NULL;
602     }
603   ret = GNUNET_strdup (sql_row[0]);
604   mysql_free_result (sql_res);
605   return ret;
606 }
607 #endif
608
609
610 /**
611  * Create a prepared statement.
612  *
613  * @return NULL on error
614  */
615 static struct GNUNET_MysqlStatementHandle *
616 prepared_statement_create (struct Plugin *plugin, 
617                            const char *statement)
618 {
619   struct GNUNET_MysqlStatementHandle *ret;
620
621   ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
622   ret->query = GNUNET_strdup (statement);
623   GNUNET_CONTAINER_DLL_insert (plugin->shead,
624                                plugin->stail,
625                                ret);
626   return ret;
627 }
628
629
630 /**
631  * Prepare a statement for running.
632  *
633  * @return GNUNET_OK on success
634  */
635 static int
636 prepare_statement (struct Plugin *plugin, 
637                    struct GNUNET_MysqlStatementHandle *ret)
638 {
639   if (GNUNET_YES == ret->valid)
640     return GNUNET_OK;
641   if ((NULL == plugin->dbf) && 
642       (GNUNET_OK != iopen (plugin)))
643     return GNUNET_SYSERR;
644   ret->statement = mysql_stmt_init (plugin->dbf);
645   if (ret->statement == NULL)
646     {
647       iclose (plugin);
648       return GNUNET_SYSERR;
649     }
650   if (mysql_stmt_prepare (ret->statement, 
651                           ret->query,
652                           strlen (ret->query)))
653     {
654       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
655                  "mysql_stmt_prepare", 
656                  plugin);
657       mysql_stmt_close (ret->statement);
658       ret->statement = NULL;
659       iclose (plugin);
660       return GNUNET_SYSERR;
661     }
662   ret->valid = GNUNET_YES;
663   return GNUNET_OK;
664
665 }
666
667
668 /**
669  * Bind the parameters for the given MySQL statement
670  * and run it.
671  *
672  * @param s statement to bind and run
673  * @param ap arguments for the binding
674  * @return GNUNET_SYSERR on error, GNUNET_OK on success
675  */
676 static int
677 init_params (struct Plugin *plugin,
678              struct GNUNET_MysqlStatementHandle *s,
679              va_list ap)
680 {
681   MYSQL_BIND qbind[MAX_PARAM];
682   unsigned int pc;
683   unsigned int off;
684   enum enum_field_types ft;
685
686   pc = mysql_stmt_param_count (s->statement);
687   if (pc > MAX_PARAM)
688     {
689       /* increase internal constant! */
690       GNUNET_break (0);
691       return GNUNET_SYSERR;
692     }
693   memset (qbind, 0, sizeof (qbind));
694   off = 0;
695   ft = 0;
696   while ((pc > 0) && (-1 != (ft = va_arg (ap, enum enum_field_types))))
697     {
698       qbind[off].buffer_type = ft;
699       switch (ft)
700         {
701         case MYSQL_TYPE_FLOAT:
702           qbind[off].buffer = va_arg (ap, float *);
703           break;
704         case MYSQL_TYPE_LONGLONG:
705           qbind[off].buffer = va_arg (ap, unsigned long long *);
706           qbind[off].is_unsigned = va_arg (ap, int);
707           break;
708         case MYSQL_TYPE_LONG:
709           qbind[off].buffer = va_arg (ap, unsigned int *);
710           qbind[off].is_unsigned = va_arg (ap, int);
711           break;
712         case MYSQL_TYPE_VAR_STRING:
713         case MYSQL_TYPE_STRING:
714         case MYSQL_TYPE_BLOB:
715           qbind[off].buffer = va_arg (ap, void *);
716           qbind[off].buffer_length = va_arg (ap, unsigned long);
717           qbind[off].length = va_arg (ap, unsigned long *);
718           break;
719         default:
720           /* unsupported type */
721           GNUNET_break (0);
722           return GNUNET_SYSERR;
723         }
724       pc--;
725       off++;
726     }
727   if (!((pc == 0) && (ft != -1) && (va_arg (ap, int) == -1)))
728     {
729       GNUNET_break (0);
730       return GNUNET_SYSERR;
731     }
732   if (mysql_stmt_bind_param (s->statement, qbind))
733     {
734       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
735                   _("`%s' failed at %s:%d with error: %s\n"),
736                   "mysql_stmt_bind_param",
737                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
738       iclose (plugin);
739       return GNUNET_SYSERR;
740     }
741   if (mysql_stmt_execute (s->statement))
742     {
743       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
744                   _("`%s' failed at %s:%d with error: %s\n"),
745                   "mysql_stmt_execute",
746                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
747       iclose (plugin);
748       return GNUNET_SYSERR;
749     }
750   return GNUNET_OK;
751 }
752
753 /**
754  * Type of a callback that will be called for each
755  * data set returned from MySQL.
756  *
757  * @param cls user-defined argument
758  * @param num_values number of elements in values
759  * @param values values returned by MySQL
760  * @return GNUNET_OK to continue iterating, GNUNET_SYSERR to abort
761  */
762 typedef int (*GNUNET_MysqlDataProcessor) (void *cls,
763                                           unsigned int num_values,
764                                           MYSQL_BIND * values);
765
766
767 /**
768  * Run a prepared SELECT statement.
769  *
770  * @param result_size number of elements in results array
771  * @param results pointer to already initialized MYSQL_BIND
772  *        array (of sufficient size) for passing results
773  * @param processor function to call on each result
774  * @param processor_cls extra argument to processor
775  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
776  *        values (size + buffer-reference for pointers); terminated
777  *        with "-1"
778  * @return GNUNET_SYSERR on error, otherwise
779  *         the number of successfully affected (or queried) rows
780  */
781 static int
782 prepared_statement_run_select (struct Plugin *plugin,
783                                struct GNUNET_MysqlStatementHandle
784                                *s,
785                                unsigned int result_size,
786                                MYSQL_BIND * results,
787                                GNUNET_MysqlDataProcessor
788                                processor, void *processor_cls,
789                                ...)
790 {
791   va_list ap;
792   int ret;
793   unsigned int rsize;
794   int total;
795
796   if (GNUNET_OK != prepare_statement (plugin, s))
797     {
798       GNUNET_break (0);
799       return GNUNET_SYSERR;
800     }
801   va_start (ap, processor_cls);
802   if (GNUNET_OK != init_params (plugin, s, ap))
803     {
804       GNUNET_break (0);
805       va_end (ap);
806       return GNUNET_SYSERR;
807     }
808   va_end (ap);
809   rsize = mysql_stmt_field_count (s->statement);
810   if (rsize > result_size)
811     {
812       GNUNET_break (0);
813       return GNUNET_SYSERR;
814     }
815   if (mysql_stmt_bind_result (s->statement, results))
816     {
817       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
818                   _("`%s' failed at %s:%d with error: %s\n"),
819                   "mysql_stmt_bind_result",
820                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
821       iclose (plugin);
822       return GNUNET_SYSERR;
823     }
824
825   total = 0;
826   while (1)
827     {
828       ret = mysql_stmt_fetch (s->statement);
829       if (ret == MYSQL_NO_DATA)
830         break;
831       if (ret != 0)
832         {
833           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
834                       _("`%s' failed at %s:%d with error: %s\n"),
835                       "mysql_stmt_fetch",
836                       __FILE__, __LINE__, mysql_stmt_error (s->statement));
837           iclose (plugin);
838           return GNUNET_SYSERR;
839         }
840       if (processor != NULL)
841         if (GNUNET_OK != processor (processor_cls, rsize, results))
842           break;
843       total++;
844     }
845   mysql_stmt_reset (s->statement);
846   return total;
847 }
848
849
850 /**
851  * Run a prepared statement that does NOT produce results.
852  *
853  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
854  *        values (size + buffer-reference for pointers); terminated
855  *        with "-1"
856  * @param insert_id NULL or address where to store the row ID of whatever
857  *        was inserted (only for INSERT statements!)
858  * @return GNUNET_SYSERR on error, otherwise
859  *         the number of successfully affected rows
860  */
861 static int
862 prepared_statement_run (struct Plugin *plugin,
863                         struct GNUNET_MysqlStatementHandle *s,
864                         unsigned long long *insert_id, ...)
865 {
866   va_list ap;
867   int affected;
868
869   if (GNUNET_OK != prepare_statement (plugin, s))
870     return GNUNET_SYSERR;
871   va_start (ap, insert_id);
872   if (GNUNET_OK != init_params (plugin, s, ap))
873     {
874       va_end (ap);
875       return GNUNET_SYSERR;
876     }
877   va_end (ap);
878   affected = mysql_stmt_affected_rows (s->statement);
879   if (NULL != insert_id)
880     *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
881   mysql_stmt_reset (s->statement);
882   return affected;
883 }
884
885
886 /**
887  * Delete an value from the gn072 table.
888  *
889  * @param vkey vkey identifying the value to delete
890  * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
891  */
892 static int
893 do_delete_value (struct Plugin *plugin,
894                  unsigned long long vkey)
895 {
896   int ret;
897
898   ret = prepared_statement_run (plugin,
899                                 plugin->delete_value,
900                                 NULL,
901                                 MYSQL_TYPE_LONGLONG,
902                                 &vkey, GNUNET_YES, -1);
903   if (ret > 0)
904     ret = GNUNET_OK;
905   return ret;
906 }
907
908 /**
909  * Insert a value into the gn072 table.
910  *
911  * @param value the value to insert
912  * @param size size of the value
913  * @param vkey vkey identifying the value henceforth (set)
914  * @return GNUNET_OK on success, GNUNET_SYSERR on error
915  */
916 static int
917 do_insert_value (struct Plugin *plugin,
918                  const void *value, unsigned int size,
919                  unsigned long long *vkey)
920 {
921   unsigned long length = size;
922
923   return prepared_statement_run (plugin,
924                                  plugin->insert_value,
925                                  vkey,
926                                  MYSQL_TYPE_BLOB,
927                                  value, length, &length, -1);
928 }
929
930 /**
931  * Delete an entry from the gn080 table.
932  *
933  * @param vkey vkey identifying the entry to delete
934  * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
935  */
936 static int
937 do_delete_entry_by_vkey (struct Plugin *plugin,
938                          unsigned long long vkey)
939 {
940   int ret;
941
942   ret = prepared_statement_run (plugin,
943                                 plugin->delete_entry_by_vkey,
944                                 NULL,
945                                 MYSQL_TYPE_LONGLONG,
946                                 &vkey, GNUNET_YES, -1);
947   if (ret > 0)
948     ret = GNUNET_OK;
949   return ret;
950 }
951
952 static int
953 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
954 {
955   return GNUNET_OK;
956 }
957
958
959 static int
960 iterator_helper_prepare (void *cls,
961                          struct NextRequestClosure *nrc)
962 {
963   struct Plugin *plugin;
964   int ret;
965
966   if (nrc == NULL)
967     return GNUNET_NO;
968   plugin = nrc->plugin;
969   ret = GNUNET_SYSERR;
970   switch (nrc->iter_select)
971     {
972     case 0:
973     case 1:
974       ret = prepared_statement_run_select (plugin,
975                                            plugin->iter[nrc->iter_select],
976                                            7,
977                                            nrc->rbind,
978                                            &return_ok,
979                                            NULL,
980                                            MYSQL_TYPE_LONG,
981                                            &nrc->last_prio,
982                                            GNUNET_YES,
983                                            MYSQL_TYPE_LONGLONG,
984                                            &nrc->last_vkey,
985                                            GNUNET_YES,
986                                            MYSQL_TYPE_LONG,
987                                            &nrc->last_prio,
988                                            GNUNET_YES,
989                                            MYSQL_TYPE_LONGLONG,
990                                            &nrc->last_vkey,
991                                            GNUNET_YES, -1);
992       break;
993     case 2:
994       ret = prepared_statement_run_select (plugin,
995                                            plugin->iter[nrc->iter_select],
996                                            7,
997                                            nrc->rbind,
998                                            &return_ok,
999                                            NULL,
1000                                            MYSQL_TYPE_LONGLONG,
1001                                            &nrc->last_expire,
1002                                            GNUNET_YES,
1003                                            MYSQL_TYPE_LONGLONG,
1004                                            &nrc->last_vkey,
1005                                            GNUNET_YES,
1006                                            MYSQL_TYPE_LONGLONG,
1007                                            &nrc->last_expire,
1008                                            GNUNET_YES,
1009                                            MYSQL_TYPE_LONGLONG,
1010                                            &nrc->last_vkey,
1011                                            GNUNET_YES, -1);
1012       break;
1013     case 3:
1014       ret = prepared_statement_run_select (plugin,
1015                                            plugin->iter[nrc->iter_select],
1016                                            7,
1017                                            nrc->rbind,
1018                                            &return_ok,
1019                                            NULL,
1020                                            MYSQL_TYPE_LONGLONG,
1021                                            &nrc->last_expire,
1022                                            GNUNET_YES,
1023                                            MYSQL_TYPE_LONGLONG,
1024                                            &nrc->last_vkey,
1025                                            GNUNET_YES,
1026                                            MYSQL_TYPE_LONGLONG,
1027                                            &nrc->now.value,
1028                                            GNUNET_YES,
1029                                            MYSQL_TYPE_LONGLONG,
1030                                            &nrc->last_expire,
1031                                            GNUNET_YES,
1032                                            MYSQL_TYPE_LONGLONG,
1033                                            &nrc->last_vkey,
1034                                            GNUNET_YES,
1035                                            MYSQL_TYPE_LONGLONG,
1036                                            &nrc->now.value,
1037                                            GNUNET_YES, -1);
1038       break;
1039     default:
1040       GNUNET_assert (0);
1041     }
1042   return ret;
1043 }
1044
1045
1046 /**
1047  * Continuation of "sqlite_next_request".
1048  *
1049  * @param next_cls the next context
1050  * @param tc the task context (unused)
1051  */
1052 static void 
1053 sqlite_next_request_cont (void *next_cls,
1054                           const struct GNUNET_SCHEDULER_TaskContext *tc)
1055 {
1056   struct NextRequestClosure *nrc = next_cls;
1057   struct Plugin *plugin;
1058   int ret;
1059   unsigned int size;
1060   unsigned int type;
1061   unsigned int priority;
1062   unsigned int anonymity;
1063   unsigned long long exp;
1064   unsigned long long vkey;
1065   unsigned long hashSize;
1066   GNUNET_HashCode key;
1067   struct GNUNET_TIME_Absolute expiration;
1068   unsigned int contentSize;
1069   unsigned long length;
1070   MYSQL_BIND *rbind; /* size 7 */
1071   MYSQL_BIND dbind[1];
1072   char datum[GNUNET_SERVER_MAX_MESSAGE_SIZE];
1073
1074   plugin = nrc->plugin;
1075   plugin->next_task = GNUNET_SCHEDULER_NO_TASK;
1076   plugin->next_task_nc = NULL;
1077
1078  AGAIN:
1079   ret = nrc->prep (nrc->prep_cls,
1080                    nrc);
1081   if (ret != GNUNET_OK)
1082     goto END_SET;
1083   nrc->now = GNUNET_TIME_absolute_get ();
1084   hashSize = sizeof (GNUNET_HashCode);
1085   memset (nrc->rbind, 0, sizeof (nrc->rbind));
1086   rbind = nrc->rbind;
1087   rbind[0].buffer_type = MYSQL_TYPE_LONG;
1088   rbind[0].buffer = &size;
1089   rbind[0].is_unsigned = 1;
1090   rbind[1].buffer_type = MYSQL_TYPE_LONG;
1091   rbind[1].buffer = &type;
1092   rbind[1].is_unsigned = 1;
1093   rbind[2].buffer_type = MYSQL_TYPE_LONG;
1094   rbind[2].buffer = &priority;
1095   rbind[2].is_unsigned = 1;
1096   rbind[3].buffer_type = MYSQL_TYPE_LONG;
1097   rbind[3].buffer = &anonymity;
1098   rbind[3].is_unsigned = 1;
1099   rbind[4].buffer_type = MYSQL_TYPE_LONGLONG;
1100   rbind[4].buffer = &exp;
1101   rbind[4].is_unsigned = 1;
1102   rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1103   rbind[5].buffer = &key;
1104   rbind[5].buffer_length = hashSize;
1105   rbind[5].length = &hashSize;
1106   rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1107   rbind[6].buffer = &vkey;
1108   rbind[6].is_unsigned = GNUNET_YES;
1109
1110   nrc->last_vkey = vkey;
1111   nrc->last_prio = priority;
1112   nrc->last_expire = exp;
1113   if ((rbind[0].buffer_type != MYSQL_TYPE_LONG) ||
1114       (!rbind[0].is_unsigned) ||
1115       (rbind[1].buffer_type != MYSQL_TYPE_LONG) ||
1116       (!rbind[1].is_unsigned) ||
1117       (rbind[2].buffer_type != MYSQL_TYPE_LONG) ||
1118       (!rbind[2].is_unsigned) ||
1119       (rbind[3].buffer_type != MYSQL_TYPE_LONG) ||
1120       (!rbind[3].is_unsigned) ||
1121       (rbind[4].buffer_type != MYSQL_TYPE_LONGLONG) ||
1122       (!rbind[4].is_unsigned) ||
1123       (rbind[5].buffer_type != MYSQL_TYPE_BLOB) ||
1124       (rbind[5].buffer_length != sizeof (GNUNET_HashCode)) ||
1125       (*rbind[5].length != sizeof (GNUNET_HashCode)) ||
1126       (rbind[6].buffer_type != MYSQL_TYPE_LONGLONG) ||
1127       (!rbind[6].is_unsigned))
1128     {
1129       GNUNET_break (0);
1130       goto END_SET;
1131     }     
1132   contentSize = *(unsigned int *) rbind[0].buffer;
1133   if (contentSize >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
1134     {
1135       GNUNET_break (0); /* far too big */
1136       goto END_SET;
1137     }
1138   /* now do query on gn072 */
1139   length = contentSize;
1140   memset (rbind, 0, sizeof (rbind));
1141   rbind[0].buffer_type = MYSQL_TYPE_BLOB;
1142   rbind[0].buffer_length = contentSize;
1143   rbind[0].length = &length;
1144   rbind[0].buffer = datum;
1145   ret = prepared_statement_run_select (plugin,
1146                                        plugin->select_value,
1147                                        1,
1148                                        dbind,
1149                                        &return_ok,
1150                                        NULL,
1151                                        MYSQL_TYPE_LONGLONG,
1152                                        &vkey, GNUNET_YES, -1);
1153   GNUNET_break (ret <= 1);     /* should only have one rbind! */
1154   if (ret > 0)
1155     ret = GNUNET_OK;
1156   if ((ret != GNUNET_OK) ||
1157       (dbind[0].buffer_length != contentSize) || (length != contentSize))
1158     {
1159       GNUNET_break (ret != 0); /* should have one rbind! */
1160       GNUNET_break (length == contentSize);    /* length should match! */
1161       GNUNET_break (dbind[0].buffer_length == contentSize);    /* length should be internally consistent! */
1162       do_delete_value (plugin, vkey);
1163       if (ret != 0)
1164         do_delete_entry_by_vkey (plugin, vkey);
1165       plugin->content_size -= contentSize;
1166       goto AGAIN;
1167     }
1168   expiration.value = exp;
1169   ret = nrc->dviter (nrc->dviter_cls,
1170                      nrc,
1171                      &key,
1172                      contentSize,
1173                      datum,
1174                      type,
1175                      priority,
1176                      anonymity,
1177                      expiration,
1178                      vkey);
1179   if (ret == GNUNET_SYSERR)
1180     {
1181       /* is this correct, or should we not call iter again period?  */
1182       goto END_SET;
1183     }
1184   if (ret == GNUNET_NO)
1185     {
1186       do_delete_value (plugin, vkey);
1187       do_delete_entry_by_vkey (plugin, vkey);
1188       plugin->content_size -= contentSize;
1189     }
1190   return;
1191  END_SET:
1192   /* call dviter with "end of set" */
1193   nrc->dviter (nrc->dviter_cls, 
1194                NULL, NULL, 0, NULL, 0, 0, 0, 
1195                GNUNET_TIME_UNIT_ZERO_ABS, 0);
1196   nrc->prep (nrc->prep_cls, NULL);
1197   GNUNET_free (nrc);
1198 }
1199
1200
1201 /**
1202  * Function invoked on behalf of a "PluginIterator"
1203  * asking the database plugin to call the iterator
1204  * with the next item.
1205  *
1206  * @param next_cls whatever argument was given
1207  *        to the PluginIterator as "next_cls".
1208  * @param end_it set to GNUNET_YES if we
1209  *        should terminate the iteration early
1210  *        (iterator should be still called once more
1211  *         to signal the end of the iteration).
1212  */
1213 static void 
1214 mysql_plugin_next_request (void *next_cls,
1215                            int end_it)
1216 {
1217   struct NextRequestClosure *nrc = next_cls;
1218
1219   if (GNUNET_YES == end_it)
1220     nrc->end_it = GNUNET_YES;
1221   nrc->plugin->next_task_nc = nrc;
1222   nrc->plugin->next_task = GNUNET_SCHEDULER_add_now (nrc->plugin->env->sched,
1223                                                      &sqlite_next_request_cont,
1224                                                      nrc);
1225 }  
1226
1227
1228 /**
1229  * Iterate over the items in the datastore
1230  * using the given query to select and order
1231  * the items.
1232  *
1233  * @param type entries of which type should be considered?
1234  *        Use 0 for any type.
1235  * @param iter never NULL
1236  * @param is_asc are we using ascending order?
1237  */
1238 static void
1239 iterateHelper (struct Plugin *plugin,
1240                unsigned int type,
1241                int is_asc,
1242                unsigned int iter_select, 
1243                PluginIterator dviter,
1244                void *dviter_cls)
1245 {
1246   struct NextRequestClosure *nrc;
1247
1248   nrc = GNUNET_malloc (sizeof (struct NextRequestClosure));
1249   nrc->plugin = plugin;
1250   nrc->type = type;  
1251   nrc->iter_select = iter_select;
1252   nrc->dviter = dviter;
1253   nrc->dviter_cls = dviter_cls;
1254   nrc->prep = &iterator_helper_prepare;
1255   if (is_asc)
1256     {
1257       nrc->last_prio = 0;
1258       nrc->last_vkey = 0;
1259       nrc->last_expire = 0;
1260     }
1261   else
1262     {
1263       nrc->last_prio = 0x7FFFFFFFL;
1264       nrc->last_vkey = 0x7FFFFFFFFFFFFFFFLL; /* MySQL only supports 63 bits */
1265       nrc->last_expire = 0x7FFFFFFFFFFFFFFFLL;       /* MySQL only supports 63 bits */
1266     }
1267   mysql_plugin_next_request (nrc, GNUNET_NO);
1268 }
1269
1270
1271 /**
1272  * Get an estimate of how much space the database is
1273  * currently using.
1274  *
1275  * @param cls our "struct Plugin*"
1276  * @return number of bytes used on disk
1277  */
1278 static unsigned long long
1279 mysql_plugin_get_size (void *cls)
1280 {
1281   struct Plugin *plugin = cls;
1282   return plugin->content_size;
1283 }
1284
1285
1286 /**
1287  * Store an item in the datastore.
1288  *
1289  * @param cls closure
1290  * @param key key for the item
1291  * @param size number of bytes in data
1292  * @param data content stored
1293  * @param type type of the content
1294  * @param priority priority of the content
1295  * @param anonymity anonymity-level for the content
1296  * @param expiration expiration time for the content
1297  * @param msg set to error message
1298  * @return GNUNET_OK on success
1299  */
1300 static int
1301 mysql_plugin_put (void *cls,
1302                      const GNUNET_HashCode * key,
1303                      uint32_t size,
1304                      const void *data,
1305                      enum GNUNET_BLOCK_Type type,
1306                      uint32_t priority,
1307                      uint32_t anonymity,
1308                      struct GNUNET_TIME_Absolute expiration,
1309                      char **msg)
1310 {
1311   struct Plugin *plugin = cls;
1312   unsigned long hashSize;
1313   unsigned long hashSize2;
1314   unsigned long long vkey;
1315   GNUNET_HashCode vhash;
1316
1317   if (size > MAX_DATUM_SIZE)
1318     {
1319       GNUNET_break (0);
1320       return GNUNET_SYSERR;
1321     }
1322   hashSize = sizeof (GNUNET_HashCode);
1323   hashSize2 = sizeof (GNUNET_HashCode);
1324   GNUNET_CRYPTO_hash (data, size, &vhash);
1325   if (GNUNET_OK != do_insert_value (plugin,
1326                                     data, size, &vkey))
1327     return GNUNET_SYSERR;
1328   if (GNUNET_OK !=
1329       prepared_statement_run (plugin,
1330                               plugin->insert_entry,
1331                               NULL,
1332                               MYSQL_TYPE_LONG,
1333                               &size,
1334                               GNUNET_YES,
1335                               MYSQL_TYPE_LONG,
1336                               &type,
1337                               GNUNET_YES,
1338                               MYSQL_TYPE_LONG,
1339                               &priority,
1340                               GNUNET_YES,
1341                               MYSQL_TYPE_LONG,
1342                               &anonymity,
1343                               GNUNET_YES,
1344                               MYSQL_TYPE_LONGLONG,
1345                               &expiration.value,
1346                               GNUNET_YES,
1347                               MYSQL_TYPE_BLOB,
1348                               key,
1349                               hashSize,
1350                               &hashSize,
1351                               MYSQL_TYPE_BLOB,
1352                               &vhash,
1353                               hashSize2,
1354                               &hashSize2,
1355                               MYSQL_TYPE_LONGLONG,
1356                               &vkey, GNUNET_YES, -1))
1357     {
1358       do_delete_value (plugin, vkey);
1359       return GNUNET_SYSERR;
1360     }
1361   plugin->content_size += size;
1362   return GNUNET_OK;
1363 }
1364
1365
1366 /**
1367  * Select a subset of the items in the datastore and call
1368  * the given iterator for each of them.
1369  *
1370  * @param cls our "struct Plugin*"
1371  * @param type entries of which type should be considered?
1372  *        Use 0 for any type.
1373  * @param iter function to call on each matching value;
1374  *        will be called once with a NULL value at the end
1375  * @param iter_cls closure for iter
1376  */
1377 static void
1378 mysql_plugin_iter_low_priority (void *cls,
1379                                 enum GNUNET_BLOCK_Type type,
1380                                 PluginIterator iter,
1381                                 void *iter_cls)
1382 {
1383   struct Plugin *plugin = cls;
1384   iterateHelper (plugin, type, GNUNET_YES, 
1385                  0, iter, iter_cls); 
1386 }
1387
1388
1389 struct GetContext
1390 {
1391   GNUNET_HashCode key;
1392   GNUNET_HashCode vhash;
1393
1394   unsigned int prio;
1395   unsigned int anonymity;
1396   unsigned int limit_off;
1397   unsigned long long expiration;
1398   unsigned long long vkey;
1399   unsigned long long total;
1400   int off;
1401   int count;
1402   int have_vhash;
1403   unsigned long size; /* OBSOLETE! */
1404   unsigned long hashSize;
1405 };
1406
1407
1408 static int
1409 get_statement_prepare (void *cls,
1410                        struct NextRequestClosure *nrc)
1411 {
1412   struct GetContext *gc = cls;
1413   struct Plugin *plugin;
1414   MYSQL_BIND *rbind;
1415   int ret;
1416
1417   if (NULL == nrc)
1418     {
1419       GNUNET_free (gc);
1420       return GNUNET_NO;
1421     }
1422   if (gc->count == gc->total)
1423     return GNUNET_NO;
1424   plugin = nrc->plugin;
1425   memset (nrc->rbind, 0, sizeof (nrc->rbind));
1426   gc->hashSize = sizeof (GNUNET_HashCode);
1427   rbind = nrc->rbind;
1428   rbind[0].buffer_type = MYSQL_TYPE_LONG;
1429   rbind[0].buffer = &gc->size;
1430   rbind[0].is_unsigned = GNUNET_YES;
1431   rbind[1].buffer_type = MYSQL_TYPE_LONG;
1432   rbind[1].buffer = &nrc->type;
1433   rbind[1].is_unsigned = GNUNET_YES;
1434   rbind[2].buffer_type = MYSQL_TYPE_LONG;
1435   rbind[2].buffer = &nrc->last_prio;
1436   rbind[2].is_unsigned = GNUNET_YES;
1437   rbind[3].buffer_type = MYSQL_TYPE_LONG;
1438   rbind[3].buffer = &gc->anonymity;
1439   rbind[3].is_unsigned = GNUNET_YES;
1440   rbind[4].buffer_type = MYSQL_TYPE_LONGLONG;
1441   rbind[4].buffer = &gc->expiration;
1442   rbind[4].is_unsigned = GNUNET_YES;
1443   rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1444   rbind[5].buffer = &gc->key;
1445   rbind[5].buffer_length = gc->hashSize;
1446   rbind[5].length = &gc->hashSize;
1447   rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1448   rbind[6].buffer = &gc->vkey;
1449   rbind[6].is_unsigned = GNUNET_YES;
1450   if (gc->count == 0)
1451     gc->limit_off = gc->off;
1452   else
1453     gc->limit_off = 0;
1454   
1455   if (nrc->type != 0)
1456     {
1457       if (gc->have_vhash)
1458         {
1459           ret =
1460             prepared_statement_run_select
1461             (plugin,
1462              plugin->select_entry_by_hash_vhash_and_type, 7, nrc->rbind, &return_ok,
1463              NULL, MYSQL_TYPE_BLOB, &gc->key, gc->hashSize, &gc->hashSize,
1464              MYSQL_TYPE_BLOB, &gc->vhash, gc->hashSize, &gc->hashSize,
1465              MYSQL_TYPE_LONGLONG, &nrc->last_vkey, GNUNET_YES, MYSQL_TYPE_LONG,
1466              &nrc->type, GNUNET_YES, MYSQL_TYPE_LONG, &gc->limit_off, GNUNET_YES,
1467              -1);
1468         }
1469       else
1470         {
1471           ret =
1472             prepared_statement_run_select
1473             (plugin,
1474              plugin->select_entry_by_hash_and_type, 7, nrc->rbind, &return_ok, NULL,
1475              MYSQL_TYPE_BLOB, &gc->key, gc->hashSize, &gc->hashSize,
1476              MYSQL_TYPE_LONGLONG, &nrc->last_vkey, GNUNET_YES, MYSQL_TYPE_LONG,
1477              &nrc->type, GNUNET_YES, MYSQL_TYPE_LONG, &gc->limit_off, GNUNET_YES,
1478              -1);
1479         }
1480     }
1481   else
1482     {
1483       if (gc->have_vhash)
1484         {
1485           ret =
1486             prepared_statement_run_select
1487             (plugin,
1488              plugin->select_entry_by_hash_and_vhash, 7, nrc->rbind, &return_ok, NULL,
1489              MYSQL_TYPE_BLOB, &gc->key, gc->hashSize, &gc->hashSize, MYSQL_TYPE_BLOB,
1490              &gc->vhash, gc->hashSize, &gc->hashSize, MYSQL_TYPE_LONGLONG,
1491              &nrc->last_vkey, GNUNET_YES, MYSQL_TYPE_LONG, &gc->limit_off,
1492              GNUNET_YES, -1);
1493         }
1494       else
1495         {
1496           ret =
1497             prepared_statement_run_select
1498             (plugin,
1499              plugin->select_entry_by_hash, 7, nrc->rbind, &return_ok, NULL,
1500              MYSQL_TYPE_BLOB, &gc->key, gc->hashSize, &gc->hashSize,
1501              MYSQL_TYPE_LONGLONG, &nrc->last_vkey, GNUNET_YES, MYSQL_TYPE_LONG,
1502              &gc->limit_off, GNUNET_YES, -1);
1503         }
1504     }
1505   if (gc->count + gc->off == gc->total)
1506     nrc->last_vkey = 0;          /* back to start */
1507   return ret;
1508 }
1509
1510
1511 /**
1512  * Iterate over the results for a particular key
1513  * in the datastore.
1514  *
1515  * @param cls closure
1516  * @param key maybe NULL (to match all entries)
1517  * @param vhash hash of the value, maybe NULL (to
1518  *        match all values that have the right key).
1519  *        Note that for DBlocks there is no difference
1520  *        betwen key and vhash, but for other blocks
1521  *        there may be!
1522  * @param type entries of which type are relevant?
1523  *     Use 0 for any type.
1524  * @param iter function to call on each matching value;
1525  *        will be called once with a NULL value at the end
1526  * @param iter_cls closure for iter
1527  */
1528 static void
1529 mysql_plugin_get (void *cls,
1530                   const GNUNET_HashCode * key,
1531                   const GNUNET_HashCode * vhash,
1532                   enum GNUNET_BLOCK_Type type,
1533                   PluginIterator iter, void *iter_cls)
1534 {
1535   struct Plugin *plugin = cls;
1536   int ret;
1537   MYSQL_BIND cbind[1];
1538   struct GetContext *gc;
1539   struct NextRequestClosure *nrc;
1540   unsigned long long total;
1541   unsigned long hashSize;
1542
1543   if (iter == NULL) 
1544     return;
1545   if (key == NULL)
1546     {
1547       mysql_plugin_iter_low_priority (plugin,
1548                                       type, 
1549                                       iter, iter_cls);
1550       return;
1551     }
1552   hashSize = sizeof (GNUNET_HashCode);
1553   memset (cbind, 0, sizeof (cbind));
1554   total = -1;
1555   cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
1556   cbind[0].buffer = &total;
1557   cbind[0].is_unsigned = GNUNET_YES;
1558   if (type != 0)
1559     {
1560       if (vhash != NULL)
1561         {
1562           ret =
1563             prepared_statement_run_select
1564             (plugin,
1565              plugin->count_entry_by_hash_vhash_and_type, 1, cbind, &return_ok, NULL,
1566              MYSQL_TYPE_BLOB, key, hashSize, &hashSize, MYSQL_TYPE_BLOB,
1567              vhash, hashSize, &hashSize, MYSQL_TYPE_LONG, &type, GNUNET_YES,
1568              -1);
1569         }
1570       else
1571         {
1572           ret =
1573             prepared_statement_run_select
1574             (plugin,
1575              plugin->count_entry_by_hash_and_type, 1, cbind, &return_ok, NULL,
1576              MYSQL_TYPE_BLOB, key, hashSize, &hashSize, MYSQL_TYPE_LONG,
1577              &type, GNUNET_YES, -1);
1578
1579         }
1580     }
1581   else
1582     {
1583       if (vhash != NULL)
1584         {
1585           ret =
1586             prepared_statement_run_select
1587             (plugin,
1588              plugin->count_entry_by_hash_and_vhash, 1, cbind, &return_ok, NULL,
1589              MYSQL_TYPE_BLOB, key, hashSize, &hashSize, MYSQL_TYPE_BLOB,
1590              vhash, hashSize, &hashSize, -1);
1591
1592         }
1593       else
1594         {
1595           ret =
1596             prepared_statement_run_select (plugin,
1597                                            plugin->count_entry_by_hash,
1598                                            1, cbind, &return_ok,
1599                                            NULL, MYSQL_TYPE_BLOB,
1600                                            key, hashSize,
1601                                            &hashSize, -1);
1602         }
1603     }
1604   if ((ret != GNUNET_OK) || (-1 == total) || (0 == total))
1605     {
1606       iter (iter_cls, 
1607             NULL, NULL, 0, NULL, 0, 0, 0, 
1608             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1609       return;
1610     }
1611   gc = GNUNET_malloc (sizeof (struct GetContext));
1612   gc->key = *key;
1613   if (vhash != NULL)
1614     {
1615       gc->have_vhash = GNUNET_YES;
1616       gc->vhash = *vhash;
1617     }
1618   gc->total = total;
1619   gc->off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
1620   
1621
1622   nrc = GNUNET_malloc (sizeof (struct NextRequestClosure));
1623   nrc->plugin = plugin;
1624   nrc->type = type;  
1625   nrc->iter_select = -1;
1626   nrc->dviter = iter;
1627   nrc->dviter_cls = iter_cls;
1628   nrc->prep = &get_statement_prepare;
1629   nrc->prep_cls = gc;
1630   nrc->last_vkey = 0; // FIXME: used to be 'vkey', why? where did vkey come from?
1631   mysql_plugin_next_request (nrc, GNUNET_NO);
1632 }
1633
1634
1635 /**
1636  * Update the priority for a particular key in the datastore.  If
1637  * the expiration time in value is different than the time found in
1638  * the datastore, the higher value should be kept.  For the
1639  * anonymity level, the lower value is to be used.  The specified
1640  * priority should be added to the existing priority, ignoring the
1641  * priority in value.
1642  *
1643  * Note that it is possible for multiple values to match this put.
1644  * In that case, all of the respective values are updated.
1645  *
1646  * @param cls our "struct Plugin*"
1647  * @param uid unique identifier of the datum
1648  * @param delta by how much should the priority
1649  *     change?  If priority + delta < 0 the
1650  *     priority should be set to 0 (never go
1651  *     negative).
1652  * @param expire new expiration time should be the
1653  *     MAX of any existing expiration time and
1654  *     this value
1655  * @param msg set to error message
1656  * @return GNUNET_OK on success
1657  */
1658 static int
1659 mysql_plugin_update (void *cls,
1660                      uint64_t uid,
1661                      int delta, 
1662                      struct GNUNET_TIME_Absolute expire,
1663                      char **msg)
1664 {
1665   struct Plugin *plugin = cls;
1666   unsigned long long vkey = uid;
1667
1668   return prepared_statement_run (plugin,
1669                                  plugin->update_entry,
1670                                  NULL,
1671                                  MYSQL_TYPE_LONG,
1672                                  &delta,
1673                                  GNUNET_NO,
1674                                  MYSQL_TYPE_LONGLONG,
1675                                  &expire.value,
1676                                  GNUNET_YES,
1677                                  MYSQL_TYPE_LONGLONG,
1678                                  &expire.value,
1679                                  GNUNET_YES,
1680                                  MYSQL_TYPE_LONGLONG,
1681                                  &vkey,
1682                                  GNUNET_YES, -1);
1683 }
1684
1685
1686 /**
1687  * Select a subset of the items in the datastore and call
1688  * the given iterator for each of them.
1689  *
1690  * @param cls our "struct Plugin*"
1691  * @param type entries of which type should be considered?
1692  *        Use 0 for any type.
1693  * @param iter function to call on each matching value;
1694  *        will be called once with a NULL value at the end
1695  * @param iter_cls closure for iter
1696  */
1697 static void
1698 mysql_plugin_iter_zero_anonymity (void *cls,
1699                                      enum GNUNET_BLOCK_Type type,
1700                                      PluginIterator iter,
1701                                      void *iter_cls)
1702 {
1703   struct Plugin *plugin = cls;
1704   iterateHelper (plugin, type, GNUNET_NO, 1, iter, iter_cls);
1705 }
1706
1707
1708 /**
1709  * Select a subset of the items in the datastore and call
1710  * the given iterator for each of them.
1711  *
1712  * @param cls our "struct Plugin*"
1713  * @param type entries of which type should be considered?
1714  *        Use 0 for any type.
1715  * @param iter function to call on each matching value;
1716  *        will be called once with a NULL value at the end
1717  * @param iter_cls closure for iter
1718  */
1719 static void
1720 mysql_plugin_iter_ascending_expiration (void *cls,
1721                                            enum GNUNET_BLOCK_Type type,
1722                                            PluginIterator iter,
1723                                         void *iter_cls)
1724 {
1725   struct Plugin *plugin = cls;
1726   iterateHelper (plugin, type, GNUNET_YES, 2, iter, iter_cls);
1727 }
1728
1729
1730 /**
1731  * Select a subset of the items in the datastore and call
1732  * the given iterator for each of them.
1733  *
1734  * @param cls our "struct Plugin*"
1735  * @param type entries of which type should be considered?
1736  *        Use 0 for any type.
1737  * @param iter function to call on each matching value;
1738  *        will be called once with a NULL value at the end
1739  * @param iter_cls closure for iter
1740  */
1741 static void
1742 mysql_plugin_iter_migration_order (void *cls,
1743                                       enum GNUNET_BLOCK_Type type,
1744                                       PluginIterator iter,
1745                                       void *iter_cls)
1746 {
1747   struct Plugin *plugin = cls;
1748   iterateHelper (plugin, 0, GNUNET_NO, 3, iter, iter_cls);
1749 }
1750
1751
1752 /**
1753  * Select a subset of the items in the datastore and call
1754  * the given iterator for each of them.
1755  *
1756  * @param cls our "struct Plugin*"
1757  * @param type entries of which type should be considered?
1758  *        Use 0 for any type.
1759  * @param iter function to call on each matching value;
1760  *        will be called once with a NULL value at the end
1761  * @param iter_cls closure for iter
1762  */
1763 static void
1764 mysql_plugin_iter_all_now (void *cls,
1765                            enum GNUNET_BLOCK_Type type,
1766                            PluginIterator iter,
1767                            void *iter_cls)
1768 {
1769   struct Plugin *plugin = cls;
1770   iterateHelper (plugin, 0, GNUNET_YES, 0, iter, iter_cls);
1771 }
1772
1773
1774 /**
1775  * Drop database.
1776  */
1777 static void 
1778 mysql_plugin_drop (void *cls)
1779 {
1780   struct Plugin *plugin = cls;
1781
1782   if ((GNUNET_OK != run_statement (plugin,
1783                                    "DROP TABLE gn080")) ||
1784       (GNUNET_OK != run_statement (plugin,
1785                                    "DROP TABLE gn072")))
1786     return;                     /* error */
1787   plugin->content_size = 0;
1788 }
1789
1790
1791 /**
1792  * Entry point for the plugin.
1793  *
1794  * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
1795  * @return our "struct Plugin*"
1796  */
1797 void *
1798 libgnunet_plugin_datastore_mysql_init (void *cls)
1799 {
1800   struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
1801   struct GNUNET_DATASTORE_PluginFunctions *api;
1802   struct Plugin *plugin;
1803
1804   plugin = GNUNET_malloc (sizeof (struct Plugin));
1805   plugin->env = env;
1806   plugin->cnffile = get_my_cnf_path (env->cfg);
1807   if (GNUNET_OK != iopen (plugin))
1808     {
1809       iclose (plugin);
1810       GNUNET_free_non_null (plugin->cnffile);
1811       GNUNET_free (plugin);
1812       return NULL;
1813     }
1814 #define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
1815 #define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
1816   if (MRUNS ("CREATE TABLE IF NOT EXISTS gn080 ("
1817              " size INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1818              " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1819              " prio INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1820              " anonLevel INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1821              " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1822              " hash BINARY(64) NOT NULL DEFAULT '',"
1823              " vhash BINARY(64) NOT NULL DEFAULT '',"
1824              " vkey BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1825              " INDEX hash (hash(64)),"
1826              " INDEX hash_vhash_vkey (hash(64),vhash(64),vkey),"
1827              " INDEX hash_vkey (hash(64),vkey),"
1828              " INDEX vkey (vkey),"
1829              " INDEX prio (prio,vkey),"
1830              " INDEX expire (expire,vkey,type),"
1831              " INDEX anonLevel (anonLevel,prio,vkey,type)"
1832              ") ENGINE=InnoDB") ||
1833       MRUNS ("CREATE TABLE IF NOT EXISTS gn072 ("
1834              " vkey BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,"
1835              " value BLOB NOT NULL DEFAULT '') ENGINE=MyISAM") ||
1836       MRUNS ("SET AUTOCOMMIT = 1") ||
1837       PINIT (plugin->select_value, SELECT_VALUE) ||
1838       PINIT (plugin->delete_value, DELETE_VALUE) ||
1839       PINIT (plugin->insert_value, INSERT_VALUE) ||
1840       PINIT (plugin->insert_entry, INSERT_ENTRY) ||
1841       PINIT (plugin->delete_entry_by_vkey, DELETE_ENTRY_BY_VKEY) ||
1842       PINIT (plugin->select_entry_by_hash, SELECT_ENTRY_BY_HASH) ||
1843       PINIT (plugin->select_entry_by_hash_and_vhash, SELECT_ENTRY_BY_HASH_AND_VHASH)
1844       || PINIT (plugin->select_entry_by_hash_and_type, SELECT_ENTRY_BY_HASH_AND_TYPE)
1845       || PINIT (plugin->select_entry_by_hash_vhash_and_type,
1846                 SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1847       || PINIT (plugin->count_entry_by_hash, COUNT_ENTRY_BY_HASH)
1848       || PINIT (plugin->count_entry_by_hash_and_vhash, COUNT_ENTRY_BY_HASH_AND_VHASH)
1849       || PINIT (plugin->count_entry_by_hash_and_type, COUNT_ENTRY_BY_HASH_AND_TYPE)
1850       || PINIT (plugin->count_entry_by_hash_vhash_and_type,
1851                 COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1852       || PINIT (plugin->update_entry, UPDATE_ENTRY)
1853       || PINIT (plugin->iter[0], SELECT_IT_LOW_PRIORITY)
1854       || PINIT (plugin->iter[1], SELECT_IT_NON_ANONYMOUS)
1855       || PINIT (plugin->iter[2], SELECT_IT_EXPIRATION_TIME)
1856       || PINIT (plugin->iter[3], SELECT_IT_MIGRATION_ORDER))
1857     {
1858       iclose (plugin);
1859       GNUNET_free_non_null (plugin->cnffile);
1860       GNUNET_free (plugin);
1861       return NULL;
1862     }
1863 #undef PINIT
1864 #undef MRUNS
1865
1866   api = GNUNET_malloc (sizeof (struct GNUNET_DATASTORE_PluginFunctions));
1867   api->cls = plugin;
1868   api->get_size = &mysql_plugin_get_size;
1869   api->put = &mysql_plugin_put;
1870   api->next_request = &mysql_plugin_next_request;
1871   api->get = &mysql_plugin_get;
1872   api->update = &mysql_plugin_update;
1873   api->iter_low_priority = &mysql_plugin_iter_low_priority;
1874   api->iter_zero_anonymity = &mysql_plugin_iter_zero_anonymity;
1875   api->iter_ascending_expiration = &mysql_plugin_iter_ascending_expiration;
1876   api->iter_migration_order = &mysql_plugin_iter_migration_order;
1877   api->iter_all_now = &mysql_plugin_iter_all_now;
1878   api->drop = &mysql_plugin_drop;
1879   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1880                    "mysql", _("Mysql database running\n"));
1881   return api;
1882 }
1883
1884
1885 /**
1886  * Exit point from the plugin.
1887  * @param cls our "struct Plugin*"
1888  * @return always NULL
1889  */
1890 void *
1891 libgnunet_plugin_datastore_mysql_done (void *cls)
1892 {
1893   struct GNUNET_DATASTORE_PluginFunctions *api = cls;
1894   struct Plugin *plugin = api->cls;
1895
1896   iclose (plugin);
1897   if (plugin->next_task != GNUNET_SCHEDULER_NO_TASK)
1898     {
1899       GNUNET_SCHEDULER_cancel (plugin->env->sched,
1900                                plugin->next_task);
1901       plugin->next_task = GNUNET_SCHEDULER_NO_TASK;
1902       plugin->next_task_nc->prep (plugin->next_task_nc->prep_cls, NULL);
1903       GNUNET_free (plugin->next_task_nc);
1904       plugin->next_task_nc = NULL;
1905     }
1906   GNUNET_free_non_null (plugin->cnffile);
1907   GNUNET_free (plugin);
1908   GNUNET_free (api);
1909   mysql_library_end ();
1910   return NULL;
1911 }
1912
1913 /* end of plugin_datastore_mysql.c */