41f40d9ca67615b48ad54ea5ee1ddf2699878020
[collectd.git] / src / utils_db_query.c
1 /**
2  * collectd - src/utils_db_query.c
3  * Copyright (C) 2008,2009  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 /*
34  * Data types
35  */
36 struct udb_result_s; /* {{{ */
37 typedef struct udb_result_s udb_result_t;
38 struct udb_result_s {
39   char *type;
40   char *instance_prefix;
41   char **instances;
42   size_t instances_num;
43   char **values;
44   size_t values_num;
45   char **metadata;
46   size_t metadata_num;
47
48   udb_result_t *next;
49 }; /* }}} */
50
51 struct udb_query_s /* {{{ */
52 {
53   char *name;
54   char *statement;
55   void *user_data;
56   char *plugin_instance_from;
57
58   unsigned int min_version;
59   unsigned int max_version;
60
61   udb_result_t *results;
62 }; /* }}} */
63
64 struct udb_result_preparation_area_s /* {{{ */
65 {
66   const data_set_t *ds;
67   size_t *instances_pos;
68   size_t *values_pos;
69   size_t *metadata_pos;
70   char **instances_buffer;
71   char **values_buffer;
72   char **metadata_buffer;
73   char *plugin_instance;
74
75   struct udb_result_preparation_area_s *next;
76 }; /* }}} */
77 typedef struct udb_result_preparation_area_s udb_result_preparation_area_t;
78
79 struct udb_query_preparation_area_s /* {{{ */
80 {
81   size_t column_num;
82   size_t plugin_instance_pos;
83   char *host;
84   char *plugin;
85   char *db_name;
86
87   cdtime_t interval;
88
89   udb_result_preparation_area_t *result_prep_areas;
90 }; /* }}} */
91
92 /*
93  * Config Private functions
94  */
95 static int udb_config_set_string(char **ret_string, /* {{{ */
96                                  oconfig_item_t *ci) {
97   char *string;
98
99   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
100     WARNING("db query utils: The `%s' config option "
101             "needs exactly one string argument.",
102             ci->key);
103     return -1;
104   }
105
106   string = strdup(ci->values[0].value.string);
107   if (string == NULL) {
108     ERROR("db query utils: strdup failed.");
109     return -1;
110   }
111
112   if (*ret_string != NULL)
113     free(*ret_string);
114   *ret_string = string;
115
116   return 0;
117 } /* }}} int udb_config_set_string */
118
119 static int udb_config_add_string(char ***ret_array, /* {{{ */
120                                  size_t *ret_array_len, oconfig_item_t *ci) {
121   char **array;
122   size_t array_len;
123
124   if (ci->values_num < 1) {
125     WARNING("db query utils: The `%s' config option "
126             "needs at least one argument.",
127             ci->key);
128     return -1;
129   }
130
131   for (int i = 0; i < ci->values_num; i++) {
132     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
133       WARNING("db query utils: Argument %i to the `%s' option "
134               "is not a string.",
135               i + 1, ci->key);
136       return -1;
137     }
138   }
139
140   array_len = *ret_array_len;
141   array = realloc(*ret_array, sizeof(char *) * (array_len + ci->values_num));
142   if (array == NULL) {
143     ERROR("db query utils: realloc failed.");
144     return -1;
145   }
146   *ret_array = array;
147
148   for (int i = 0; i < ci->values_num; i++) {
149     array[array_len] = strdup(ci->values[i].value.string);
150     if (array[array_len] == NULL) {
151       ERROR("db query utils: strdup failed.");
152       *ret_array_len = array_len;
153       return -1;
154     }
155     array_len++;
156   }
157
158   *ret_array_len = array_len;
159   return 0;
160 } /* }}} int udb_config_add_string */
161
162 static int udb_config_set_uint(unsigned int *ret_value, /* {{{ */
163                                oconfig_item_t *ci) {
164   double tmp;
165
166   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
167     WARNING("db query utils: The `%s' config option "
168             "needs exactly one numeric argument.",
169             ci->key);
170     return -1;
171   }
172
173   tmp = ci->values[0].value.number;
174   if ((tmp < 0.0) || (tmp > ((double)UINT_MAX)))
175     return -ERANGE;
176
177   *ret_value = (unsigned int)(tmp + .5);
178   return 0;
179 } /* }}} int udb_config_set_uint */
180
181 /*
182  * Result private functions
183  */
184 static int udb_result_submit(udb_result_t *r, /* {{{ */
185                              udb_result_preparation_area_t *r_area,
186                              udb_query_t const *q,
187                              udb_query_preparation_area_t *q_area) {
188   value_list_t vl = VALUE_LIST_INIT;
189
190   assert(r != NULL);
191   assert(r_area->ds != NULL);
192   assert(((size_t)r_area->ds->ds_num) == r->values_num);
193   assert(r->values_num > 0);
194
195   vl.values = calloc(r->values_num, sizeof(*vl.values));
196   if (vl.values == NULL) {
197     ERROR("db query utils: calloc failed.");
198     return -1;
199   }
200   vl.values_len = r_area->ds->ds_num;
201
202   for (size_t i = 0; i < r->values_num; i++) {
203     char *value_str = r_area->values_buffer[i];
204
205     if (0 != parse_value(value_str, &vl.values[i], r_area->ds->ds[i].type)) {
206       ERROR("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
207             value_str, DS_TYPE_TO_STRING(r_area->ds->ds[i].type));
208       errno = EINVAL;
209       free(vl.values);
210       return -1;
211     }
212   }
213
214   if (q_area->interval > 0)
215     vl.interval = q_area->interval;
216
217   sstrncpy(vl.host, q_area->host, sizeof(vl.host));
218   sstrncpy(vl.plugin, q_area->plugin, sizeof(vl.plugin));
219   sstrncpy(vl.type, r->type, sizeof(vl.type));
220
221   /* Set vl.plugin_instance */
222   if (q->plugin_instance_from != NULL) {
223     sstrncpy(vl.plugin_instance, r_area->plugin_instance,
224              sizeof(vl.plugin_instance));
225   } else {
226     sstrncpy(vl.plugin_instance, q_area->db_name, sizeof(vl.plugin_instance));
227   }
228
229   /* Set vl.type_instance {{{ */
230   if (r->instances_num == 0) {
231     if (r->instance_prefix == NULL)
232       vl.type_instance[0] = 0;
233     else
234       sstrncpy(vl.type_instance, r->instance_prefix, sizeof(vl.type_instance));
235   } else /* if ((r->instances_num > 0) */
236   {
237     if (r->instance_prefix == NULL) {
238       int status = strjoin(vl.type_instance, sizeof(vl.type_instance),
239                            r_area->instances_buffer, r->instances_num, "-");
240       if (status < 0) {
241         ERROR(
242             "udb_result_submit: creating type_instance failed with status %d.",
243             status);
244         return status;
245       }
246     } else {
247       char tmp[DATA_MAX_NAME_LEN];
248
249       int status = strjoin(tmp, sizeof(tmp), r_area->instances_buffer,
250                            r->instances_num, "-");
251       if (status < 0) {
252         ERROR(
253             "udb_result_submit: creating type_instance failed with status %d.",
254             status);
255         return status;
256       }
257       tmp[sizeof(tmp) - 1] = 0;
258
259       snprintf(vl.type_instance, sizeof(vl.type_instance), "%s-%s",
260                r->instance_prefix, tmp);
261     }
262   }
263   vl.type_instance[sizeof(vl.type_instance) - 1] = 0;
264   /* }}} */
265
266   /* Annotate meta data. {{{ */
267   if (r->metadata_num > 0) {
268     vl.meta = meta_data_create();
269     if (vl.meta == NULL) {
270       ERROR("db query utils:: meta_data_create failed.");
271       return -ENOMEM;
272     }
273
274     for (size_t i = 0; i < r->metadata_num; i++) {
275       int status = meta_data_add_string(vl.meta, r->metadata[i],
276                                         r_area->metadata_buffer[i]);
277       if (status != 0) {
278         ERROR("db query utils:: meta_data_add_string failed.");
279         meta_data_destroy(vl.meta);
280         vl.meta = NULL;
281         return status;
282       }
283     }
284   }
285   /* }}} */
286
287   plugin_dispatch_values(&vl);
288
289   if (r->metadata_num > 0) {
290     meta_data_destroy(vl.meta);
291     vl.meta = NULL;
292   }
293   sfree(vl.values);
294   return 0;
295 } /* }}} void udb_result_submit */
296
297 static void udb_result_finish_result(udb_result_t const *r, /* {{{ */
298                                      udb_result_preparation_area_t *prep_area) {
299   if ((r == NULL) || (prep_area == NULL))
300     return;
301
302   prep_area->ds = NULL;
303   sfree(prep_area->instances_pos);
304   sfree(prep_area->values_pos);
305   sfree(prep_area->metadata_pos);
306   sfree(prep_area->instances_buffer);
307   sfree(prep_area->values_buffer);
308   sfree(prep_area->metadata_buffer);
309 } /* }}} void udb_result_finish_result */
310
311 static int udb_result_handle_result(udb_result_t *r, /* {{{ */
312                                     udb_query_preparation_area_t *q_area,
313                                     udb_result_preparation_area_t *r_area,
314                                     udb_query_t const *q,
315                                     char **column_values) {
316   assert(r && q_area && r_area);
317
318   for (size_t i = 0; i < r->instances_num; i++)
319     r_area->instances_buffer[i] = column_values[r_area->instances_pos[i]];
320
321   for (size_t i = 0; i < r->values_num; i++)
322     r_area->values_buffer[i] = column_values[r_area->values_pos[i]];
323
324   for (size_t i = 0; i < r->metadata_num; i++)
325     r_area->metadata_buffer[i] = column_values[r_area->metadata_pos[i]];
326
327   if (q->plugin_instance_from)
328     r_area->plugin_instance = column_values[q_area->plugin_instance_pos];
329
330   return udb_result_submit(r, r_area, q, q_area);
331 } /* }}} int udb_result_handle_result */
332
333 static int udb_result_prepare_result(udb_result_t const *r, /* {{{ */
334                                      udb_result_preparation_area_t *prep_area,
335                                      char **column_names, size_t column_num) {
336   if ((r == NULL) || (prep_area == NULL))
337     return -EINVAL;
338
339 #define BAIL_OUT(status)                                                       \
340   prep_area->ds = NULL;                                                        \
341   sfree(prep_area->instances_pos);                                             \
342   sfree(prep_area->values_pos);                                                \
343   sfree(prep_area->metadata_pos);                                              \
344   sfree(prep_area->instances_buffer);                                          \
345   sfree(prep_area->values_buffer);                                             \
346   sfree(prep_area->metadata_buffer);                                           \
347   return (status)
348
349   /* Make sure previous preparations are cleaned up. */
350   udb_result_finish_result(r, prep_area);
351   prep_area->instances_pos = NULL;
352   prep_area->values_pos = NULL;
353   prep_area->metadata_pos = NULL;
354
355   /* Read `ds' and check number of values {{{ */
356   prep_area->ds = plugin_get_ds(r->type);
357   if (prep_area->ds == NULL) {
358     ERROR("db query utils: udb_result_prepare_result: Type `%s' is not "
359           "known by the daemon. See types.db(5) for details.",
360           r->type);
361     BAIL_OUT(-1);
362   }
363
364   if (prep_area->ds->ds_num != r->values_num) {
365     ERROR("db query utils: udb_result_prepare_result: The type `%s' "
366           "requires exactly %zu value%s, but the configuration specifies %zu.",
367           r->type, prep_area->ds->ds_num,
368           (prep_area->ds->ds_num == 1) ? "" : "s", r->values_num);
369     BAIL_OUT(-1);
370   }
371   /* }}} */
372
373   /* Allocate r->instances_pos, r->values_pos, r->metadata_post,
374    * r->instances_buffer, r->values_buffer, and r->metadata_buffer {{{ */
375   if (r->instances_num > 0) {
376     prep_area->instances_pos =
377         (size_t *)calloc(r->instances_num, sizeof(size_t));
378     if (prep_area->instances_pos == NULL) {
379       ERROR("db query utils: udb_result_prepare_result: calloc failed.");
380       BAIL_OUT(-ENOMEM);
381     }
382
383     prep_area->instances_buffer =
384         (char **)calloc(r->instances_num, sizeof(char *));
385     if (prep_area->instances_buffer == NULL) {
386       ERROR("db query utils: udb_result_prepare_result: calloc failed.");
387       BAIL_OUT(-ENOMEM);
388     }
389   } /* if (r->instances_num > 0) */
390
391   prep_area->values_pos = (size_t *)calloc(r->values_num, sizeof(size_t));
392   if (prep_area->values_pos == NULL) {
393     ERROR("db query utils: udb_result_prepare_result: calloc failed.");
394     BAIL_OUT(-ENOMEM);
395   }
396
397   prep_area->values_buffer = (char **)calloc(r->values_num, sizeof(char *));
398   if (prep_area->values_buffer == NULL) {
399     ERROR("db query utils: udb_result_prepare_result: calloc failed.");
400     BAIL_OUT(-ENOMEM);
401   }
402
403   prep_area->metadata_pos = (size_t *)calloc(r->metadata_num, sizeof(size_t));
404   if (prep_area->metadata_pos == NULL) {
405     ERROR("db query utils: udb_result_prepare_result: calloc failed.");
406     BAIL_OUT(-ENOMEM);
407   }
408
409   prep_area->metadata_buffer = (char **)calloc(r->metadata_num, sizeof(char *));
410   if (prep_area->metadata_buffer == NULL) {
411     ERROR("db query utils: udb_result_prepare_result: calloc failed.");
412     BAIL_OUT(-ENOMEM);
413   }
414
415   /* }}} */
416
417   /* Determine the position of the plugin instance column {{{ */
418   for (size_t i = 0; i < r->instances_num; i++) {
419     size_t j;
420
421     for (j = 0; j < column_num; j++) {
422       if (strcasecmp(r->instances[i], column_names[j]) == 0) {
423         prep_area->instances_pos[i] = j;
424         break;
425       }
426     }
427
428     if (j >= column_num) {
429       ERROR("db query utils: udb_result_prepare_result: "
430             "Column `%s' could not be found.",
431             r->instances[i]);
432       BAIL_OUT(-ENOENT);
433     }
434   } /* }}} for (i = 0; i < r->instances_num; i++) */
435
436   /* Determine the position of the value columns {{{ */
437   for (size_t i = 0; i < r->values_num; i++) {
438     size_t j;
439
440     for (j = 0; j < column_num; j++) {
441       if (strcasecmp(r->values[i], column_names[j]) == 0) {
442         prep_area->values_pos[i] = j;
443         break;
444       }
445     }
446
447     if (j >= column_num) {
448       ERROR("db query utils: udb_result_prepare_result: "
449             "Column `%s' could not be found.",
450             r->values[i]);
451       BAIL_OUT(-ENOENT);
452     }
453   } /* }}} for (i = 0; i < r->values_num; i++) */
454
455   /* Determine the position of the metadata columns {{{ */
456   for (size_t i = 0; i < r->metadata_num; i++) {
457     size_t j;
458
459     for (j = 0; j < column_num; j++) {
460       if (strcasecmp(r->metadata[i], column_names[j]) == 0) {
461         prep_area->metadata_pos[i] = j;
462         break;
463       }
464     }
465
466     if (j >= column_num) {
467       ERROR("db query utils: udb_result_prepare_result: "
468             "Metadata column `%s' could not be found.",
469             r->values[i]);
470       BAIL_OUT(-ENOENT);
471     }
472   } /* }}} for (i = 0; i < r->metadata_num; i++) */
473
474 #undef BAIL_OUT
475   return 0;
476 } /* }}} int udb_result_prepare_result */
477
478 static void udb_result_free(udb_result_t *r) /* {{{ */
479 {
480   if (r == NULL)
481     return;
482
483   sfree(r->type);
484   sfree(r->instance_prefix);
485
486   for (size_t i = 0; i < r->instances_num; i++)
487     sfree(r->instances[i]);
488   sfree(r->instances);
489
490   for (size_t i = 0; i < r->values_num; i++)
491     sfree(r->values[i]);
492   sfree(r->values);
493
494   for (size_t i = 0; i < r->metadata_num; i++)
495     sfree(r->metadata[i]);
496   sfree(r->metadata);
497
498   udb_result_free(r->next);
499
500   sfree(r);
501 } /* }}} void udb_result_free */
502
503 static int udb_result_create(const char *query_name, /* {{{ */
504                              udb_result_t **r_head, oconfig_item_t *ci) {
505   udb_result_t *r;
506   int status;
507
508   if (ci->values_num != 0) {
509     WARNING("db query utils: The `Result' block doesn't accept "
510             "any arguments. Ignoring %i argument%s.",
511             ci->values_num, (ci->values_num == 1) ? "" : "s");
512   }
513
514   r = calloc(1, sizeof(*r));
515   if (r == NULL) {
516     ERROR("db query utils: calloc failed.");
517     return -1;
518   }
519   r->type = NULL;
520   r->instance_prefix = NULL;
521   r->instances = NULL;
522   r->values = NULL;
523   r->metadata = NULL;
524   r->next = NULL;
525
526   /* Fill the `udb_result_t' structure.. */
527   status = 0;
528   for (int i = 0; i < ci->children_num; i++) {
529     oconfig_item_t *child = ci->children + i;
530
531     if (strcasecmp("Type", child->key) == 0)
532       status = udb_config_set_string(&r->type, child);
533     else if (strcasecmp("InstancePrefix", child->key) == 0)
534       status = udb_config_set_string(&r->instance_prefix, child);
535     else if (strcasecmp("InstancesFrom", child->key) == 0)
536       status = udb_config_add_string(&r->instances, &r->instances_num, child);
537     else if (strcasecmp("ValuesFrom", child->key) == 0)
538       status = udb_config_add_string(&r->values, &r->values_num, child);
539     else if (strcasecmp("MetadataFrom", child->key) == 0)
540       status = udb_config_add_string(&r->metadata, &r->metadata_num, child);
541     else {
542       WARNING("db query utils: Query `%s': Option `%s' not allowed here.",
543               query_name, child->key);
544       status = -1;
545     }
546
547     if (status != 0)
548       break;
549   }
550
551   /* Check that all necessary options have been given. */
552   while (status == 0) {
553     if (r->type == NULL) {
554       WARNING("db query utils: `Type' not given for "
555               "result in query `%s'",
556               query_name);
557       status = -1;
558     }
559     if (r->values == NULL) {
560       WARNING("db query utils: `ValuesFrom' not given for "
561               "result in query `%s'",
562               query_name);
563       status = -1;
564     }
565
566     break;
567   } /* while (status == 0) */
568
569   if (status != 0) {
570     udb_result_free(r);
571     return -1;
572   }
573
574   /* If all went well, add this result to the list of results. */
575   if (*r_head == NULL) {
576     *r_head = r;
577   } else {
578     udb_result_t *last;
579
580     last = *r_head;
581     while (last->next != NULL)
582       last = last->next;
583
584     last->next = r;
585   }
586
587   return 0;
588 } /* }}} int udb_result_create */
589
590 /*
591  * Query private functions
592  */
593 static void udb_query_free_one(udb_query_t *q) /* {{{ */
594 {
595   if (q == NULL)
596     return;
597
598   sfree(q->name);
599   sfree(q->statement);
600   sfree(q->plugin_instance_from);
601
602   udb_result_free(q->results);
603
604   sfree(q);
605 } /* }}} void udb_query_free_one */
606
607 /*
608  * Query public functions
609  */
610 int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
611                      size_t *ret_query_list_len, oconfig_item_t *ci,
612                      udb_query_create_callback_t cb) {
613   udb_query_t **query_list;
614   size_t query_list_len;
615
616   udb_query_t *q;
617   int status;
618
619   if ((ret_query_list == NULL) || (ret_query_list_len == NULL))
620     return -EINVAL;
621   query_list = *ret_query_list;
622   query_list_len = *ret_query_list_len;
623
624   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
625     WARNING("db query utils: The `Query' block "
626             "needs exactly one string argument.");
627     return -1;
628   }
629
630   q = calloc(1, sizeof(*q));
631   if (q == NULL) {
632     ERROR("db query utils: calloc failed.");
633     return -1;
634   }
635   q->min_version = 0;
636   q->max_version = UINT_MAX;
637   q->statement = NULL;
638   q->results = NULL;
639   q->plugin_instance_from = NULL;
640
641   status = udb_config_set_string(&q->name, ci);
642   if (status != 0) {
643     sfree(q);
644     return status;
645   }
646
647   /* Fill the `udb_query_t' structure.. */
648   for (int i = 0; i < ci->children_num; i++) {
649     oconfig_item_t *child = ci->children + i;
650
651     if (strcasecmp("Statement", child->key) == 0)
652       status = udb_config_set_string(&q->statement, child);
653     else if (strcasecmp("Result", child->key) == 0)
654       status = udb_result_create(q->name, &q->results, child);
655     else if (strcasecmp("MinVersion", child->key) == 0)
656       status = udb_config_set_uint(&q->min_version, child);
657     else if (strcasecmp("MaxVersion", child->key) == 0)
658       status = udb_config_set_uint(&q->max_version, child);
659     else if (strcasecmp("PluginInstanceFrom", child->key) == 0)
660       status = udb_config_set_string(&q->plugin_instance_from, child);
661
662     /* Call custom callbacks */
663     else if (cb != NULL) {
664       status = (*cb)(q, child);
665       if (status != 0) {
666         WARNING("db query utils: The configuration callback failed "
667                 "to handle `%s'.",
668                 child->key);
669       }
670     } else {
671       WARNING("db query utils: Query `%s': Option `%s' not allowed here.",
672               q->name, child->key);
673       status = -1;
674     }
675
676     if (status != 0)
677       break;
678   }
679
680   /* Check that all necessary options have been given. */
681   if (status == 0) {
682     if (q->statement == NULL) {
683       WARNING("db query utils: Query `%s': No `Statement' given.", q->name);
684       status = -1;
685     }
686     if (q->results == NULL) {
687       WARNING("db query utils: Query `%s': No (valid) `Result' block given.",
688               q->name);
689       status = -1;
690     }
691   } /* if (status == 0) */
692
693   /* If all went well, add this query to the list of queries within the
694    * database structure. */
695   if (status == 0) {
696     udb_query_t **temp;
697
698     temp = realloc(query_list, sizeof(*query_list) * (query_list_len + 1));
699     if (temp == NULL) {
700       ERROR("db query utils: realloc failed");
701       status = -1;
702     } else {
703       query_list = temp;
704       query_list[query_list_len] = q;
705       query_list_len++;
706     }
707   }
708
709   if (status != 0) {
710     udb_query_free_one(q);
711     return -1;
712   }
713
714   *ret_query_list = query_list;
715   *ret_query_list_len = query_list_len;
716
717   return 0;
718 } /* }}} int udb_query_create */
719
720 void udb_query_free(udb_query_t **query_list, size_t query_list_len) /* {{{ */
721 {
722   if (query_list == NULL)
723     return;
724
725   for (size_t i = 0; i < query_list_len; i++)
726     udb_query_free_one(query_list[i]);
727
728   sfree(query_list);
729 } /* }}} void udb_query_free */
730
731 int udb_query_pick_from_list_by_name(const char *name, /* {{{ */
732                                      udb_query_t **src_list,
733                                      size_t src_list_len,
734                                      udb_query_t ***dst_list,
735                                      size_t *dst_list_len) {
736   int num_added;
737
738   if ((name == NULL) || (src_list == NULL) || (dst_list == NULL) ||
739       (dst_list_len == NULL)) {
740     ERROR("db query utils: udb_query_pick_from_list_by_name: "
741           "Invalid argument.");
742     return -EINVAL;
743   }
744
745   num_added = 0;
746   for (size_t i = 0; i < src_list_len; i++) {
747     udb_query_t **tmp_list;
748     size_t tmp_list_len;
749
750     if (strcasecmp(name, src_list[i]->name) != 0)
751       continue;
752
753     tmp_list_len = *dst_list_len;
754     tmp_list = realloc(*dst_list, (tmp_list_len + 1) * sizeof(udb_query_t *));
755     if (tmp_list == NULL) {
756       ERROR("db query utils: realloc failed.");
757       return -ENOMEM;
758     }
759
760     tmp_list[tmp_list_len] = src_list[i];
761     tmp_list_len++;
762
763     *dst_list = tmp_list;
764     *dst_list_len = tmp_list_len;
765
766     num_added++;
767   } /* for (i = 0; i < src_list_len; i++) */
768
769   if (num_added <= 0) {
770     ERROR("db query utils: Cannot find query `%s'. Make sure the <Query> "
771           "block is above the database definition!",
772           name);
773     return -ENOENT;
774   } else {
775     DEBUG("db query utils: Added %i versions of query `%s'.", num_added, name);
776   }
777
778   return 0;
779 } /* }}} int udb_query_pick_from_list_by_name */
780
781 int udb_query_pick_from_list(oconfig_item_t *ci, /* {{{ */
782                              udb_query_t **src_list, size_t src_list_len,
783                              udb_query_t ***dst_list, size_t *dst_list_len) {
784   const char *name;
785
786   if ((ci == NULL) || (src_list == NULL) || (dst_list == NULL) ||
787       (dst_list_len == NULL)) {
788     ERROR("db query utils: udb_query_pick_from_list: "
789           "Invalid argument.");
790     return -EINVAL;
791   }
792
793   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
794     ERROR("db query utils: The `%s' config option "
795           "needs exactly one string argument.",
796           ci->key);
797     return -1;
798   }
799   name = ci->values[0].value.string;
800
801   return udb_query_pick_from_list_by_name(name, src_list, src_list_len,
802                                           dst_list, dst_list_len);
803 } /* }}} int udb_query_pick_from_list */
804
805 const char *udb_query_get_name(udb_query_t *q) /* {{{ */
806 {
807   if (q == NULL)
808     return NULL;
809
810   return q->name;
811 } /* }}} const char *udb_query_get_name */
812
813 const char *udb_query_get_statement(udb_query_t *q) /* {{{ */
814 {
815   if (q == NULL)
816     return NULL;
817
818   return q->statement;
819 } /* }}} const char *udb_query_get_statement */
820
821 void udb_query_set_user_data(udb_query_t *q, void *user_data) /* {{{ */
822 {
823   if (q == NULL)
824     return;
825
826   q->user_data = user_data;
827 } /* }}} void udb_query_set_user_data */
828
829 void *udb_query_get_user_data(udb_query_t *q) /* {{{ */
830 {
831   if (q == NULL)
832     return NULL;
833
834   return q->user_data;
835 } /* }}} void *udb_query_get_user_data */
836
837 int udb_query_check_version(udb_query_t *q, unsigned int version) /* {{{ */
838 {
839   if (q == NULL)
840     return -EINVAL;
841
842   if ((version < q->min_version) || (version > q->max_version))
843     return 0;
844
845   return 1;
846 } /* }}} int udb_query_check_version */
847
848 void udb_query_finish_result(udb_query_t const *q, /* {{{ */
849                              udb_query_preparation_area_t *prep_area) {
850   udb_result_preparation_area_t *r_area;
851   udb_result_t *r;
852
853   if ((q == NULL) || (prep_area == NULL))
854     return;
855
856   prep_area->column_num = 0;
857   sfree(prep_area->host);
858   sfree(prep_area->plugin);
859   sfree(prep_area->db_name);
860
861   prep_area->interval = 0;
862
863   for (r = q->results, r_area = prep_area->result_prep_areas; r != NULL;
864        r = r->next, r_area = r_area->next) {
865     /* this may happen during error conditions of the caller */
866     if (r_area == NULL)
867       break;
868     udb_result_finish_result(r, r_area);
869   }
870 } /* }}} void udb_query_finish_result */
871
872 int udb_query_handle_result(udb_query_t const *q, /* {{{ */
873                             udb_query_preparation_area_t *prep_area,
874                             char **column_values) {
875   udb_result_preparation_area_t *r_area;
876   udb_result_t *r;
877   int success;
878   int status;
879
880   if ((q == NULL) || (prep_area == NULL))
881     return -EINVAL;
882
883   if ((prep_area->column_num < 1) || (prep_area->host == NULL) ||
884       (prep_area->plugin == NULL) || (prep_area->db_name == NULL)) {
885     ERROR("db query utils: Query `%s': Query is not prepared; "
886           "can't handle result.",
887           q->name);
888     return -EINVAL;
889   }
890
891 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG /* {{{ */
892   do {
893     for (size_t i = 0; i < prep_area->column_num; i++) {
894       DEBUG("db query utils: udb_query_handle_result (%s, %s): "
895             "column[%zu] = %s;",
896             prep_area->db_name, q->name, i, column_values[i]);
897     }
898   } while (0);
899 #endif /* }}} */
900
901   success = 0;
902   for (r = q->results, r_area = prep_area->result_prep_areas; r != NULL;
903        r = r->next, r_area = r_area->next) {
904     status = udb_result_handle_result(r, prep_area, r_area, q, column_values);
905     if (status == 0)
906       success++;
907   }
908
909   if (success == 0) {
910     ERROR("db query utils: udb_query_handle_result (%s, %s): "
911           "All results failed.",
912           prep_area->db_name, q->name);
913     return -1;
914   }
915
916   return 0;
917 } /* }}} int udb_query_handle_result */
918
919 int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
920                              udb_query_preparation_area_t *prep_area,
921                              const char *host, const char *plugin,
922                              const char *db_name, char **column_names,
923                              size_t column_num, cdtime_t interval) {
924   udb_result_preparation_area_t *r_area;
925   udb_result_t *r;
926   int status;
927
928   if ((q == NULL) || (prep_area == NULL))
929     return -EINVAL;
930
931   udb_query_finish_result(q, prep_area);
932
933   prep_area->column_num = column_num;
934   prep_area->host = strdup(host);
935   prep_area->plugin = strdup(plugin);
936   prep_area->db_name = strdup(db_name);
937
938   prep_area->interval = interval;
939
940   if ((prep_area->host == NULL) || (prep_area->plugin == NULL) ||
941       (prep_area->db_name == NULL)) {
942     ERROR("db query utils: Query `%s': Prepare failed: Out of memory.",
943           q->name);
944     udb_query_finish_result(q, prep_area);
945     return -ENOMEM;
946   }
947
948 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG
949   do {
950     for (size_t i = 0; i < column_num; i++) {
951       DEBUG("db query utils: udb_query_prepare_result: "
952             "query = %s; column[%zu] = %s;",
953             q->name, i, column_names[i]);
954     }
955   } while (0);
956 #endif
957
958   /* Determine the position of the PluginInstance column {{{ */
959   if (q->plugin_instance_from != NULL) {
960     size_t i;
961
962     for (i = 0; i < column_num; i++) {
963       if (strcasecmp(q->plugin_instance_from, column_names[i]) == 0) {
964         prep_area->plugin_instance_pos = i;
965         break;
966       }
967     }
968
969     if (i >= column_num) {
970       ERROR("db query utils: udb_query_prepare_result: "
971             "Column `%s' from `PluginInstanceFrom' could not be found.",
972             q->plugin_instance_from);
973       udb_query_finish_result(q, prep_area);
974       return -ENOENT;
975     }
976   }
977   /* }}} */
978
979   for (r = q->results, r_area = prep_area->result_prep_areas; r != NULL;
980        r = r->next, r_area = r_area->next) {
981     if (!r_area) {
982       ERROR("db query utils: Query `%s': Invalid number of result "
983             "preparation areas.",
984             q->name);
985       udb_query_finish_result(q, prep_area);
986       return -EINVAL;
987     }
988
989     status = udb_result_prepare_result(r, r_area, column_names, column_num);
990     if (status != 0) {
991       udb_query_finish_result(q, prep_area);
992       return status;
993     }
994   }
995
996   return 0;
997 } /* }}} int udb_query_prepare_result */
998
999 udb_query_preparation_area_t *
1000 udb_query_allocate_preparation_area(udb_query_t *q) /* {{{ */
1001 {
1002   udb_query_preparation_area_t *q_area;
1003   udb_result_preparation_area_t **next_r_area;
1004   udb_result_t *r;
1005
1006   q_area = calloc(1, sizeof(*q_area));
1007   if (q_area == NULL)
1008     return NULL;
1009
1010   next_r_area = &q_area->result_prep_areas;
1011   for (r = q->results; r != NULL; r = r->next) {
1012     udb_result_preparation_area_t *r_area;
1013
1014     r_area = calloc(1, sizeof(*r_area));
1015     if (r_area == NULL) {
1016       udb_result_preparation_area_t *a = q_area->result_prep_areas;
1017
1018       while (a != NULL) {
1019         udb_result_preparation_area_t *next = a->next;
1020         sfree(a);
1021         a = next;
1022       }
1023
1024       free(q_area);
1025       return NULL;
1026     }
1027
1028     *next_r_area = r_area;
1029     next_r_area = &r_area->next;
1030   }
1031
1032   return q_area;
1033 } /* }}} udb_query_preparation_area_t *udb_query_allocate_preparation_area */
1034
1035 void udb_query_delete_preparation_area(
1036     udb_query_preparation_area_t *q_area) /* {{{ */
1037 {
1038   udb_result_preparation_area_t *r_area;
1039
1040   if (q_area == NULL)
1041     return;
1042
1043   r_area = q_area->result_prep_areas;
1044   while (r_area != NULL) {
1045     udb_result_preparation_area_t *area = r_area;
1046
1047     r_area = r_area->next;
1048
1049     sfree(area->instances_pos);
1050     sfree(area->values_pos);
1051     sfree(area->instances_buffer);
1052     sfree(area->values_buffer);
1053     free(area);
1054   }
1055
1056   sfree(q_area->host);
1057   sfree(q_area->plugin);
1058   sfree(q_area->db_name);
1059
1060   free(q_area);
1061 } /* }}} void udb_query_delete_preparation_area */