adding configure code for --enable-benchmarks, --enable-expensive-tests, some clean up
[oweals/gnunet.git] / src / datastore / plugin_datastore_mysql.c
1 /*
2      This file is part of GNUnet
3      (C) 2009, 2010, 2011 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  * @verbatim
52        [datastore]
53        DATABASE = "mysql"
54    @endverbatim
55  * 2) Then access mysql as root,
56  * @verbatim
57      $ mysql -u root -p
58    @endverbatim
59  *    and do the following. [You should replace $USER with the username
60  *    that will be running the gnunetd process].
61  * @verbatim
62       CREATE DATABASE gnunet;
63       GRANT select,insert,update,delete,create,alter,drop,create temporary tables
64          ON gnunet.* TO $USER@localhost;
65       SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
66       FLUSH PRIVILEGES;
67    @endverbatim
68  * 3) In the $HOME directory of $USER, create a ".my.cnf" file
69  *    with the following lines
70  * @verbatim
71       [client]
72       user=$USER
73       password=$the_password_you_like
74    @endverbatim
75  *
76  * Thats it. Note that .my.cnf file is a security risk unless its on
77  * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
78  * link. Even greater security risk can be achieved by setting no
79  * password for $USER.  Luckily $USER has only priviledges to mess
80  * up GNUnet's tables, nothing else (unless you give him more,
81  * of course).<p>
82  *
83  * 4) Still, perhaps you should briefly try if the DB connection
84  *    works. First, login as $USER. Then use,
85  *
86  * @verbatim
87      $ mysql -u $USER -p $the_password_you_like
88      mysql> use gnunet;
89    @endverbatim
90  *
91  *    If you get the message &quot;Database changed&quot; it probably works.
92  *
93  *    [If you get &quot;ERROR 2002: Can't connect to local MySQL server
94  *     through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
95  *     &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
96  *     so there may be some additional trouble depending on your mysql setup.]
97  *
98  * REPAIRING TABLES
99  *
100  * - Its probably healthy to check your tables for inconsistencies
101  *   every now and then.
102  * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
103  *   databases have been corrupted.
104  * - The tables can be verified/fixed in two ways;
105  *   1) by running mysqlcheck -A, or
106  *   2) by executing (inside of mysql using the GNUnet database):
107  * @verbatim
108      mysql> REPAIR TABLE gn090;
109    @endverbatim
110  *
111  * PROBLEMS?
112  *
113  * If you have problems related to the mysql module, your best
114  * friend is probably the mysql manual. The first thing to check
115  * is that mysql is basically operational, that you can connect
116  * to it, create tables, issue queries etc.
117  */
118
119 #include "platform.h"
120 #include "gnunet_datastore_plugin.h"
121 #include "gnunet_util_lib.h"
122 #include <mysql/mysql.h>
123
124 #define DEBUG_MYSQL GNUNET_NO
125
126 #define MAX_DATUM_SIZE 65536
127
128 /**
129  * Maximum number of supported parameters for a prepared
130  * statement.  Increase if needed.
131  */
132 #define MAX_PARAM 16
133
134 /**
135  * Die with an error message that indicates
136  * a failure of the command 'cmd' with the message given
137  * by strerror(errno).
138  */
139 #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);
140
141 /**
142  * Log an error message at log-level 'level' that indicates
143  * a failure of the command 'cmd' on file 'filename'
144  * with the message given by strerror(errno).
145  */
146 #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);
147
148
149 struct GNUNET_MysqlStatementHandle
150 {
151   struct GNUNET_MysqlStatementHandle *next;
152
153   struct GNUNET_MysqlStatementHandle *prev;
154
155   char *query;
156
157   MYSQL_STMT *statement;
158
159   int valid;
160
161 };
162
163
164 /**
165  * Context for all functions in this plugin.
166  */
167 struct Plugin 
168 {
169   /**
170    * Our execution environment.
171    */
172   struct GNUNET_DATASTORE_PluginEnvironment *env;
173
174   /**
175    * Handle to talk to MySQL.
176    */
177   MYSQL *dbf;
178   
179   /**
180    * We keep all prepared statements in a DLL.  This is the head.
181    */
182   struct GNUNET_MysqlStatementHandle *shead;
183
184   /**
185    * We keep all prepared statements in a DLL.  This is the tail.
186    */
187   struct GNUNET_MysqlStatementHandle *stail;
188
189   /**
190    * Filename of "my.cnf" (msyql configuration).
191    */
192   char *cnffile;
193
194   /**
195    * Prepared statements.
196    */
197 #define INSERT_ENTRY "INSERT INTO gn090 (repl,type,prio,anonLevel,expire,hash,vhash,value) VALUES (?,?,?,?,?,?,?,?)"
198   struct GNUNET_MysqlStatementHandle *insert_entry;
199   
200 #define DELETE_ENTRY_BY_UID "DELETE FROM gn090 WHERE uid=?"
201   struct GNUNET_MysqlStatementHandle *delete_entry_by_uid;
202
203 #define COUNT_ENTRY_BY_HASH "SELECT count(*) FROM gn090 WHERE hash=?"
204   struct GNUNET_MysqlStatementHandle *count_entry_by_hash;
205   
206 #define SELECT_ENTRY_BY_HASH "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? ORDER BY uid LIMIT 1 OFFSET ?"
207   struct GNUNET_MysqlStatementHandle *select_entry_by_hash;
208
209 #define COUNT_ENTRY_BY_HASH_AND_VHASH "SELECT count(*) FROM gn090 WHERE hash=? AND vhash=?"
210   struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_vhash;
211
212 #define SELECT_ENTRY_BY_HASH_AND_VHASH "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? AND vhash=? ORDER BY uid LIMIT 1 OFFSET ?"
213   struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_vhash;
214  
215 #define COUNT_ENTRY_BY_HASH_AND_TYPE "SELECT count(*) FROM gn090 WHERE hash=? AND type=?"
216   struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_type;
217
218 #define SELECT_ENTRY_BY_HASH_AND_TYPE "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? AND type=? ORDER BY uid LIMIT 1 OFFSET ?"
219   struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_type;
220  
221 #define COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT count(*) FROM gn090 WHERE hash=? AND vhash=? AND type=?"
222   struct GNUNET_MysqlStatementHandle *count_entry_by_hash_vhash_and_type;
223   
224 #define SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? AND vhash=? AND type=? ORDER BY uid ASC LIMIT 1 OFFSET ?"
225   struct GNUNET_MysqlStatementHandle *select_entry_by_hash_vhash_and_type;
226
227 #define UPDATE_ENTRY "UPDATE gn090 SET prio=prio+?,expire=IF(expire>=?,expire,?) WHERE uid=?"
228   struct GNUNET_MysqlStatementHandle *update_entry;
229
230 #define DEC_REPL "UPDATE gn090 SET repl=GREATEST (0, repl - 1) WHERE uid=?"
231   struct GNUNET_MysqlStatementHandle *dec_repl;
232
233 #define SELECT_SIZE "SELECT SUM(BIT_LENGTH(value) DIV 8) FROM gn090"
234   struct GNUNET_MysqlStatementHandle *get_size;
235
236 #define SELECT_IT_NON_ANONYMOUS "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE anonLevel=0 AND type=? ORDER BY uid DESC LIMIT 1 OFFSET ?"
237   struct GNUNET_MysqlStatementHandle *zero_iter;
238
239 #define SELECT_IT_EXPIRATION "(SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE expire < ? ORDER BY prio ASC LIMIT 1) "\
240   "UNION "\
241   "(SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 ORDER BY prio ASC LIMIT 1) "\
242   "ORDER BY expire ASC LIMIT 1"
243   struct GNUNET_MysqlStatementHandle *select_expiration;
244
245 #define SELECT_IT_REPLICATION "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 ORDER BY repl DESC,RAND() LIMIT 1"
246   struct GNUNET_MysqlStatementHandle *select_replication;
247
248 };
249
250
251 /**
252  * Obtain the location of ".my.cnf".
253  *
254  * @param cfg our configuration
255  * @return NULL on error
256  */
257 static char *
258 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg)
259 {
260   char *cnffile;
261   char *home_dir;
262   struct stat st;
263 #ifndef WINDOWS
264   struct passwd *pw;
265 #endif
266   int configured;
267
268 #ifndef WINDOWS
269   pw = getpwuid (getuid ());
270   if (!pw)
271     {
272       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 
273                            "getpwuid");
274       return NULL;
275     }
276   if (GNUNET_YES ==
277       GNUNET_CONFIGURATION_have_value (cfg,
278                                        "datastore-mysql", "CONFIG"))
279     {
280       GNUNET_assert (GNUNET_OK == 
281                      GNUNET_CONFIGURATION_get_value_filename (cfg,
282                                                               "datastore-mysql", "CONFIG", &cnffile));
283       configured = GNUNET_YES;
284     }
285   else
286     {
287       home_dir = GNUNET_strdup (pw->pw_dir);
288 #else
289       home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
290       plibc_conv_to_win_path ("~/", home_dir);
291 #endif
292       GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
293       GNUNET_free (home_dir);
294       configured = GNUNET_NO;
295     }
296   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
297               _("Trying to use file `%s' for MySQL configuration.\n"),
298               cnffile);
299   if ((0 != STAT (cnffile, &st)) ||
300       (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
301     {
302       if (configured == GNUNET_YES)
303         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
304                     _("Could not access file `%s': %s\n"), cnffile,
305                     STRERROR (errno));
306       GNUNET_free (cnffile);
307       return NULL;
308     }
309   return cnffile;
310 }
311
312
313 /**
314  * Free a prepared statement.
315  *
316  * @param plugin plugin context
317  * @param s prepared statement
318  */
319 static void
320 prepared_statement_destroy (struct Plugin *plugin, 
321                             struct GNUNET_MysqlStatementHandle *s)
322 {
323   GNUNET_CONTAINER_DLL_remove (plugin->shead,
324                                plugin->stail,
325                                s);
326   if (s->valid)
327     mysql_stmt_close (s->statement);
328   GNUNET_free (s->query);
329   GNUNET_free (s);
330 }
331
332
333 /**
334  * Close database connection and all prepared statements (we got a DB
335  * disconnect error).
336  * 
337  * @param plugin plugin context
338  */
339 static int
340 iclose (struct Plugin *plugin)
341 {
342   while (NULL != plugin->shead)
343     prepared_statement_destroy (plugin,
344                                 plugin->shead);
345   if (plugin->dbf != NULL)
346     {
347       mysql_close (plugin->dbf);
348       plugin->dbf = NULL;
349     }
350   return GNUNET_OK;
351 }
352
353
354 /**
355  * Open the connection with the database (and initialize
356  * our default options).
357  *
358  * @param plugin plugin context
359  * @return GNUNET_OK on success
360  */
361 static int
362 iopen (struct Plugin *plugin)
363 {
364   char *mysql_dbname;
365   char *mysql_server;
366   char *mysql_user;
367   char *mysql_password;
368   unsigned long long mysql_port;
369   my_bool reconnect;
370   unsigned int timeout;
371
372   plugin->dbf = mysql_init (NULL);
373   if (plugin->dbf == NULL)
374     return GNUNET_SYSERR;
375   if (plugin->cnffile != NULL)
376     mysql_options (plugin->dbf, MYSQL_READ_DEFAULT_FILE, plugin->cnffile);
377   mysql_options (plugin->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
378   reconnect = 0;
379   mysql_options (plugin->dbf, MYSQL_OPT_RECONNECT, &reconnect);
380   mysql_options (plugin->dbf,
381                  MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
382   mysql_options(plugin->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
383   timeout = 60; /* in seconds */
384   mysql_options (plugin->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
385   mysql_options (plugin->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
386   mysql_dbname = NULL;
387   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
388                                                      "datastore-mysql", "DATABASE"))
389     GNUNET_assert (GNUNET_OK == 
390                    GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
391                                                           "datastore-mysql", "DATABASE", 
392                                                           &mysql_dbname));
393   else
394     mysql_dbname = GNUNET_strdup ("gnunet");
395   mysql_user = NULL;
396   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
397                                                      "datastore-mysql", "USER"))
398     {
399       GNUNET_assert (GNUNET_OK == 
400                     GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
401                                                            "datastore-mysql", "USER", 
402                                                            &mysql_user));
403     }
404   mysql_password = NULL;
405   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
406                                                      "datastore-mysql", "PASSWORD"))
407     {
408       GNUNET_assert (GNUNET_OK ==
409                     GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
410                                                            "datastore-mysql", "PASSWORD",
411                                                            &mysql_password));
412     }
413   mysql_server = NULL;
414   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
415                                                      "datastore-mysql", "HOST"))
416     {
417       GNUNET_assert (GNUNET_OK == 
418                     GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
419                                                            "datastore-mysql", "HOST", 
420                                                            &mysql_server));
421     }
422   mysql_port = 0;
423   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
424                                                      "datastore-mysql", "PORT"))
425     {
426       GNUNET_assert (GNUNET_OK ==
427                     GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg, "datastore-mysql",
428                                                            "PORT", &mysql_port));
429     }
430
431   GNUNET_assert (mysql_dbname != NULL);
432   mysql_real_connect (plugin->dbf, 
433                       mysql_server, 
434                       mysql_user, mysql_password,
435                       mysql_dbname, 
436                       (unsigned int) mysql_port, NULL,
437                       CLIENT_IGNORE_SIGPIPE);
438   GNUNET_free_non_null (mysql_server);
439   GNUNET_free_non_null (mysql_user);
440   GNUNET_free_non_null (mysql_password);
441   GNUNET_free (mysql_dbname);
442   if (mysql_error (plugin->dbf)[0])
443     {
444       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
445                  "mysql_real_connect", plugin);
446       return GNUNET_SYSERR;
447     }
448   return GNUNET_OK;
449 }
450
451
452 /**
453  * Run the given MySQL statement.
454  *
455  * @param plugin plugin context
456  * @param statement SQL statement to run
457  * @return GNUNET_OK on success, GNUNET_SYSERR on error
458  */
459 static int
460 run_statement (struct Plugin *plugin,
461                const char *statement)
462 {
463   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
464     return GNUNET_SYSERR;
465   mysql_query (plugin->dbf, statement);
466   if (mysql_error (plugin->dbf)[0])
467     {
468       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
469                  "mysql_query", plugin);
470       iclose (plugin);
471       return GNUNET_SYSERR;
472     }
473   return GNUNET_OK;
474 }
475
476
477 /**
478  * Create a prepared statement.
479  *
480  * @param plugin plugin context
481  * @param statement SQL statement text to prepare
482  * @return NULL on error
483  */
484 static struct GNUNET_MysqlStatementHandle *
485 prepared_statement_create (struct Plugin *plugin, 
486                            const char *statement)
487 {
488   struct GNUNET_MysqlStatementHandle *ret;
489
490   ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
491   ret->query = GNUNET_strdup (statement);
492   GNUNET_CONTAINER_DLL_insert (plugin->shead,
493                                plugin->stail,
494                                ret);
495   return ret;
496 }
497
498
499 /**
500  * Prepare a statement for running.
501  *
502  * @param plugin plugin context
503  * @param ret handle to prepared statement
504  * @return GNUNET_OK on success
505  */
506 static int
507 prepare_statement (struct Plugin *plugin, 
508                    struct GNUNET_MysqlStatementHandle *ret)
509 {
510   if (GNUNET_YES == ret->valid)
511     return GNUNET_OK;
512   if ((NULL == plugin->dbf) && 
513       (GNUNET_OK != iopen (plugin)))
514     return GNUNET_SYSERR;
515   ret->statement = mysql_stmt_init (plugin->dbf);
516   if (ret->statement == NULL)
517     {
518       iclose (plugin);
519       return GNUNET_SYSERR;
520     }
521   if (mysql_stmt_prepare (ret->statement, 
522                           ret->query,
523                           strlen (ret->query)))
524     {
525       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
526                        "mysql",
527                        _("Failed to prepare statement `%s'\n"),
528                        ret->query);
529       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
530                  "mysql_stmt_prepare", 
531                  plugin);
532       mysql_stmt_close (ret->statement);
533       ret->statement = NULL;
534       iclose (plugin);
535       return GNUNET_SYSERR;
536     }
537   ret->valid = GNUNET_YES;
538   return GNUNET_OK;
539
540 }
541
542
543 /**
544  * Bind the parameters for the given MySQL statement
545  * and run it.
546  *
547  * @param plugin plugin context
548  * @param s statement to bind and run
549  * @param ap arguments for the binding
550  * @return GNUNET_SYSERR on error, GNUNET_OK on success
551  */
552 static int
553 init_params (struct Plugin *plugin,
554              struct GNUNET_MysqlStatementHandle *s,
555              va_list ap)
556 {
557   MYSQL_BIND qbind[MAX_PARAM];
558   unsigned int pc;
559   unsigned int off;
560   enum enum_field_types ft;
561
562   pc = mysql_stmt_param_count (s->statement);
563   if (pc > MAX_PARAM)
564     {
565       /* increase internal constant! */
566       GNUNET_break (0);
567       return GNUNET_SYSERR;
568     }
569   memset (qbind, 0, sizeof (qbind));
570   off = 0;
571   ft = 0;
572   while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
573     {
574       qbind[off].buffer_type = ft;
575       switch (ft)
576         {
577         case MYSQL_TYPE_FLOAT:
578           qbind[off].buffer = va_arg (ap, float *);
579           break;
580         case MYSQL_TYPE_LONGLONG:
581           qbind[off].buffer = va_arg (ap, unsigned long long *);
582           qbind[off].is_unsigned = va_arg (ap, int);
583           break;
584         case MYSQL_TYPE_LONG:
585           qbind[off].buffer = va_arg (ap, unsigned int *);
586           qbind[off].is_unsigned = va_arg (ap, int);
587           break;
588         case MYSQL_TYPE_VAR_STRING:
589         case MYSQL_TYPE_STRING:
590         case MYSQL_TYPE_BLOB:
591           qbind[off].buffer = va_arg (ap, void *);
592           qbind[off].buffer_length = va_arg (ap, unsigned long);
593           qbind[off].length = va_arg (ap, unsigned long *);
594           break;
595         default:
596           /* unsupported type */
597           GNUNET_break (0);
598           return GNUNET_SYSERR;
599         }
600       pc--;
601       off++;
602     }
603   if (! ( (pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1) ) )
604     {
605       GNUNET_assert (0);
606       return GNUNET_SYSERR;
607     }
608   if (mysql_stmt_bind_param (s->statement, qbind))
609     {
610       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
611                   _("`%s' failed at %s:%d with error: %s\n"),
612                   "mysql_stmt_bind_param",
613                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
614       iclose (plugin);
615       return GNUNET_SYSERR;
616     }
617   if (mysql_stmt_execute (s->statement))
618     {
619       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
620                   _("`%s' for `%s' failed at %s:%d with error: %s\n"),
621                   "mysql_stmt_execute",
622                   s->query,
623                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
624       iclose (plugin);
625       return GNUNET_SYSERR;
626     }
627   return GNUNET_OK;
628 }
629
630
631 /**
632  * Run a prepared SELECT statement.
633  *
634  * @param plugin plugin context
635  * @param s statement to run
636  * @param result_size number of elements in results array
637  * @param results pointer to already initialized MYSQL_BIND
638  *        array (of sufficient size) for passing results
639  * @param ap pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
640  *        values (size + buffer-reference for pointers); terminated
641  *        with "-1"
642  * @return GNUNET_SYSERR on error, otherwise GNUNET_OK or GNUNET_NO (no result)
643  */
644 static int
645 prepared_statement_run_select_va (struct Plugin *plugin,
646                                   struct GNUNET_MysqlStatementHandle *s,
647                                   unsigned int result_size,
648                                   MYSQL_BIND *results,
649                                   va_list ap)
650 {
651   int ret;
652   unsigned int rsize;
653
654   if (GNUNET_OK != prepare_statement (plugin, s))
655     {
656       GNUNET_break (0);
657       return GNUNET_SYSERR;
658     }
659   if (GNUNET_OK != init_params (plugin, s, ap))
660     {
661       GNUNET_break (0);
662       return GNUNET_SYSERR;
663     }
664   rsize = mysql_stmt_field_count (s->statement);
665   if (rsize > result_size)
666     {
667       GNUNET_break (0);
668       return GNUNET_SYSERR;
669     }
670   if (mysql_stmt_bind_result (s->statement, results))
671     {
672       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
673                   _("`%s' failed at %s:%d with error: %s\n"),
674                   "mysql_stmt_bind_result",
675                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
676       iclose (plugin);
677       return GNUNET_SYSERR;
678     }
679   ret = mysql_stmt_fetch (s->statement);
680   if (ret == MYSQL_NO_DATA)
681     return GNUNET_NO;
682   if (ret != 0)
683     {
684       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
685                   _("`%s' failed at %s:%d with error: %s\n"),
686                   "mysql_stmt_fetch",
687                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
688       iclose (plugin);
689       return GNUNET_SYSERR;
690     }
691   mysql_stmt_reset (s->statement);
692   return GNUNET_OK;
693 }
694
695
696 /**
697  * Run a prepared SELECT statement.
698  *
699  * @param plugin plugin context
700  * @param s statement to run
701  * @param result_size number of elements in results array
702  * @param results pointer to already initialized MYSQL_BIND
703  *        array (of sufficient size) for passing results
704  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
705  *        values (size + buffer-reference for pointers); terminated
706  *        with "-1"
707  * @return GNUNET_SYSERR on error, otherwise
708  *         the number of successfully affected (or queried) rows
709  */
710 static int
711 prepared_statement_run_select (struct Plugin *plugin,
712                                struct GNUNET_MysqlStatementHandle *s,
713                                unsigned int result_size,
714                                MYSQL_BIND *results,
715                                ...)
716 {
717   va_list ap;
718   int ret;
719
720   va_start (ap, results);
721   ret = prepared_statement_run_select_va (plugin, s, 
722                                           result_size, results,
723                                           ap);
724   va_end (ap);
725   return ret;
726 }
727
728
729 /**
730  * Run a prepared statement that does NOT produce results.
731  *
732  * @param plugin plugin context
733  * @param s statement to run
734  * @param insert_id NULL or address where to store the row ID of whatever
735  *        was inserted (only for INSERT statements!)
736  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
737  *        values (size + buffer-reference for pointers); terminated
738  *        with "-1"
739  * @return GNUNET_SYSERR on error, otherwise
740  *         the number of successfully affected rows
741  */
742 static int
743 prepared_statement_run (struct Plugin *plugin,
744                         struct GNUNET_MysqlStatementHandle *s,
745                         unsigned long long *insert_id, ...)
746 {
747   va_list ap;
748   int affected;
749
750   if (GNUNET_OK != prepare_statement (plugin, s))
751     return GNUNET_SYSERR;
752   va_start (ap, insert_id);
753   if (GNUNET_OK != init_params (plugin, s, ap))
754     {
755       va_end (ap);
756       return GNUNET_SYSERR;
757     }
758   va_end (ap);
759   affected = mysql_stmt_affected_rows (s->statement);
760   if (NULL != insert_id)
761     *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
762   mysql_stmt_reset (s->statement);
763   return affected;
764 }
765
766
767 /**
768  * Delete an entry from the gn090 table.
769  *
770  * @param plugin plugin context
771  * @param uid unique ID of the entry to delete
772  * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
773  */
774 static int
775 do_delete_entry (struct Plugin *plugin,
776                  unsigned long long uid)
777 {
778   int ret;
779  
780 #if DEBUG_MYSQL
781   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
782               "Deleting value %llu from gn090 table\n",
783               uid);
784 #endif
785   ret = prepared_statement_run (plugin,
786                                 plugin->delete_entry_by_uid,
787                                 NULL,
788                                 MYSQL_TYPE_LONGLONG, &uid, GNUNET_YES,
789                                 -1);
790   if (ret >= 0)
791     return GNUNET_OK;
792   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
793               "Deleting value %llu from gn090 table failed\n",
794               uid);
795   return ret;
796 }
797
798
799 /**
800  * Get an estimate of how much space the database is
801  * currently using.
802  *
803  * @param cls our "struct Plugin *"
804  * @return number of bytes used on disk
805  */
806 static unsigned long long
807 mysql_plugin_estimate_size (void *cls)
808 {
809   struct Plugin *plugin = cls;
810   MYSQL_BIND cbind[1];
811   long long total;
812
813   memset (cbind, 0, sizeof (cbind));
814   total = 0;
815   cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
816   cbind[0].buffer = &total;
817   cbind[0].is_unsigned = GNUNET_NO;
818   if (GNUNET_OK != 
819       prepared_statement_run_select (plugin,
820                                      plugin->get_size,
821                                      1, cbind, 
822                                      -1))
823     return 0;
824   return total;
825 }
826
827
828 /**
829  * Store an item in the datastore.
830  *
831  * @param cls closure
832  * @param key key for the item
833  * @param size number of bytes in data
834  * @param data content stored
835  * @param type type of the content
836  * @param priority priority of the content
837  * @param anonymity anonymity-level for the content
838  * @param replication replication-level for the content
839  * @param expiration expiration time for the content
840  * @param msg set to error message
841  * @return GNUNET_OK on success
842  */
843 static int
844 mysql_plugin_put (void *cls,
845                   const GNUNET_HashCode * key,
846                   uint32_t size,
847                   const void *data,
848                   enum GNUNET_BLOCK_Type type,
849                   uint32_t priority,
850                   uint32_t anonymity,
851                   uint32_t replication,
852                   struct GNUNET_TIME_Absolute expiration,
853                   char **msg)
854 {
855   struct Plugin *plugin = cls;
856   unsigned int irepl = replication;
857   unsigned int ipriority = priority;
858   unsigned int ianonymity = anonymity;
859   unsigned long long lexpiration = expiration.abs_value;
860   unsigned long hashSize;
861   unsigned long hashSize2;
862   unsigned long lsize;
863   GNUNET_HashCode vhash;
864
865   if (size > MAX_DATUM_SIZE)
866     {
867       GNUNET_break (0);
868       return GNUNET_SYSERR;
869     }
870   hashSize = sizeof (GNUNET_HashCode);
871   hashSize2 = sizeof (GNUNET_HashCode);
872   lsize = size;
873   GNUNET_CRYPTO_hash (data, size, &vhash);
874   if (GNUNET_OK !=
875       prepared_statement_run (plugin,
876                               plugin->insert_entry,
877                               NULL,
878                               MYSQL_TYPE_LONG, &irepl, GNUNET_YES,
879                               MYSQL_TYPE_LONG, &type, GNUNET_YES,
880                               MYSQL_TYPE_LONG, &ipriority, GNUNET_YES,
881                               MYSQL_TYPE_LONG, &ianonymity, GNUNET_YES,
882                               MYSQL_TYPE_LONGLONG, &lexpiration, GNUNET_YES,
883                               MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
884                               MYSQL_TYPE_BLOB, &vhash, hashSize2, &hashSize2,
885                               MYSQL_TYPE_BLOB, data, lsize, &lsize, 
886                               -1))
887     return GNUNET_SYSERR;    
888 #if DEBUG_MYSQL
889   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
890               "Inserted value `%s' with size %u into gn090 table\n",
891               GNUNET_h2s (key),
892               (unsigned int) size);
893 #endif
894   if (size > 0)
895     plugin->env->duc (plugin->env->cls,
896                       size);
897   return GNUNET_OK;
898 }
899
900
901 /**
902  * Update the priority for a particular key in the datastore.  If
903  * the expiration time in value is different than the time found in
904  * the datastore, the higher value should be kept.  For the
905  * anonymity level, the lower value is to be used.  The specified
906  * priority should be added to the existing priority, ignoring the
907  * priority in value.
908  *
909  * Note that it is possible for multiple values to match this put.
910  * In that case, all of the respective values are updated.
911  *
912  * @param cls our "struct Plugin*"
913  * @param uid unique identifier of the datum
914  * @param delta by how much should the priority
915  *     change?  If priority + delta < 0 the
916  *     priority should be set to 0 (never go
917  *     negative).
918  * @param expire new expiration time should be the
919  *     MAX of any existing expiration time and
920  *     this value
921  * @param msg set to error message
922  * @return GNUNET_OK on success
923  */
924 static int
925 mysql_plugin_update (void *cls,
926                      uint64_t uid,
927                      int delta, 
928                      struct GNUNET_TIME_Absolute expire,
929                      char **msg)
930 {
931   struct Plugin *plugin = cls;
932   unsigned long long vkey = uid;
933   unsigned long long lexpire = expire.abs_value;
934   int ret;
935
936 #if DEBUG_MYSQL
937   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
938               "Updating value %llu adding %d to priority and maxing exp at %llu\n",
939               vkey,
940               delta,
941               lexpire);
942 #endif
943   ret = prepared_statement_run (plugin,
944                                 plugin->update_entry,
945                                 NULL,
946                                 MYSQL_TYPE_LONG, &delta, GNUNET_NO,
947                                 MYSQL_TYPE_LONGLONG, &lexpire, GNUNET_YES,
948                                 MYSQL_TYPE_LONGLONG, &lexpire, GNUNET_YES,
949                                 MYSQL_TYPE_LONGLONG, &vkey, GNUNET_YES, 
950                                 -1);
951   if (ret != GNUNET_OK)
952     {
953       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
954                   "Failed to update value %llu\n",
955                   vkey);
956     }
957   return ret;
958 }
959
960
961 /**
962  * Run the given select statement and call 'proc' on the resulting
963  * values (which must be in particular positions).
964  *
965  * @param plugin the plugin handle
966  * @param stmt select statement to run
967  * @param proc function to call on result
968  * @param proc_cls closure for proc
969  * @param ... arguments to initialize stmt
970  */
971 static void 
972 execute_select (struct Plugin *plugin,
973                 struct GNUNET_MysqlStatementHandle *stmt,
974                 PluginDatumProcessor proc, void *proc_cls,
975                 ...)
976 {
977   va_list ap;
978   int ret;
979   unsigned int type;
980   unsigned int priority;
981   unsigned int anonymity;
982   unsigned long long exp;
983   unsigned long hashSize;
984   unsigned long size;
985   unsigned long long uid;
986   char value[GNUNET_DATASTORE_MAX_VALUE_SIZE];
987   GNUNET_HashCode key;
988   struct GNUNET_TIME_Absolute expiration;
989   MYSQL_BIND rbind[7];
990
991   hashSize = sizeof (GNUNET_HashCode);
992   memset (rbind, 0, sizeof (rbind));
993   rbind[0].buffer_type = MYSQL_TYPE_LONG;
994   rbind[0].buffer = &type;
995   rbind[0].is_unsigned = 1;
996   rbind[1].buffer_type = MYSQL_TYPE_LONG;
997   rbind[1].buffer = &priority;
998   rbind[1].is_unsigned = 1;
999   rbind[2].buffer_type = MYSQL_TYPE_LONG;
1000   rbind[2].buffer = &anonymity;
1001   rbind[2].is_unsigned = 1;
1002   rbind[3].buffer_type = MYSQL_TYPE_LONGLONG;
1003   rbind[3].buffer = &exp;
1004   rbind[3].is_unsigned = 1;
1005   rbind[4].buffer_type = MYSQL_TYPE_BLOB;
1006   rbind[4].buffer = &key;
1007   rbind[4].buffer_length = hashSize;
1008   rbind[4].length = &hashSize;
1009   rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1010   rbind[5].buffer = value;
1011   rbind[5].buffer_length = size = sizeof (value);
1012   rbind[5].length = &size;
1013   rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1014   rbind[6].buffer = &uid;
1015   rbind[6].is_unsigned = 1;
1016
1017   va_start (ap, proc_cls);
1018   ret = prepared_statement_run_select_va (plugin,
1019                                           stmt,
1020                                           7, rbind,
1021                                           ap);
1022   va_end (ap);
1023   if (ret <= 0)
1024     {
1025       proc (proc_cls, 
1026             NULL, 0, NULL, 0, 0, 0, 
1027             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1028       return;
1029     }
1030   GNUNET_assert (size <= sizeof(value));
1031   if ( (rbind[4].buffer_length != sizeof (GNUNET_HashCode)) ||
1032        (hashSize != sizeof (GNUNET_HashCode)) )
1033     {
1034       GNUNET_break (0);
1035       proc (proc_cls, 
1036             NULL, 0, NULL, 0, 0, 0, 
1037             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1038       return;
1039     }     
1040 #if DEBUG_MYSQL
1041   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1042               "Found %u-byte value under key `%s' with prio %u, anon %u, expire %llu selecting from gn090 table\n",
1043               (unsigned int) size,
1044               GNUNET_h2s (&key),
1045               priority,
1046               anonymity,
1047               exp);
1048 #endif
1049   GNUNET_assert (size < MAX_DATUM_SIZE);
1050   expiration.abs_value = exp;
1051   ret = proc (proc_cls, 
1052               &key,
1053               size, value,
1054               type, priority, anonymity, expiration,
1055               uid);
1056   if (ret == GNUNET_NO)
1057     {
1058       do_delete_entry (plugin, uid);
1059       if (size != 0)
1060         plugin->env->duc (plugin->env->cls,
1061                           - size);
1062     }
1063 }
1064
1065
1066
1067 /**
1068  * Get one of the results for a particular key in the datastore.
1069  *
1070  * @param cls closure
1071  * @param offset offset of the result (mod #num-results); 
1072  *               specific ordering does not matter for the offset
1073  * @param key key to match, never NULL
1074  * @param vhash hash of the value, maybe NULL (to
1075  *        match all values that have the right key).
1076  *        Note that for DBlocks there is no difference
1077  *        betwen key and vhash, but for other blocks
1078  *        there may be!
1079  * @param type entries of which type are relevant?
1080  *     Use 0 for any type.
1081  * @param proc function to call on each matching value; however,
1082  *        after the first call to "proc", the plugin must wait
1083  *        until "NextRequest" was called before giving the processor
1084  *        the next item; finally, the "proc" should be called once
1085  *        once with a NULL value at the end ("next_cls" should be NULL
1086  *        for that last call)
1087  * @param proc_cls closure for proc
1088  */
1089 static void
1090 mysql_plugin_get_key (void *cls,
1091                       uint64_t offset,
1092                       const GNUNET_HashCode *key,
1093                       const GNUNET_HashCode *vhash,
1094                       enum GNUNET_BLOCK_Type type,                    
1095                       PluginDatumProcessor proc, void *proc_cls)
1096 {
1097   struct Plugin *plugin = cls;
1098   int ret;
1099   MYSQL_BIND cbind[1];
1100   long long total;
1101   unsigned long hashSize;
1102   unsigned long hashSize2;
1103   unsigned long long off;
1104
1105   GNUNET_assert (key != NULL);
1106   GNUNET_assert (NULL != proc);
1107   hashSize = sizeof (GNUNET_HashCode);
1108   hashSize2 = sizeof (GNUNET_HashCode);
1109   memset (cbind, 0, sizeof (cbind));
1110   total = -1;
1111   cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
1112   cbind[0].buffer = &total;
1113   cbind[0].is_unsigned = GNUNET_NO;
1114   if (type != 0)
1115     {
1116       if (vhash != NULL)
1117         {
1118           ret =
1119             prepared_statement_run_select (plugin,
1120                                            plugin->count_entry_by_hash_vhash_and_type, 
1121                                            1, cbind, 
1122                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1123                                            MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2, 
1124                                            MYSQL_TYPE_LONG, &type, GNUNET_YES,
1125                                            -1);
1126         }
1127       else
1128         {
1129           ret =
1130             prepared_statement_run_select (plugin,
1131                                            plugin->count_entry_by_hash_and_type, 
1132                                            1, cbind, 
1133                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1134                                            MYSQL_TYPE_LONG, &type, GNUNET_YES,
1135                                            -1);
1136         }
1137     }
1138   else
1139     {
1140       if (vhash != NULL)
1141         {
1142           ret =
1143             prepared_statement_run_select (plugin,
1144                                            plugin->count_entry_by_hash_and_vhash, 
1145                                            1, cbind,
1146                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1147                                            MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2, 
1148                                            -1);
1149
1150         }
1151       else
1152         {
1153           ret =
1154             prepared_statement_run_select (plugin,
1155                                            plugin->count_entry_by_hash,
1156                                            1, cbind, 
1157                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1158                                            -1);
1159         }
1160     }
1161   if ((ret != GNUNET_OK) || (0 >= total))
1162     {
1163       proc (proc_cls, 
1164             NULL, 0, NULL, 0, 0, 0, 
1165             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1166       return;
1167     }
1168   offset = offset % total;
1169   off = (unsigned long long) offset;
1170 #if DEBUG_MYSQL
1171   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1172               "Obtaining %llu/%lld result for GET `%s'\n",
1173               off,
1174               total,
1175               GNUNET_h2s (key));
1176 #endif
1177
1178   if (type != GNUNET_BLOCK_TYPE_ANY)
1179     {
1180       if (NULL != vhash)
1181         {
1182           execute_select (plugin,
1183                           plugin->select_entry_by_hash_vhash_and_type, 
1184                           proc, proc_cls,
1185                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1186                           MYSQL_TYPE_BLOB, vhash, hashSize, &hashSize,
1187                           MYSQL_TYPE_LONG, &type, GNUNET_YES, 
1188                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1189                           -1);
1190         }
1191       else
1192         {
1193           execute_select (plugin,
1194                           plugin->select_entry_by_hash_and_type, 
1195                           proc, proc_cls,
1196                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1197                           MYSQL_TYPE_LONG, &type, GNUNET_YES, 
1198                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1199                           -1);
1200         }
1201     }
1202   else
1203     {
1204       if (NULL != vhash)
1205         {
1206           execute_select (plugin,
1207                           plugin->select_entry_by_hash_and_vhash, 
1208                           proc, proc_cls,
1209                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1210                           MYSQL_TYPE_BLOB, vhash, hashSize, &hashSize, 
1211                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES, 
1212                           -1);
1213         }
1214       else
1215         {
1216           execute_select (plugin,
1217                           plugin->select_entry_by_hash, 
1218                           proc, proc_cls,
1219                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1220                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES, 
1221                           -1);
1222         }
1223     }
1224 }
1225
1226
1227 /**
1228  * Get a zero-anonymity datum from the datastore.
1229  *
1230  * @param cls our "struct Plugin*"
1231  * @param offset offset of the result
1232  * @param type entries of which type should be considered?
1233  *        Use 0 for any type.
1234  * @param iter function to call on each matching value;
1235  *        will be called once with a NULL value at the end
1236  * @param iter_cls closure for iter
1237  */
1238 static void
1239 mysql_plugin_get_zero_anonymity (void *cls,
1240                                  uint64_t offset,
1241                                  enum GNUNET_BLOCK_Type type,
1242                                  PluginDatumProcessor proc, void *proc_cls)
1243 {
1244   struct Plugin *plugin = cls;
1245   unsigned long long off;
1246
1247   off = (unsigned long long) offset;
1248   execute_select (plugin,
1249                   plugin->zero_iter,
1250                   proc, proc_cls,
1251                   MYSQL_TYPE_LONG, &type, GNUNET_YES,
1252                   MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1253                   -1);
1254
1255 }
1256
1257
1258 /**
1259  * Context for 'repl_proc' function.
1260  */
1261 struct ReplCtx
1262 {
1263   
1264   /**
1265    * Plugin handle.
1266    */
1267   struct Plugin *plugin;
1268   
1269   /**
1270    * Function to call for the result (or the NULL).
1271    */
1272   PluginDatumProcessor proc;
1273   
1274   /**
1275    * Closure for proc.
1276    */
1277   void *proc_cls;
1278 };
1279
1280
1281 /**
1282  * Wrapper for the processor for 'mysql_plugin_get_replication'.
1283  * Decrements the replication counter and calls the original
1284  * iterator.
1285  *
1286  * @param cls closure
1287  * @param key key for the content
1288  * @param size number of bytes in data
1289  * @param data content stored
1290  * @param type type of the content
1291  * @param priority priority of the content
1292  * @param anonymity anonymity-level for the content
1293  * @param expiration expiration time for the content
1294  * @param uid unique identifier for the datum;
1295  *        maybe 0 if no unique identifier is available
1296  *
1297  * @return GNUNET_SYSERR to abort the iteration, GNUNET_OK to continue
1298  *         (continue on call to "next", of course),
1299  *         GNUNET_NO to delete the item and continue (if supported)
1300  */
1301 static int
1302 repl_proc (void *cls,
1303            const GNUNET_HashCode *key,
1304            uint32_t size,
1305            const void *data,
1306            enum GNUNET_BLOCK_Type type,
1307            uint32_t priority,
1308            uint32_t anonymity,
1309            struct GNUNET_TIME_Absolute expiration, 
1310            uint64_t uid)
1311 {
1312   struct ReplCtx *rc = cls;
1313   struct Plugin *plugin = rc->plugin;
1314   unsigned long long oid;
1315   int ret;
1316   int iret;
1317
1318   ret = rc->proc (rc->proc_cls,
1319                   key,
1320                   size, data, 
1321                   type, priority, anonymity, expiration,
1322                   uid);
1323   if (NULL != key)
1324     {
1325       oid = (unsigned long long) uid;
1326       iret = prepared_statement_run (plugin,
1327                                      plugin->dec_repl,
1328                                      NULL,
1329                                      MYSQL_TYPE_LONGLONG, &oid, GNUNET_YES, 
1330                                      -1);
1331       if (iret == GNUNET_SYSERR)
1332         {
1333           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1334                       "Failed to reduce replication counter\n");
1335           return GNUNET_SYSERR;
1336         }
1337     }
1338   return ret;
1339 }
1340
1341
1342 /**
1343  * Get a random item for replication.  Returns a single, not expired,
1344  * random item from those with the highest replication counters.  The
1345  * item's replication counter is decremented by one IF it was positive
1346  * before.  Call 'proc' with all values ZERO or NULL if the datastore
1347  * is empty.
1348  *
1349  * @param cls closure
1350  * @param proc function to call the value (once only).
1351  * @param iter_cls closure for proc
1352  */
1353 static void
1354 mysql_plugin_get_replication (void *cls,
1355                               PluginDatumProcessor proc, void *proc_cls)
1356 {
1357   struct Plugin *plugin = cls;
1358   struct ReplCtx rc;
1359   
1360   rc.plugin = plugin;
1361   rc.proc = proc;
1362   rc.proc_cls = proc_cls;
1363   execute_select (plugin,
1364                   plugin->select_replication, 
1365                   &repl_proc, &rc,
1366                   -1);
1367
1368 }
1369
1370
1371 /**
1372  * Get a random item for expiration.
1373  * Call 'proc' with all values ZERO or NULL if the datastore is empty.
1374  *
1375  * @param cls closure
1376  * @param proc function to call the value (once only).
1377  * @param proc_cls closure for proc
1378  */
1379 static void
1380 mysql_plugin_get_expiration (void *cls,
1381                              PluginDatumProcessor proc, void *proc_cls)
1382 {
1383   struct Plugin *plugin = cls;
1384   long long nt;
1385
1386   nt = (long long) GNUNET_TIME_absolute_get().abs_value;
1387   execute_select (plugin,
1388                   plugin->select_expiration, 
1389                   proc, proc_cls,
1390                   MYSQL_TYPE_LONGLONG, &nt, GNUNET_YES, 
1391                   -1);
1392
1393 }
1394
1395
1396 /**
1397  * Drop database.
1398  *
1399  * @param cls the "struct Plugin*"
1400  */
1401 static void 
1402 mysql_plugin_drop (void *cls)
1403 {
1404   struct Plugin *plugin = cls;
1405
1406   if (GNUNET_OK != run_statement (plugin,
1407                                   "DROP TABLE gn090"))
1408     return;           /* error */
1409   plugin->env->duc (plugin->env->cls, 0);
1410 }
1411
1412
1413 /**
1414  * Entry point for the plugin.
1415  *
1416  * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
1417  * @return our "struct Plugin*"
1418  */
1419 void *
1420 libgnunet_plugin_datastore_mysql_init (void *cls)
1421 {
1422   struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
1423   struct GNUNET_DATASTORE_PluginFunctions *api;
1424   struct Plugin *plugin;
1425
1426   plugin = GNUNET_malloc (sizeof (struct Plugin));
1427   plugin->env = env;
1428   plugin->cnffile = get_my_cnf_path (env->cfg);
1429   if (GNUNET_OK != iopen (plugin))
1430     {
1431       iclose (plugin);
1432       GNUNET_free_non_null (plugin->cnffile);
1433       GNUNET_free (plugin);
1434       return NULL;
1435     }
1436 #define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
1437 #define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
1438   if (MRUNS ("CREATE TABLE IF NOT EXISTS gn090 ("
1439              " repl INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1440              " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1441              " prio INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1442              " anonLevel INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1443              " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1444              " hash BINARY(64) NOT NULL DEFAULT '',"
1445              " vhash BINARY(64) NOT NULL DEFAULT '',"
1446              " value BLOB NOT NULL DEFAULT '',"
1447              " uid BIGINT NOT NULL AUTO_INCREMENT,"
1448              " PRIMARY KEY (uid),"
1449              " INDEX idx_hash (hash(64)),"
1450              " INDEX idx_hash_uid (hash(64),uid),"
1451              " INDEX idx_hash_vhash (hash(64),vhash(64)),"
1452              " INDEX idx_hash_type_uid (hash(64),type,uid),"
1453              " INDEX idx_prio (prio),"
1454              " INDEX idx_repl (repl),"
1455              " INDEX idx_expire_prio (expire,prio),"
1456              " INDEX idx_anonLevel_uid (anonLevel,uid)"
1457              ") ENGINE=InnoDB") ||
1458       MRUNS ("SET AUTOCOMMIT = 1") ||
1459       PINIT (plugin->insert_entry, INSERT_ENTRY) ||
1460       PINIT (plugin->delete_entry_by_uid, DELETE_ENTRY_BY_UID) ||
1461       PINIT (plugin->select_entry_by_hash, SELECT_ENTRY_BY_HASH) ||
1462       PINIT (plugin->select_entry_by_hash_and_vhash, SELECT_ENTRY_BY_HASH_AND_VHASH)
1463       || PINIT (plugin->select_entry_by_hash_and_type, SELECT_ENTRY_BY_HASH_AND_TYPE)
1464       || PINIT (plugin->select_entry_by_hash_vhash_and_type,
1465                 SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1466       || PINIT (plugin->count_entry_by_hash, COUNT_ENTRY_BY_HASH)
1467       || PINIT (plugin->get_size, SELECT_SIZE)
1468       || PINIT (plugin->count_entry_by_hash_and_vhash, COUNT_ENTRY_BY_HASH_AND_VHASH)
1469       || PINIT (plugin->count_entry_by_hash_and_type, COUNT_ENTRY_BY_HASH_AND_TYPE)
1470       || PINIT (plugin->count_entry_by_hash_vhash_and_type,
1471                 COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1472       || PINIT (plugin->update_entry, UPDATE_ENTRY)
1473       || PINIT (plugin->dec_repl, DEC_REPL)
1474       || PINIT (plugin->zero_iter, SELECT_IT_NON_ANONYMOUS) 
1475       || PINIT (plugin->select_expiration, SELECT_IT_EXPIRATION) 
1476       || PINIT (plugin->select_replication, SELECT_IT_REPLICATION) )
1477     {
1478       iclose (plugin);
1479       GNUNET_free_non_null (plugin->cnffile);
1480       GNUNET_free (plugin);
1481       return NULL;
1482     }
1483 #undef PINIT
1484 #undef MRUNS
1485
1486   api = GNUNET_malloc (sizeof (struct GNUNET_DATASTORE_PluginFunctions));
1487   api->cls = plugin;
1488   api->estimate_size = &mysql_plugin_estimate_size;
1489   api->put = &mysql_plugin_put;
1490   api->update = &mysql_plugin_update;
1491   api->get_key = &mysql_plugin_get_key;
1492   api->get_replication = &mysql_plugin_get_replication;
1493   api->get_expiration = &mysql_plugin_get_expiration;
1494   api->get_zero_anonymity = &mysql_plugin_get_zero_anonymity;
1495   api->drop = &mysql_plugin_drop;
1496   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1497                    "mysql", _("Mysql database running\n"));
1498   return api;
1499 }
1500
1501
1502 /**
1503  * Exit point from the plugin.
1504  * @param cls our "struct Plugin*"
1505  * @return always NULL
1506  */
1507 void *
1508 libgnunet_plugin_datastore_mysql_done (void *cls)
1509 {
1510   struct GNUNET_DATASTORE_PluginFunctions *api = cls;
1511   struct Plugin *plugin = api->cls;
1512
1513   iclose (plugin);
1514   GNUNET_free_non_null (plugin->cnffile);
1515   GNUNET_free (plugin);
1516   GNUNET_free (api);
1517   mysql_library_end ();
1518   return NULL;
1519 }
1520
1521 /* end of plugin_datastore_mysql.c */