27e8a55f9a94aeeb88758bacd007fb5bd8fe97eb
[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   udb_result_t *next;
50 }; /* }}} */
51
52 struct udb_query_s /* {{{ */
53 {
54   char *name;
55   char *statement;
56   void *user_data;
57
58   /* Preparation area */
59   size_t column_num;
60   char *host;
61   char *plugin;
62   char *db_name;
63
64   udb_result_t *results;
65 }; /* }}} */
66
67 /*
68  * Config Private functions
69  */
70 static int udb_config_set_string (char **ret_string, /* {{{ */
71     oconfig_item_t *ci)
72 {
73   char *string;
74
75   if ((ci->values_num != 1)
76       || (ci->values[0].type != OCONFIG_TYPE_STRING))
77   {
78     WARNING ("db query utils: The `%s' config option "
79         "needs exactly one string argument.", ci->key);
80     return (-1);
81   }
82
83   string = strdup (ci->values[0].value.string);
84   if (string == NULL)
85   {
86     ERROR ("db query utils: strdup failed.");
87     return (-1);
88   }
89
90   if (*ret_string != NULL)
91     free (*ret_string);
92   *ret_string = string;
93
94   return (0);
95 } /* }}} int udb_config_set_string */
96
97 static int udb_config_add_string (char ***ret_array, /* {{{ */
98     size_t *ret_array_len, oconfig_item_t *ci)
99 {
100   char **array;
101   size_t array_len;
102   int i;
103
104   if (ci->values_num < 1)
105   {
106     WARNING ("db query utils: The `%s' config option "
107         "needs at least one argument.", ci->key);
108     return (-1);
109   }
110
111   for (i = 0; i < ci->values_num; i++)
112   {
113     if (ci->values[i].type != OCONFIG_TYPE_STRING)
114     {
115       WARNING ("db query utils: Argument %i to the `%s' option "
116           "is not a string.", i + 1, ci->key);
117       return (-1);
118     }
119   }
120
121   array_len = *ret_array_len;
122   array = (char **) realloc (*ret_array,
123       sizeof (char *) * (array_len + ci->values_num));
124   if (array == NULL)
125   {
126     ERROR ("db query utils: realloc failed.");
127     return (-1);
128   }
129   *ret_array = array;
130
131   for (i = 0; i < ci->values_num; i++)
132   {
133     array[array_len] = strdup (ci->values[i].value.string);
134     if (array[array_len] == NULL)
135     {
136       ERROR ("db query utils: strdup failed.");
137       *ret_array_len = array_len;
138       return (-1);
139     }
140     array_len++;
141   }
142
143   *ret_array_len = array_len;
144   return (0);
145 } /* }}} int udb_config_add_string */
146
147 /*
148  * Result private functions
149  */
150 static void udb_result_submit (udb_result_t *r, udb_query_t *q) /* {{{ */
151 {
152   value_list_t vl = VALUE_LIST_INIT;
153   size_t i;
154
155   assert (((size_t) r->ds->ds_num) == r->values_num);
156
157   DEBUG ("db query utils: udb_result_submit: r->instance_prefix = %s;",
158       (r->instance_prefix == NULL) ? "NULL" : r->instance_prefix);
159   for (i = 0; i < r->instances_num; i++)
160   {
161     DEBUG ("db query utils: udb_result_submit: r->instances_buffer[%zu] = %s;",
162         i, r->instances_buffer[i]);
163   }
164
165   vl.values = (value_t *) calloc (r->ds->ds_num, sizeof (value_t));
166   if (vl.values == NULL)
167   {
168     ERROR ("db query utils: malloc failed.");
169     return;
170   }
171   vl.values_len = r->ds->ds_num;
172
173   for (i = 0; i < r->values_num; i++)
174   {
175     char *endptr;
176
177     endptr = NULL;
178     errno = 0;
179     if (r->ds->ds[i].type == DS_TYPE_COUNTER)
180       vl.values[i].counter = (counter_t) strtoll (r->values_buffer[i],
181           &endptr, /* base = */ 0);
182     else if (r->ds->ds[i].type == DS_TYPE_GAUGE)
183       vl.values[i].gauge = (gauge_t) strtod (r->values_buffer[i], &endptr);
184     else
185       errno = EINVAL;
186
187     if ((endptr == r->values_buffer[i]) || (errno != 0))
188     {
189       WARNING ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
190           r->values_buffer[i],
191           (r->ds->ds[i].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
192       vl.values[i].gauge = NAN;
193     }
194   }
195
196   vl.time = time (NULL);
197   sstrncpy (vl.host, q->host, sizeof (vl.host));
198   sstrncpy (vl.plugin, q->plugin, sizeof (vl.plugin));
199   sstrncpy (vl.plugin_instance, q->db_name, sizeof (vl.type_instance));
200   sstrncpy (vl.type, r->type, sizeof (vl.type));
201
202   if (r->instance_prefix == NULL)
203   {
204     strjoin (vl.type_instance, sizeof (vl.type_instance),
205         r->instances_buffer, r->instances_num, "-");
206   }
207   else
208   {
209     char tmp[DATA_MAX_NAME_LEN];
210
211     strjoin (tmp, sizeof (tmp), r->instances_buffer, r->instances_num, "-");
212     tmp[sizeof (tmp) - 1] = 0;
213
214     snprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s",
215         r->instance_prefix, tmp);
216   }
217   vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
218
219   plugin_dispatch_values (&vl);
220
221   sfree (vl.values);
222 } /* }}} void udb_result_submit */
223
224 static void udb_result_finish_result (udb_result_t *r) /* {{{ */
225 {
226   if (r == NULL)
227     return;
228
229   r->ds = NULL;
230   sfree (r->instances_pos);
231   sfree (r->values_pos);
232   sfree (r->instances_buffer);
233   sfree (r->values_buffer);
234 } /* }}} void udb_result_finish_result */
235
236 static int udb_result_handle_result (udb_result_t *r, /* {{{ */
237     udb_query_t *q, char **column_values)
238 {
239   size_t i;
240
241   for (i = 0; i < r->instances_num; i++)
242     r->instances_buffer[i] = column_values[r->instances_pos[i]];
243
244   for (i = 0; i < r->values_num; i++)
245     r->values_buffer[i] = column_values[r->values_pos[i]];
246
247   udb_result_submit (r, q);
248
249   return (0);
250 } /* }}} int udb_result_handle_result */
251
252 static int udb_result_prepare_result (udb_result_t *r, /* {{{ */
253     char **column_names, size_t column_num)
254 {
255   size_t i;
256
257   if (r == NULL)
258     return (-EINVAL);
259
260 #define BAIL_OUT(status) \
261   r->ds = NULL; \
262   sfree (r->instances_pos); \
263   sfree (r->values_pos); \
264   sfree (r->instances_buffer); \
265   sfree (r->values_buffer); \
266   return (status)
267
268   /* Make sure previous preparations are cleaned up. */
269   udb_result_finish_result (r);
270   r->instances_pos = NULL;
271   r->values_pos = NULL;
272
273   /* Read `ds' and check number of values {{{ */
274   r->ds = plugin_get_ds (r->type);
275   if (r->ds == NULL)
276   {
277     ERROR ("db query utils: udb_result_prepare_result: Type `%s' is not "
278         "known by the daemon. See types.db(5) for details.",
279         r->type);
280     BAIL_OUT (-1);
281   }
282
283   if (((size_t) r->ds->ds_num) != r->values_num)
284   {
285     ERROR ("db query utils: udb_result_prepare_result: The type `%s' "
286         "requires exactly %i value%s, but the configuration specifies %zu.",
287         r->type,
288         r->ds->ds_num, (r->ds->ds_num == 1) ? "" : "s",
289         r->values_num);
290     BAIL_OUT (-1);
291   }
292   /* }}} */
293
294   /* Allocate r->instances_pos, r->values_pos, r->instances_buffer, and
295    * r->values_buffer {{{ */
296   r->instances_pos = (size_t *) calloc (r->instances_num, sizeof (size_t));
297   if (r->instances_pos == NULL)
298   {
299     ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
300     BAIL_OUT (-ENOMEM);
301   }
302
303   r->values_pos = (size_t *) calloc (r->values_num, sizeof (size_t));
304   if (r->values_pos == NULL)
305   {
306     ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
307     BAIL_OUT (-ENOMEM);
308   }
309
310   r->instances_buffer = (char **) calloc (r->instances_num, sizeof (char *));
311   if (r->instances_buffer == NULL)
312   {
313     ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
314     BAIL_OUT (-ENOMEM);
315   }
316
317   r->values_buffer = (char **) calloc (r->values_num, sizeof (char *));
318   if (r->values_buffer == NULL)
319   {
320     ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
321     BAIL_OUT (-ENOMEM);
322   }
323   /* }}} */
324
325   /* Determine the position of the instance columns {{{ */
326   for (i = 0; i < r->instances_num; i++)
327   {
328     size_t j;
329
330     for (j = 0; j < column_num; j++)
331     {
332       if (strcasecmp (r->instances[i], column_names[j]) == 0)
333       {
334         r->instances_pos[i] = j;
335         break;
336       }
337     }
338
339     if (j >= column_num)
340     {
341       ERROR ("db query utils: udb_result_prepare_result: "
342           "Column `%s' could not be found.",
343           r->instances[i]);
344       BAIL_OUT (-ENOENT);
345     }
346   } /* }}} for (i = 0; i < r->instances_num; i++) */
347
348   /* Determine the position of the value columns {{{ */
349   for (i = 0; i < r->values_num; i++)
350   {
351     size_t j;
352
353     for (j = 0; j < column_num; j++)
354     {
355       if (strcasecmp (r->values[i], column_names[j]) == 0)
356       {
357         r->values_pos[i] = j;
358         break;
359       }
360     }
361
362     if (j >= column_num)
363     {
364       ERROR ("db query utils: udb_result_prepare_result: "
365           "Column `%s' could not be found.",
366           r->values[i]);
367       BAIL_OUT (-ENOENT);
368     }
369   } /* }}} for (i = 0; i < r->values_num; i++) */
370
371 #undef BAIL_OUT
372   return (0);
373 } /* }}} int udb_result_prepare_result */
374
375 static void udb_result_free (udb_result_t *r) /* {{{ */
376 {
377   size_t i;
378
379   if (r == NULL)
380     return;
381
382   sfree (r->type);
383
384   for (i = 0; i < r->instances_num; i++)
385     sfree (r->instances[i]);
386   sfree (r->instances);
387
388   for (i = 0; i < r->values_num; i++)
389     sfree (r->values[i]);
390   sfree (r->values);
391
392   udb_result_free (r->next);
393
394   sfree (r);
395 } /* }}} void udb_result_free */
396
397 static int udb_result_create (const char *query_name, /* {{{ */
398     udb_result_t **r_head, oconfig_item_t *ci)
399 {
400   udb_result_t *r;
401   int status;
402   int i;
403
404   if (ci->values_num != 0)
405   {
406     WARNING ("db query utils: The `Result' block doesn't accept "
407         "any arguments. Ignoring %i argument%s.",
408         ci->values_num, (ci->values_num == 1) ? "" : "s");
409   }
410
411   r = (udb_result_t *) malloc (sizeof (*r));
412   if (r == NULL)
413   {
414     ERROR ("db query utils: malloc failed.");
415     return (-1);
416   }
417   memset (r, 0, sizeof (*r));
418   r->type = NULL;
419   r->instance_prefix = NULL;
420   r->instances = NULL;
421   r->values = NULL;
422   r->next = NULL;
423
424   /* Fill the `udb_result_t' structure.. */
425   status = 0;
426   for (i = 0; i < ci->children_num; i++)
427   {
428     oconfig_item_t *child = ci->children + i;
429
430     if (strcasecmp ("Type", child->key) == 0)
431       status = udb_config_set_string (&r->type, child);
432     else if (strcasecmp ("InstancePrefix", child->key) == 0)
433       status = udb_config_set_string (&r->instance_prefix, child);
434     else if (strcasecmp ("InstancesFrom", child->key) == 0)
435       status = udb_config_add_string (&r->instances, &r->instances_num, child);
436     else if (strcasecmp ("ValuesFrom", child->key) == 0)
437       status = udb_config_add_string (&r->values, &r->values_num, child);
438     else
439     {
440       WARNING ("db query utils: Query `%s': Option `%s' not allowed here.",
441           query_name, child->key);
442       status = -1;
443     }
444
445     if (status != 0)
446       break;
447   }
448
449   /* Check that all necessary options have been given. */
450   while (status == 0)
451   {
452     if (r->type == NULL)
453     {
454       WARNING ("db query utils: `Type' not given for "
455           "result in query `%s'", query_name);
456       status = -1;
457     }
458     if (r->instances == NULL)
459     {
460       WARNING ("db query utils: `InstancesFrom' not given for "
461           "result in query `%s'", query_name);
462       status = -1;
463     }
464     if (r->values == NULL)
465     {
466       WARNING ("db query utils: `ValuesFrom' not given for "
467           "result in query `%s'", query_name);
468       status = -1;
469     }
470
471     break;
472   } /* while (status == 0) */
473
474   if (status != 0)
475   {
476     udb_result_free (r);
477     return (-1);
478   }
479
480   /* If all went well, add this result to the list of results. */
481   if (*r_head == NULL)
482   {
483     *r_head = r;
484   }
485   else
486   {
487     udb_result_t *last;
488
489     last = *r_head;
490     while (last->next != NULL)
491       last = last->next;
492
493     last->next = r;
494   }
495
496   return (0);
497 } /* }}} int udb_result_create */
498
499 /*
500  * Query private functions
501  */
502 void udb_query_free_one (udb_query_t *q) /* {{{ */
503 {
504   if (q == NULL)
505     return;
506
507   sfree (q->name);
508   sfree (q->statement);
509
510   udb_result_free (q->results);
511
512   sfree (q);
513 } /* }}} void udb_query_free_one */
514
515 /*
516  * Query public functions
517  */
518 int udb_query_create (udb_query_t ***ret_query_list, /* {{{ */
519     size_t *ret_query_list_len, oconfig_item_t *ci)
520 {
521   udb_query_t **query_list;
522   size_t        query_list_len;
523
524   udb_query_t *q;
525   int status;
526   int i;
527
528   if ((ret_query_list == NULL) || (ret_query_list_len == NULL))
529     return (-EINVAL);
530   query_list     = *ret_query_list;
531   query_list_len = *ret_query_list_len;
532
533   if ((ci->values_num != 1)
534       || (ci->values[0].type != OCONFIG_TYPE_STRING))
535   {
536     WARNING ("db query utils: The `Query' block "
537         "needs exactly one string argument.");
538     return (-1);
539   }
540
541   q = (udb_query_t *) malloc (sizeof (*q));
542   if (q == NULL)
543   {
544     ERROR ("db query utils: malloc failed.");
545     return (-1);
546   }
547   memset (q, 0, sizeof (*q));
548
549   status = udb_config_set_string (&q->name, ci);
550   if (status != 0)
551   {
552     sfree (q);
553     return (status);
554   }
555
556   /* Fill the `udb_query_t' structure.. */
557   for (i = 0; i < ci->children_num; i++)
558   {
559     oconfig_item_t *child = ci->children + i;
560
561     if (strcasecmp ("Statement", child->key) == 0)
562       status = udb_config_set_string (&q->statement, child);
563     else if (strcasecmp ("Result", child->key) == 0)
564       status = udb_result_create (q->name, &q->results, child);
565     else
566     {
567       WARNING ("db query utils: Query `%s': Option `%s' not allowed here.",
568           q->name, child->key);
569       status = -1;
570     }
571
572     if (status != 0)
573       break;
574   }
575
576   /* Check that all necessary options have been given. */
577   if (status == 0)
578   {
579     if (q->statement == NULL)
580     {
581       WARNING ("db query utils: Query `%s': No `Statement' given.", q->name);
582       status = -1;
583     }
584     if (q->results == NULL)
585     {
586       WARNING ("db query utils: Query `%s': No (valid) `Result' block given.",
587           q->name);
588       status = -1;
589     }
590   } /* if (status == 0) */
591
592   /* If all went well, add this query to the list of queries within the
593    * database structure. */
594   if (status == 0)
595   {
596     udb_query_t **temp;
597
598     temp = (udb_query_t **) realloc (query_list,
599         sizeof (*query_list) * (query_list_len + 1));
600     if (temp == NULL)
601     {
602       ERROR ("db query utils: realloc failed");
603       status = -1;
604     }
605     else
606     {
607       query_list = temp;
608       query_list[query_list_len] = q;
609       query_list_len++;
610     }
611   }
612
613   if (status != 0)
614   {
615     udb_query_free_one (q);
616     return (-1);
617   }
618
619   *ret_query_list     = query_list;
620   *ret_query_list_len = query_list_len;
621
622   return (0);
623 } /* }}} int udb_query_create */
624
625 void udb_query_free (udb_query_t **query_list, size_t query_list_len) /* {{{ */
626 {
627   size_t i;
628
629   if (query_list == NULL)
630     return;
631
632   for (i = 0; i < query_list_len; i++)
633     udb_query_free_one (query_list[i]);
634
635   sfree (query_list);
636 } /* }}} void udb_query_free */
637
638 int udb_query_pick_from_list (oconfig_item_t *ci, /* {{{ */
639     udb_query_t **src_list, size_t src_list_len,
640     udb_query_t ***dst_list, size_t *dst_list_len)
641 {
642   const char *name;
643   udb_query_t *q;
644   udb_query_t **tmp_list;
645   size_t tmp_list_len;
646   size_t i;
647
648   if ((ci == NULL) || (src_list == NULL) || (dst_list == NULL)
649       || (dst_list_len == NULL))
650   {
651     ERROR ("db query utils: Invalid argument.");
652     return (-EINVAL);
653   }
654
655   if ((ci->values_num != 1)
656       || (ci->values[0].type != OCONFIG_TYPE_STRING))
657   {
658     ERROR ("db query utils: The `%s' config option "
659         "needs exactly one string argument.", ci->key);
660     return (-1);
661   }
662   name = ci->values[0].value.string;
663
664   q = NULL;
665   for (i = 0; i < src_list_len; i++)
666     if (strcasecmp (name, src_list[i]->name) == 0)
667     {
668       q = src_list[i];
669       break;
670     }
671
672   if (q == NULL)
673   {
674     ERROR ("db query utils: Cannot find query `%s'. Make sure the <%s> "
675         "block is above the database definition!",
676         name, ci->key);
677     return (-ENOENT);
678   }
679
680   tmp_list_len = *dst_list_len;
681   tmp_list = (udb_query_t **) realloc (*dst_list, (tmp_list_len + 1)
682       * sizeof (udb_query_t *));
683   if (tmp_list == NULL)
684   {
685     ERROR ("db query utils: realloc failed.");
686     return (-ENOMEM);
687   }
688   tmp_list[tmp_list_len] = q;
689   tmp_list_len++;
690
691   *dst_list = tmp_list;
692   *dst_list_len = tmp_list_len;
693
694   return (0);
695 } /* }}} int udb_query_pick_from_list */
696
697 const char *udb_query_get_name (udb_query_t *q) /* {{{ */
698 {
699   if (q == NULL)
700     return (NULL);
701
702   return (q->name);
703 } /* }}} const char *udb_query_get_name */
704
705 const char *udb_query_get_statement (udb_query_t *q) /* {{{ */
706 {
707   if (q == NULL)
708     return (NULL);
709
710   return (q->statement);
711 } /* }}} const char *udb_query_get_statement */
712
713 void udb_query_set_user_data (udb_query_t *q, void *user_data) /* {{{ */
714 {
715   if (q == NULL)
716     return;
717
718   q->user_data = user_data;
719 } /* }}} void udb_query_set_user_data */
720
721 void *udb_query_get_user_data (udb_query_t *q) /* {{{ */
722 {
723   if (q == NULL)
724     return (NULL);
725
726   return (q->user_data);
727 } /* }}} void *udb_query_get_user_data */
728
729 void udb_query_finish_result (udb_query_t *q) /* {{{ */
730 {
731   udb_result_t *r;
732
733   if (q == NULL)
734     return;
735
736   q->column_num = 0;
737   sfree (q->host);
738   sfree (q->plugin);
739   sfree (q->db_name);
740
741   for (r = q->results; r != NULL; r = r->next)
742     udb_result_finish_result (r);
743 } /* }}} void udb_query_finish_result */
744
745 int udb_query_handle_result (udb_query_t *q, char **column_values) /* {{{ */
746 {
747   udb_result_t *r;
748   int success;
749   int status;
750
751   if (q == NULL)
752     return (-EINVAL);
753
754   if ((q->column_num < 1) || (q->host == NULL) || (q->plugin == NULL)
755       || (q->db_name == NULL))
756   {
757     ERROR ("db query utils: Query `%s': Query is not prepared; "
758         "can't handle result.", q->name);
759     return (-EINVAL);
760   }
761
762 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG /* {{{ */
763   do
764   {
765     size_t i;
766
767     for (i = 0; i < q->column_num; i++)
768     {
769       DEBUG ("db query utils: udb_query_handle_result (%s, %s): "
770           "column[%zu] = %s;",
771           q->db_name, q->name, i, column_values[i]);
772     }
773   } while (0);
774 #endif /* }}} */
775
776   success = 0;
777   for (r = q->results; r != NULL; r = r->next)
778   {
779     status = udb_result_handle_result (r, q, column_values);
780     if (status == 0)
781       success++;
782   }
783
784   if (success == 0)
785   {
786     ERROR ("db query utils: udb_query_handle_result (%s, %s): "
787         "All results failed.", q->db_name, q->name);
788     return (-1);
789   }
790
791   return (0);
792 } /* }}} int udb_query_handle_result */
793
794 int udb_query_prepare_result (udb_query_t *q, /* {{{ */
795     const char *host, const char *plugin, const char *db_name,
796     char **column_names, size_t column_num)
797 {
798   udb_result_t *r;
799   int status;
800
801   if (q == NULL)
802     return (-EINVAL);
803
804   udb_query_finish_result (q);
805
806   q->column_num = column_num;
807   q->host = strdup (host);
808   q->plugin = strdup (plugin);
809   q->db_name = strdup (db_name);
810
811   if ((q->host == NULL) || (q->plugin == NULL) || (q->db_name == NULL))
812   {
813     ERROR ("db query utils: Query `%s': Prepare failed: Out of memory.", q->name);
814     udb_query_finish_result (q);
815     return (-ENOMEM);
816   }
817
818 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG
819   do
820   {
821     size_t i;
822
823     for (i = 0; i < column_num; i++)
824     {
825       DEBUG ("db query utils: udb_query_prepare_result: "
826           "query = %s; column[%zu] = %s;",
827           q->name, i, column_names[i]);
828     }
829   } while (0);
830 #endif
831
832   for (r = q->results; r != NULL; r = r->next)
833   {
834     status = udb_result_prepare_result (r, column_names, column_num);
835     if (status != 0)
836     {
837       udb_query_finish_result (q);
838       return (status);
839     }
840   }
841
842   return (0);
843 } /* }}} int udb_query_prepare_result */
844
845 /* vim: set sw=2 sts=2 et fdm=marker : */