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