1909d8ca222a9ff215a32980dee9abab3aa5fc5f
[collectd.git] / src / dbi.c
1 /**
2  * collectd - src/dbi.c
3  * Copyright (C) 2008-2015  Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_db_query.h"
32
33 #include <dbi/dbi.h>
34
35 /* libdbi 0.9.0 introduced a new thread-safe interface and marked the old
36  * functions "deprecated". These macros convert the new functions to their old
37  * counterparts for backwards compatibility. */
38 #if !defined(LIBDBI_VERSION) || (LIBDBI_VERSION < 900)
39 #define HAVE_LEGACY_LIBDBI 1
40 #define dbi_initialize_r(a, inst) dbi_initialize(a)
41 #define dbi_shutdown_r(inst) dbi_shutdown()
42 #define dbi_set_verbosity_r(a, inst) dbi_set_verbosity(a)
43 #define dbi_driver_list_r(a, inst) dbi_driver_list(a)
44 #define dbi_driver_open_r(a, inst) dbi_driver_open(a)
45 #endif
46
47 /*
48  * Data types
49  */
50 struct cdbi_driver_option_s /* {{{ */
51 {
52   char *key;
53   union {
54     char *string;
55     int numeric;
56   } value;
57   bool is_numeric;
58 };
59 typedef struct cdbi_driver_option_s cdbi_driver_option_t; /* }}} */
60
61 struct cdbi_database_s /* {{{ */
62 {
63   char *name;
64   char *select_db;
65   char *plugin_name;
66
67   cdtime_t interval;
68
69   char *driver;
70   char *host;
71   cdbi_driver_option_t *driver_options;
72   size_t driver_options_num;
73
74   udb_query_preparation_area_t **q_prep_areas;
75   udb_query_t **queries;
76   size_t queries_num;
77
78   dbi_conn connection;
79 };
80 typedef struct cdbi_database_s cdbi_database_t; /* }}} */
81
82 /*
83  * Global variables
84  */
85 #if !defined(HAVE_LEGACY_LIBDBI) || !HAVE_LEGACY_LIBDBI
86 static dbi_inst dbi_instance;
87 #endif
88 static udb_query_t **queries;
89 static size_t queries_num;
90 static cdbi_database_t **databases;
91 static size_t databases_num;
92
93 static int cdbi_read_database(user_data_t *ud);
94
95 /*
96  * Functions
97  */
98 static const char *cdbi_strerror(dbi_conn conn, /* {{{ */
99                                  char *buffer, size_t buffer_size) {
100   const char *msg;
101   int status;
102
103   if (conn == NULL) {
104     sstrncpy(buffer, "connection is NULL", buffer_size);
105     return buffer;
106   }
107
108   msg = NULL;
109   status = dbi_conn_error(conn, &msg);
110   if ((status >= 0) && (msg != NULL))
111     snprintf(buffer, buffer_size, "%s (status %i)", msg, status);
112   else
113     snprintf(buffer, buffer_size, "dbi_conn_error failed with status %i",
114              status);
115
116   return buffer;
117 } /* }}} const char *cdbi_conn_error */
118
119 static int cdbi_result_get_field(dbi_result res, /* {{{ */
120                                  unsigned int index, char *buffer,
121                                  size_t buffer_size) {
122   unsigned short src_type;
123
124   src_type = dbi_result_get_field_type_idx(res, index);
125   if (src_type == DBI_TYPE_ERROR) {
126     ERROR("dbi plugin: cdbi_result_get: "
127           "dbi_result_get_field_type_idx failed.");
128     return -1;
129   }
130
131   if (src_type == DBI_TYPE_INTEGER) {
132     long long value;
133
134     value = dbi_result_get_longlong_idx(res, index);
135     snprintf(buffer, buffer_size, "%lli", value);
136   } else if (src_type == DBI_TYPE_DECIMAL) {
137     double value;
138
139     value = dbi_result_get_double_idx(res, index);
140     snprintf(buffer, buffer_size, "%63.15g", value);
141   } else if (src_type == DBI_TYPE_STRING) {
142     const char *value;
143
144     value = dbi_result_get_string_idx(res, index);
145     if (value == NULL)
146       sstrncpy(buffer, "", buffer_size);
147     else if (strcmp("ERROR", value) == 0)
148       return -1;
149     else
150       sstrncpy(buffer, value, buffer_size);
151   }
152   /* DBI_TYPE_BINARY */
153   /* DBI_TYPE_DATETIME */
154   else {
155     const char *field_name;
156
157     field_name = dbi_result_get_field_name(res, index);
158     if (field_name == NULL)
159       field_name = "<unknown>";
160
161     ERROR("dbi plugin: Column `%s': Don't know how to handle "
162           "source type %hu.",
163           field_name, src_type);
164     return -1;
165   }
166
167   return 0;
168 } /* }}} int cdbi_result_get_field */
169
170 static void cdbi_database_free(cdbi_database_t *db) /* {{{ */
171 {
172   if (db == NULL)
173     return;
174
175   sfree(db->name);
176   sfree(db->select_db);
177   sfree(db->plugin_name);
178   sfree(db->driver);
179   sfree(db->host);
180
181   for (size_t i = 0; i < db->driver_options_num; i++) {
182     sfree(db->driver_options[i].key);
183     if (!db->driver_options[i].is_numeric)
184       sfree(db->driver_options[i].value.string);
185   }
186   sfree(db->driver_options);
187
188   if (db->q_prep_areas)
189     for (size_t i = 0; i < db->queries_num; ++i)
190       udb_query_delete_preparation_area(db->q_prep_areas[i]);
191   sfree(db->q_prep_areas);
192   /* N.B.: db->queries references objects "owned" by the global queries
193    * variable. Free the array here, but not the content. */
194   sfree(db->queries);
195
196   sfree(db);
197 } /* }}} void cdbi_database_free */
198
199 /* Configuration handling functions {{{
200  *
201  * <Plugin dbi>
202  *   <Query "plugin_instance0">
203  *     Statement "SELECT name, value FROM table"
204  *     <Result>
205  *       Type "gauge"
206  *       InstancesFrom "name"
207  *       ValuesFrom "value"
208  *     </Result>
209  *     ...
210  *   </Query>
211  *
212  *   <Database "plugin_instance1">
213  *     Driver "mysql"
214  *     Interval 120
215  *     DriverOption "hostname" "localhost"
216  *     ...
217  *     Query "plugin_instance0"
218  *   </Database>
219  * </Plugin>
220  */
221
222 static int cdbi_config_add_database_driver_option(cdbi_database_t *db, /* {{{ */
223                                                   oconfig_item_t *ci) {
224   cdbi_driver_option_t *option;
225
226   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
227       ((ci->values[1].type != OCONFIG_TYPE_STRING) &&
228        (ci->values[1].type != OCONFIG_TYPE_NUMBER))) {
229     WARNING("dbi plugin: The `DriverOption' config option "
230             "needs exactly two arguments.");
231     return -1;
232   }
233
234   option = realloc(db->driver_options,
235                    sizeof(*option) * (db->driver_options_num + 1));
236   if (option == NULL) {
237     ERROR("dbi plugin: realloc failed");
238     return -1;
239   }
240
241   db->driver_options = option;
242   option = db->driver_options + db->driver_options_num;
243   memset(option, 0, sizeof(*option));
244
245   option->key = strdup(ci->values[0].value.string);
246   if (option->key == NULL) {
247     ERROR("dbi plugin: strdup failed.");
248     return -1;
249   }
250
251   if (ci->values[1].type == OCONFIG_TYPE_STRING) {
252     option->value.string = strdup(ci->values[1].value.string);
253     if (option->value.string == NULL) {
254       ERROR("dbi plugin: strdup failed.");
255       sfree(option->key);
256       return -1;
257     }
258   } else {
259     assert(ci->values[1].type == OCONFIG_TYPE_NUMBER);
260     option->value.numeric = (int)(ci->values[1].value.number + .5);
261     option->is_numeric = true;
262   }
263
264   db->driver_options_num++;
265   return 0;
266 } /* }}} int cdbi_config_add_database_driver_option */
267
268 static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */
269 {
270   cdbi_database_t *db;
271   int status;
272
273   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
274     WARNING("dbi plugin: The `Database' block "
275             "needs exactly one string argument.");
276     return -1;
277   }
278
279   db = calloc(1, sizeof(*db));
280   if (db == NULL) {
281     ERROR("dbi plugin: calloc failed.");
282     return -1;
283   }
284
285   status = cf_util_get_string(ci, &db->name);
286   if (status != 0) {
287     sfree(db);
288     return status;
289   }
290
291   /* Fill the `cdbi_database_t' structure.. */
292   for (int i = 0; i < ci->children_num; i++) {
293     oconfig_item_t *child = ci->children + i;
294
295     if (strcasecmp("Driver", child->key) == 0)
296       status = cf_util_get_string(child, &db->driver);
297     else if (strcasecmp("DriverOption", child->key) == 0)
298       status = cdbi_config_add_database_driver_option(db, child);
299     else if (strcasecmp("SelectDB", child->key) == 0)
300       status = cf_util_get_string(child, &db->select_db);
301     else if (strcasecmp("Query", child->key) == 0)
302       status = udb_query_pick_from_list(child, queries, queries_num,
303                                         &db->queries, &db->queries_num);
304     else if (strcasecmp("Host", child->key) == 0)
305       status = cf_util_get_string(child, &db->host);
306     else if (strcasecmp("Interval", child->key) == 0)
307       status = cf_util_get_cdtime(child, &db->interval);
308     else if (strcasecmp("Plugin", child->key) == 0)
309       status = cf_util_get_string(child, &db->plugin_name);
310     else {
311       WARNING("dbi plugin: Option `%s' not allowed here.", child->key);
312       status = -1;
313     }
314
315     if (status != 0)
316       break;
317   }
318
319   /* Check that all necessary options have been given. */
320   while (status == 0) {
321     if (db->driver == NULL) {
322       WARNING("dbi plugin: `Driver' not given for database `%s'", db->name);
323       status = -1;
324     }
325     if (db->driver_options_num == 0) {
326       WARNING("dbi plugin: No `DriverOption' given for database `%s'. "
327               "This will likely not work.",
328               db->name);
329     }
330
331     break;
332   } /* while (status == 0) */
333
334   while ((status == 0) && (db->queries_num > 0)) {
335     db->q_prep_areas = calloc(db->queries_num, sizeof(*db->q_prep_areas));
336     if (db->q_prep_areas == NULL) {
337       WARNING("dbi plugin: calloc failed");
338       status = -1;
339       break;
340     }
341
342     for (size_t i = 0; i < db->queries_num; ++i) {
343       db->q_prep_areas[i] = udb_query_allocate_preparation_area(db->queries[i]);
344
345       if (db->q_prep_areas[i] == NULL) {
346         WARNING("dbi plugin: udb_query_allocate_preparation_area failed");
347         status = -1;
348         break;
349       }
350     }
351
352     break;
353   }
354
355   /* If all went well, add this database to the global list of databases. */
356   if (status == 0) {
357     cdbi_database_t **temp;
358
359     temp = realloc(databases, sizeof(*databases) * (databases_num + 1));
360     if (temp == NULL) {
361       ERROR("dbi plugin: realloc failed");
362       status = -1;
363     } else {
364       databases = temp;
365       databases[databases_num] = db;
366       databases_num++;
367
368       char *name = ssnprintf_alloc("dbi:%s", db->name);
369       plugin_register_complex_read(
370           /* group = */ NULL,
371           /* name = */ name ? name : db->name,
372           /* callback = */ cdbi_read_database,
373           /* interval = */ (db->interval > 0) ? db->interval : 0,
374           &(user_data_t){
375               .data = db,
376           });
377       sfree(name);
378     }
379   }
380
381   if (status != 0) {
382     cdbi_database_free(db);
383     return -1;
384   }
385
386   return 0;
387 } /* }}} int cdbi_config_add_database */
388
389 static int cdbi_config(oconfig_item_t *ci) /* {{{ */
390 {
391   for (int i = 0; i < ci->children_num; i++) {
392     oconfig_item_t *child = ci->children + i;
393     if (strcasecmp("Query", child->key) == 0)
394       udb_query_create(&queries, &queries_num, child,
395                        /* callback = */ NULL);
396     else if (strcasecmp("Database", child->key) == 0)
397       cdbi_config_add_database(child);
398     else {
399       WARNING("dbi plugin: Ignoring unknown config option `%s'.", child->key);
400     }
401   } /* for (ci->children) */
402
403   return 0;
404 } /* }}} int cdbi_config */
405
406 /* }}} End of configuration handling functions */
407
408 static int cdbi_init(void) /* {{{ */
409 {
410   static int did_init;
411   int status;
412
413   if (did_init != 0)
414     return 0;
415
416   if (queries_num == 0) {
417     ERROR("dbi plugin: No <Query> blocks have been found. Without them, "
418           "this plugin can't do anything useful, so we will return an error.");
419     return -1;
420   }
421
422   if (databases_num == 0) {
423     ERROR("dbi plugin: No <Database> blocks have been found. Without them, "
424           "this plugin can't do anything useful, so we will return an error.");
425     return -1;
426   }
427
428   status = dbi_initialize_r(/* driverdir = */ NULL, &dbi_instance);
429   if (status < 0) {
430     ERROR("dbi plugin: cdbi_init: dbi_initialize_r failed with status %i.",
431           status);
432     return -1;
433   } else if (status == 0) {
434     ERROR("dbi plugin: `dbi_initialize_r' could not load any drivers. Please "
435           "install at least one `DBD' or check your installation.");
436     return -1;
437   }
438   DEBUG("dbi plugin: cdbi_init: dbi_initialize_r reports %i driver%s.", status,
439         (status == 1) ? "" : "s");
440
441   return 0;
442 } /* }}} int cdbi_init */
443
444 static int cdbi_read_database_query(cdbi_database_t *db, /* {{{ */
445                                     udb_query_t *q,
446                                     udb_query_preparation_area_t *prep_area) {
447   const char *statement;
448   dbi_result res;
449   size_t column_num;
450   char **column_names;
451   char **column_values;
452   int status;
453
454 /* Macro that cleans up dynamically allocated memory and returns the
455  * specified status. */
456 #define BAIL_OUT(status)                                                       \
457   if (column_names != NULL) {                                                  \
458     sfree(column_names[0]);                                                    \
459     sfree(column_names);                                                       \
460   }                                                                            \
461   if (column_values != NULL) {                                                 \
462     sfree(column_values[0]);                                                   \
463     sfree(column_values);                                                      \
464   }                                                                            \
465   if (res != NULL) {                                                           \
466     dbi_result_free(res);                                                      \
467     res = NULL;                                                                \
468   }                                                                            \
469   return (status)
470
471   column_names = NULL;
472   column_values = NULL;
473
474   statement = udb_query_get_statement(q);
475   assert(statement != NULL);
476
477   res = dbi_conn_query(db->connection, statement);
478   if (res == NULL) {
479     char errbuf[1024];
480     ERROR("dbi plugin: cdbi_read_database_query (%s, %s): "
481           "dbi_conn_query failed: %s",
482           db->name, udb_query_get_name(q),
483           cdbi_strerror(db->connection, errbuf, sizeof(errbuf)));
484     BAIL_OUT(-1);
485   } else /* Get the number of columns */
486   {
487     unsigned int db_status;
488
489     db_status = dbi_result_get_numfields(res);
490     if (db_status == DBI_FIELD_ERROR) {
491       char errbuf[1024];
492       ERROR("dbi plugin: cdbi_read_database_query (%s, %s): "
493             "dbi_result_get_numfields failed: %s",
494             db->name, udb_query_get_name(q),
495             cdbi_strerror(db->connection, errbuf, sizeof(errbuf)));
496       BAIL_OUT(-1);
497     }
498
499     column_num = (size_t)db_status;
500     DEBUG("cdbi_read_database_query (%s, %s): There are %" PRIsz " columns.",
501           db->name, udb_query_get_name(q), column_num);
502   }
503
504   /* Allocate `column_names' and `column_values'. {{{ */
505   column_names = calloc(column_num, sizeof(*column_names));
506   if (column_names == NULL) {
507     ERROR("dbi plugin: calloc failed.");
508     BAIL_OUT(-1);
509   }
510
511   column_names[0] = calloc(column_num, DATA_MAX_NAME_LEN);
512   if (column_names[0] == NULL) {
513     ERROR("dbi plugin: calloc failed.");
514     BAIL_OUT(-1);
515   }
516   for (size_t i = 1; i < column_num; i++)
517     column_names[i] = column_names[i - 1] + DATA_MAX_NAME_LEN;
518
519   column_values = calloc(column_num, sizeof(*column_values));
520   if (column_values == NULL) {
521     ERROR("dbi plugin: calloc failed.");
522     BAIL_OUT(-1);
523   }
524
525   column_values[0] = calloc(column_num, DATA_MAX_NAME_LEN);
526   if (column_values[0] == NULL) {
527     ERROR("dbi plugin: calloc failed.");
528     BAIL_OUT(-1);
529   }
530   for (size_t i = 1; i < column_num; i++)
531     column_values[i] = column_values[i - 1] + DATA_MAX_NAME_LEN;
532   /* }}} */
533
534   /* Copy the field names to `column_names' */
535   for (size_t i = 0; i < column_num; i++) /* {{{ */
536   {
537     const char *column_name;
538
539     column_name = dbi_result_get_field_name(res, (unsigned int)(i + 1));
540     if (column_name == NULL) {
541       ERROR("dbi plugin: cdbi_read_database_query (%s, %s): "
542             "Cannot retrieve name of field %" PRIsz ".",
543             db->name, udb_query_get_name(q), i + 1);
544       BAIL_OUT(-1);
545     }
546
547     sstrncpy(column_names[i], column_name, DATA_MAX_NAME_LEN);
548   } /* }}} for (i = 0; i < column_num; i++) */
549
550   status = udb_query_prepare_result(
551       q, prep_area, (db->host ? db->host : hostname_g),
552       /* plugin = */ (db->plugin_name != NULL) ? db->plugin_name : "dbi",
553       db->name, column_names, column_num,
554       /* interval = */ (db->interval > 0) ? db->interval : 0);
555
556   if (status != 0) {
557     ERROR("dbi plugin: udb_query_prepare_result failed with status %i.",
558           status);
559     BAIL_OUT(-1);
560   }
561
562   /* 0 = error; 1 = success; */
563   status = dbi_result_first_row(res); /* {{{ */
564   if (status != 1) {
565     char errbuf[1024];
566     ERROR("dbi plugin: cdbi_read_database_query (%s, %s): "
567           "dbi_result_first_row failed: %s. Maybe the statement didn't "
568           "return any rows?",
569           db->name, udb_query_get_name(q),
570           cdbi_strerror(db->connection, errbuf, sizeof(errbuf)));
571     udb_query_finish_result(q, prep_area);
572     BAIL_OUT(-1);
573   } /* }}} */
574
575   /* Iterate over all rows and call `udb_query_handle_result' with each list of
576    * values. */
577   while (42) /* {{{ */
578   {
579     status = 0;
580     /* Copy the value of the columns to `column_values' */
581     for (size_t i = 0; i < column_num; i++) /* {{{ */
582     {
583       status = cdbi_result_get_field(res, (unsigned int)(i + 1),
584                                      column_values[i], DATA_MAX_NAME_LEN);
585
586       if (status != 0) {
587         ERROR("dbi plugin: cdbi_read_database_query (%s, %s): "
588               "cdbi_result_get_field (%" PRIsz ") failed.",
589               db->name, udb_query_get_name(q), i + 1);
590         status = -1;
591         break;
592       }
593     } /* }}} for (i = 0; i < column_num; i++) */
594
595     /* If all values were copied successfully, call `udb_query_handle_result'
596      * to dispatch the row to the daemon. */
597     if (status == 0) /* {{{ */
598     {
599       status = udb_query_handle_result(q, prep_area, column_values);
600       if (status != 0) {
601         ERROR("dbi plugin: cdbi_read_database_query (%s, %s): "
602               "udb_query_handle_result failed.",
603               db->name, udb_query_get_name(q));
604       }
605     } /* }}} */
606
607     /* Get the next row from the database. */
608     status = dbi_result_next_row(res); /* {{{ */
609     if (status != 1) {
610       if (dbi_conn_error(db->connection, NULL) != 0) {
611         char errbuf[1024];
612         WARNING("dbi plugin: cdbi_read_database_query (%s, %s): "
613                 "dbi_result_next_row failed: %s.",
614                 db->name, udb_query_get_name(q),
615                 cdbi_strerror(db->connection, errbuf, sizeof(errbuf)));
616       }
617       break;
618     } /* }}} */
619   }   /* }}} while (42) */
620
621   /* Tell the db query interface that we're done with this query. */
622   udb_query_finish_result(q, prep_area);
623
624   /* Clean up and return `status = 0' (success) */
625   BAIL_OUT(0);
626 #undef BAIL_OUT
627 } /* }}} int cdbi_read_database_query */
628
629 static int cdbi_connect_database(cdbi_database_t *db) /* {{{ */
630 {
631   dbi_driver driver;
632   dbi_conn connection;
633   int status;
634
635   if (db->connection != NULL) {
636     status = dbi_conn_ping(db->connection);
637     if (status != 0) /* connection is alive */
638       return 0;
639
640     dbi_conn_close(db->connection);
641     db->connection = NULL;
642   }
643
644   driver = dbi_driver_open_r(db->driver, dbi_instance);
645   if (driver == NULL) {
646     ERROR("dbi plugin: cdbi_connect_database: dbi_driver_open_r (%s) failed.",
647           db->driver);
648     INFO("dbi plugin: Maybe the driver isn't installed? "
649          "Known drivers are:");
650     for (driver = dbi_driver_list_r(NULL, dbi_instance); driver != NULL;
651          driver = dbi_driver_list_r(driver, dbi_instance)) {
652       INFO("dbi plugin: * %s", dbi_driver_get_name(driver));
653     }
654     return -1;
655   }
656
657   connection = dbi_conn_open(driver);
658   if (connection == NULL) {
659     ERROR("dbi plugin: cdbi_connect_database: dbi_conn_open (%s) failed.",
660           db->driver);
661     return -1;
662   }
663
664   /* Set all the driver options. Because this is a very very very generic
665    * interface, the error handling is kind of long. If an invalid option is
666    * encountered, it will get a list of options understood by the driver and
667    * report that as `INFO'. This way, users hopefully don't have too much
668    * trouble finding out how to configure the plugin correctly.. */
669   for (size_t i = 0; i < db->driver_options_num; i++) {
670     if (db->driver_options[i].is_numeric) {
671       status =
672           dbi_conn_set_option_numeric(connection, db->driver_options[i].key,
673                                       db->driver_options[i].value.numeric);
674       if (status != 0) {
675         char errbuf[1024];
676         ERROR("dbi plugin: cdbi_connect_database (%s): "
677               "dbi_conn_set_option_numeric (\"%s\", %i) failed: %s.",
678               db->name, db->driver_options[i].key,
679               db->driver_options[i].value.numeric,
680               cdbi_strerror(connection, errbuf, sizeof(errbuf)));
681       }
682     } else {
683       status = dbi_conn_set_option(connection, db->driver_options[i].key,
684                                    db->driver_options[i].value.string);
685       if (status != 0) {
686         char errbuf[1024];
687         ERROR("dbi plugin: cdbi_connect_database (%s): "
688               "dbi_conn_set_option (\"%s\", \"%s\") failed: %s.",
689               db->name, db->driver_options[i].key,
690               db->driver_options[i].value.string,
691               cdbi_strerror(connection, errbuf, sizeof(errbuf)));
692       }
693     }
694
695     if (status != 0) {
696       INFO("dbi plugin: This is a list of all options understood "
697            "by the `%s' driver:",
698            db->driver);
699       for (const char *opt = dbi_conn_get_option_list(connection, NULL);
700            opt != NULL; opt = dbi_conn_get_option_list(connection, opt)) {
701         INFO("dbi plugin: * %s", opt);
702       }
703
704       dbi_conn_close(connection);
705       return -1;
706     }
707   } /* for (i = 0; i < db->driver_options_num; i++) */
708
709   status = dbi_conn_connect(connection);
710   if (status != 0) {
711     char errbuf[1024];
712     ERROR("dbi plugin: cdbi_connect_database (%s): "
713           "dbi_conn_connect failed: %s",
714           db->name, cdbi_strerror(connection, errbuf, sizeof(errbuf)));
715     dbi_conn_close(connection);
716     return -1;
717   }
718
719   if (db->select_db != NULL) {
720     status = dbi_conn_select_db(connection, db->select_db);
721     if (status != 0) {
722       char errbuf[1024];
723       WARNING(
724           "dbi plugin: cdbi_connect_database (%s): "
725           "dbi_conn_select_db (%s) failed: %s. Check the `SelectDB' option.",
726           db->name, db->select_db,
727           cdbi_strerror(connection, errbuf, sizeof(errbuf)));
728       dbi_conn_close(connection);
729       return -1;
730     }
731   }
732
733   db->connection = connection;
734   return 0;
735 } /* }}} int cdbi_connect_database */
736
737 static int cdbi_read_database(user_data_t *ud) /* {{{ */
738 {
739   cdbi_database_t *db = (cdbi_database_t *)ud->data;
740   int success;
741   int status;
742
743   unsigned int db_version;
744
745   status = cdbi_connect_database(db);
746   if (status != 0)
747     return status;
748   assert(db->connection != NULL);
749
750   db_version = dbi_conn_get_engine_version(db->connection);
751   /* TODO: Complain if `db_version == 0' */
752
753   success = 0;
754   for (size_t i = 0; i < db->queries_num; i++) {
755     /* Check if we know the database's version and if so, if this query applies
756      * to that version. */
757     if ((db_version != 0) &&
758         (udb_query_check_version(db->queries[i], db_version) == 0))
759       continue;
760
761     status = cdbi_read_database_query(db, db->queries[i], db->q_prep_areas[i]);
762     if (status == 0)
763       success++;
764   }
765
766   if (success == 0) {
767     ERROR("dbi plugin: All queries failed for database `%s'.", db->name);
768     return -1;
769   }
770
771   return 0;
772 } /* }}} int cdbi_read_database */
773
774 static int cdbi_shutdown(void) /* {{{ */
775 {
776   for (size_t i = 0; i < databases_num; i++) {
777     if (databases[i]->connection != NULL) {
778       dbi_conn_close(databases[i]->connection);
779       databases[i]->connection = NULL;
780     }
781     cdbi_database_free(databases[i]);
782   }
783   sfree(databases);
784   databases_num = 0;
785
786   udb_query_free(queries, queries_num);
787   queries = NULL;
788   queries_num = 0;
789
790   return 0;
791 } /* }}} int cdbi_shutdown */
792
793 void module_register(void) /* {{{ */
794 {
795   plugin_register_complex_config("dbi", cdbi_config);
796   plugin_register_init("dbi", cdbi_init);
797   plugin_register_shutdown("dbi", cdbi_shutdown);
798 } /* }}} void module_register */