oracle plugin: Add <Result> blocks.
[collectd.git] / src / oracle.c
1 /**
2  * collectd - src/oracle.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  * Linking src/oracle.c ("the oracle plugin") statically or dynamically with
19  * other modules is making a combined work based on the oracle plugin. Thus,
20  * the terms and conditions of the GNU General Public License cover the whole
21  * combination.
22  *
23  * In addition, as a special exception, the copyright holders of the oracle
24  * plugin give you permission to combine the oracle plugin with free software
25  * programs or libraries that are released under the GNU LGPL and with code
26  * included in the standard release of the Oracle® Call Interface (OCI) under
27  * the Oracle® Technology Network (OTN) License (or modified versions of such
28  * code, with unchanged license). You may copy and distribute such a system
29  * following the terms of the GNU GPL for the oracle plugin and the licenses of
30  * the other code concerned.
31  *
32  * Note that people who make modified versions of the oracle plugin are not
33  * obligated to grant this special exception for their modified versions; it is
34  * their choice whether to do so. The GNU General Public License gives
35  * permission to release a modified version without this exception; this
36  * exception also makes it possible to release a modified version which carries
37  * forward this exception. However, without this exception the OTN License does
38  * not allow linking with code licensed under the GNU General Public License.
39  *
40  * Oracle® is a registered trademark of Oracle Corporation and/or its
41  * affiliates. Other names may be trademarks of their respective owners.
42  *
43  * Authors:
44  *   Florian octo Forster <octo at noris.net>
45  **/
46
47 #include "collectd.h"
48 #include "common.h"
49 #include "plugin.h"
50
51 #include <oci.h>
52
53 /*
54  * Data types
55  */
56 struct o_result_s;
57 typedef struct o_result_s o_result_t;
58 struct o_result_s
59 {
60   char    *type;
61   char   **instances;
62   size_t   instances_num;
63   char   **values;
64   size_t   values_num;
65
66   o_result_t *next;
67 };
68
69 struct o_query_s
70 {
71   char    *name;
72   char    *statement;
73   OCIStmt *oci_statement;
74
75   o_result_t *results;
76 };
77 typedef struct o_query_s o_query_t;
78
79 struct o_database_s
80 {
81   char *name;
82   char *connect_id;
83   char *username;
84   char *password;
85
86   o_query_t **queries;
87   size_t      queries_num;
88
89   OCISvcCtx *oci_service_context;
90 };
91 typedef struct o_database_s o_database_t;
92
93 /*
94  * Global variables
95  */
96 static o_query_t    **queries       = NULL;
97 static size_t         queries_num   = 0;
98 static o_database_t **databases     = NULL;
99 static size_t         databases_num = 0;
100
101 OCIEnv   *oci_env = NULL;
102 OCIError *oci_error = NULL;
103
104 /*
105  * Functions
106  */
107 static void o_report_error (const char *where, /* {{{ */
108     const char *what, OCIError *eh)
109 {
110   char buffer[2048];
111   sb4 error_code;
112   int status;
113
114   status = OCIErrorGet (eh, /* record number = */ 1,
115       /* sqlstate = */ NULL,
116       &error_code,
117       (text *) &buffer[0],
118       (ub4) sizeof (buffer),
119       OCI_HTYPE_ERROR);
120   buffer[sizeof (buffer) - 1] = 0;
121
122   if (status == OCI_SUCCESS)
123   {
124     size_t buffer_length;
125
126     buffer_length = strlen (buffer);
127     while ((buffer_length > 0) && (buffer[buffer_length - 1] < 32))
128     {
129       buffer_length--;
130       buffer[buffer_length] = 0;
131     }
132
133     ERROR ("oracle plugin: %s: %s failed: %s",
134         where, what, buffer);
135   }
136   else
137   {
138     ERROR ("oracle plugin: %s: %s failed. Additionally, OCIErrorGet failed with status %i.",
139         where, what, status);
140   }
141 } /* }}} void o_report_error */
142
143 static void o_result_free (o_result_t *r) /* {{{ */
144 {
145   size_t i;
146
147   if (r == NULL)
148     return;
149
150   sfree (r->type);
151
152   for (i = 0; i < r->instances_num; i++)
153     sfree (r->instances[i]);
154   sfree (r->instances);
155
156   for (i = 0; i < r->values_num; i++)
157     sfree (r->values[i]);
158   sfree (r->values);
159
160   o_result_free (r->next);
161
162   sfree (r);
163 } /* }}} void o_result_free */
164
165 static void o_query_free (o_query_t *q) /* {{{ */
166 {
167   if (q == NULL)
168     return;
169
170   sfree (q->name);
171   sfree (q->statement);
172
173   o_result_free (q->results);
174
175   sfree (q);
176 } /* }}} void o_query_free */
177
178 static void o_database_free (o_database_t *db) /* {{{ */
179 {
180   if (db == NULL)
181     return;
182
183   sfree (db->name);
184   sfree (db->connect_id);
185   sfree (db->username);
186   sfree (db->password);
187   sfree (db->queries);
188
189   sfree (db);
190 } /* }}} void o_database_free */
191
192 /* Configuration handling functions {{{
193  *
194  * <Plugin oracle>
195  *   <Query "plugin_instance0">
196  *     Statement "SELECT name, value FROM table"
197  *     <Result>
198  *       Type "gauge"
199  *       InstancesFrom "name"
200  *       ValuesFrom "value"
201  *     </Result>
202  *   </Query>
203  *     
204  *   <Database "plugin_instance1">
205  *     ConnectID "db01"
206  *     Username "oracle"
207  *     Password "secret"
208  *     Query "plugin_instance0"
209  *   </Database>
210  * </Plugin>
211  */
212
213 static int o_config_set_string (char **ret_string, /* {{{ */
214     oconfig_item_t *ci)
215 {
216   char *string;
217
218   if ((ci->values_num != 1)
219       || (ci->values[0].type != OCONFIG_TYPE_STRING))
220   {
221     WARNING ("oracle plugin: The `%s' config option "
222         "needs exactly one string argument.", ci->key);
223     return (-1);
224   }
225
226   string = strdup (ci->values[0].value.string);
227   if (string == NULL)
228   {
229     ERROR ("oracle plugin: strdup failed.");
230     return (-1);
231   }
232
233   if (*ret_string != NULL)
234     free (*ret_string);
235   *ret_string = string;
236
237   return (0);
238 } /* }}} int o_config_set_string */
239
240 static int o_config_add_string (char ***ret_array, /* {{{ */
241     size_t *ret_array_len, oconfig_item_t *ci)
242 {
243   char **array;
244   size_t array_len;
245   int i;
246
247   if (ci->values_num < 1)
248   {
249     WARNING ("oracle plugin: The `%s' config option "
250         "needs at least one argument.", ci->key);
251     return (-1);
252   }
253
254   for (i = 0; i < ci->values_num; i++)
255   {
256     if (ci->values[i].type != OCONFIG_TYPE_STRING)
257     {
258       WARNING ("oracle plugin: Argument %i to the `%s' option "
259           "is not a string.", i + 1, ci->key);
260       return (-1);
261     }
262   }
263
264   array_len = *ret_array_len;
265   array = (char **) realloc (*ret_array,
266       sizeof (char *) * (array_len + ci->values_num));
267   if (array == NULL)
268   {
269     ERROR ("oracle plugin: realloc failed.");
270     return (-1);
271   }
272   *ret_array = array;
273
274   for (i = 0; i < ci->values_num; i++)
275   {
276     array[array_len] = strdup (ci->values[i].value.string);
277     if (array[array_len] == NULL)
278     {
279       ERROR ("oracle plugin: strdup failed.");
280       *ret_array_len = array_len;
281       return (-1);
282     }
283     array_len++;
284   }
285
286   *ret_array_len = array_len;
287   return (0);
288 } /* }}} int o_config_add_string */
289
290 static int o_config_add_query_result (o_query_t *q, /* {{{ */
291     oconfig_item_t *ci)
292 {
293   o_result_t *r;
294   int status;
295   int i;
296
297   if (ci->values_num != 0)
298   {
299     WARNING ("oracle plugin: The `Result' block doesn't accept any arguments. "
300         "Ignoring %i argument%s.",
301         ci->values_num, (ci->values_num == 1) ? "" : "s");
302   }
303
304   r = (o_result_t *) malloc (sizeof (*r));
305   if (r == NULL)
306   {
307     ERROR ("oracle plugin: malloc failed.");
308     return (-1);
309   }
310   memset (r, 0, sizeof (*r));
311   r->type = NULL;
312   r->instances = NULL;
313   r->values = NULL;
314   r->next = NULL;
315
316   /* Fill the `o_result_t' structure.. */
317   status = 0;
318   for (i = 0; i < ci->children_num; i++)
319   {
320     oconfig_item_t *child = ci->children + i;
321
322     if (strcasecmp ("Type", child->key) == 0)
323       status = o_config_set_string (&r->type, child);
324     else if (strcasecmp ("InstancesFrom", child->key) == 0)
325       status = o_config_add_string (&r->instances, &r->instances_num, child);
326     else if (strcasecmp ("ValuesFrom", child->key) == 0)
327       status = o_config_add_string (&r->values, &r->values_num, child);
328     else
329     {
330       WARNING ("oracle plugin: Query `%s': Option `%s' not allowed here.",
331           q->name, child->key);
332       status = -1;
333     }
334
335     if (status != 0)
336       break;
337   }
338
339   /* Check that all necessary options have been given. */
340   while (status == 0)
341   {
342     if (r->type == NULL)
343     {
344       WARNING ("oracle plugin: `Type' not given for "
345           "result in query `%s'", q->name);
346       status = -1;
347     }
348     if (r->instances == NULL)
349     {
350       WARNING ("oracle plugin: `InstancesFrom' not given for "
351           "result in query `%s'", q->name);
352       status = -1;
353     }
354     if (r->values == NULL)
355     {
356       WARNING ("oracle plugin: `ValuesFrom' not given for "
357           "result in query `%s'", q->name);
358       status = -1;
359     }
360
361     break;
362   } /* while (status == 0) */
363
364   /* If all went well, add this result to the list of results within the
365    * query structure. */
366   if (status == 0)
367   {
368     if (q->results == NULL)
369     {
370       q->results = r;
371     }
372     else
373     {
374       o_result_t *last;
375
376       last = q->results;
377       while (last->next != NULL)
378         last = last->next;
379
380       last->next = r;
381     }
382   }
383
384   if (status != 0)
385   {
386     o_result_free (r);
387     return (-1);
388   }
389
390   return (0);
391 } /* }}} int o_config_add_query_result */
392
393 static int o_config_add_query (oconfig_item_t *ci) /* {{{ */
394 {
395   o_query_t *q;
396   int status;
397   int i;
398
399   if ((ci->values_num != 1)
400       || (ci->values[0].type != OCONFIG_TYPE_STRING))
401   {
402     WARNING ("oracle plugin: The `Query' block "
403         "needs exactly one string argument.");
404     return (-1);
405   }
406
407   q = (o_query_t *) malloc (sizeof (*q));
408   if (q == NULL)
409   {
410     ERROR ("oracle plugin: malloc failed.");
411     return (-1);
412   }
413   memset (q, 0, sizeof (*q));
414
415   status = o_config_set_string (&q->name, ci);
416   if (status != 0)
417   {
418     sfree (q);
419     return (status);
420   }
421
422   /* Fill the `o_query_t' structure.. */
423   for (i = 0; i < ci->children_num; i++)
424   {
425     oconfig_item_t *child = ci->children + i;
426
427     if (strcasecmp ("Statement", child->key) == 0)
428       status = o_config_set_string (&q->statement, child);
429     else if (strcasecmp ("Result", child->key) == 0)
430       status = o_config_add_query_result (q, child);
431     else
432     {
433       WARNING ("oracle plugin: Option `%s' not allowed here.", child->key);
434       status = -1;
435     }
436
437     if (status != 0)
438       break;
439   }
440
441   /* Check that all necessary options have been given. */
442   while (status == 0)
443   {
444     if (q->statement == NULL)
445     {
446       WARNING ("oracle plugin: `Statement' not given for query `%s'", q->name);
447       status = -1;
448     }
449     if (q->results == NULL)
450     {
451       WARNING ("oracle plugin: No (valid) `Result' block given for query `%s'",
452           q->name);
453       status = -1;
454     }
455
456     break;
457   } /* while (status == 0) */
458
459   /* If all went well, add this query to the list of queries within the
460    * database structure. */
461   if (status == 0)
462   {
463     o_query_t **temp;
464
465     temp = (o_query_t **) realloc (queries,
466         sizeof (*queries) * (queries_num + 1));
467     if (temp == NULL)
468     {
469       ERROR ("oracle plugin: realloc failed");
470       status = -1;
471     }
472     else
473     {
474       queries = temp;
475       queries[queries_num] = q;
476       queries_num++;
477     }
478   }
479
480   if (status != 0)
481   {
482     o_query_free (q);
483     return (-1);
484   }
485
486   return (0);
487 } /* }}} int o_config_add_query */
488
489 static int o_config_add_database_query (o_database_t *db, /* {{{ */
490     oconfig_item_t *ci)
491 {
492   o_query_t *q;
493   o_query_t **temp;
494   size_t i;
495
496   if ((ci->values_num != 1)
497       || (ci->values[0].type != OCONFIG_TYPE_STRING))
498   {
499     WARNING ("oracle plugin: The `Query' config option "
500         "needs exactly one string argument.");
501     return (-1);
502   }
503
504   q = NULL;
505   for (i = 0; i < queries_num; i++)
506   {
507     if (strcasecmp (queries[i]->name, ci->values[0].value.string) == 0)
508     {
509       q = queries[i];
510       break;
511     }
512   }
513
514   if (q == NULL)
515   {
516     WARNING ("oracle plugin: Database `%s': Unknown query `%s'. "
517         "Please make sure that the <Query \"%s\"> block comes before "
518         "the <Database \"%s\"> block.",
519         db->name, ci->values[0].value.string,
520         ci->values[0].value.string, db->name);
521     return (-1);
522   }
523
524   temp = (o_query_t **) realloc (db->queries,
525       sizeof (*db->queries) * (db->queries_num + 1));
526   if (temp == NULL)
527   {
528     ERROR ("oracle plugin: realloc failed");
529     return (-1);
530   }
531   else
532   {
533     db->queries = temp;
534     db->queries[db->queries_num] = q;
535     db->queries_num++;
536   }
537
538   return (0);
539 } /* }}} int o_config_add_database_query */
540
541 static int o_config_add_database (oconfig_item_t *ci) /* {{{ */
542 {
543   o_database_t *db;
544   int status;
545   int i;
546
547   if ((ci->values_num != 1)
548       || (ci->values[0].type != OCONFIG_TYPE_STRING))
549   {
550     WARNING ("oracle plugin: The `Database' block "
551         "needs exactly one string argument.");
552     return (-1);
553   }
554
555   db = (o_database_t *) malloc (sizeof (*db));
556   if (db == NULL)
557   {
558     ERROR ("oracle plugin: malloc failed.");
559     return (-1);
560   }
561   memset (db, 0, sizeof (*db));
562
563   status = o_config_set_string (&db->name, ci);
564   if (status != 0)
565   {
566     sfree (db);
567     return (status);
568   }
569
570   /* Fill the `o_database_t' structure.. */
571   for (i = 0; i < ci->children_num; i++)
572   {
573     oconfig_item_t *child = ci->children + i;
574
575     if (strcasecmp ("ConnectID", child->key) == 0)
576       status = o_config_set_string (&db->connect_id, child);
577     else if (strcasecmp ("Username", child->key) == 0)
578       status = o_config_set_string (&db->username, child);
579     else if (strcasecmp ("Password", child->key) == 0)
580       status = o_config_set_string (&db->password, child);
581     else if (strcasecmp ("Query", child->key) == 0)
582       status = o_config_add_database_query (db, child);
583     else
584     {
585       WARNING ("oracle plugin: Option `%s' not allowed here.", child->key);
586       status = -1;
587     }
588
589     if (status != 0)
590       break;
591   }
592
593   /* Check that all necessary options have been given. */
594   while (status == 0)
595   {
596     if (db->connect_id == NULL)
597     {
598       WARNING ("oracle plugin: `ConnectID' not given for query `%s'", db->name);
599       status = -1;
600     }
601     if (db->username == NULL)
602     {
603       WARNING ("oracle plugin: `Username' not given for query `%s'", db->name);
604       status = -1;
605     }
606     if (db->password == NULL)
607     {
608       WARNING ("oracle plugin: `Password' not given for query `%s'", db->name);
609       status = -1;
610     }
611
612     break;
613   } /* while (status == 0) */
614
615   /* If all went well, add this query to the list of queries within the
616    * database structure. */
617   if (status == 0)
618   {
619     o_database_t **temp;
620
621     temp = (o_database_t **) realloc (databases,
622         sizeof (*databases) * (databases_num + 1));
623     if (temp == NULL)
624     {
625       ERROR ("oracle plugin: realloc failed");
626       status = -1;
627     }
628     else
629     {
630       databases = temp;
631       databases[databases_num] = db;
632       databases_num++;
633     }
634   }
635
636   if (status != 0)
637   {
638     o_database_free (db);
639     return (-1);
640   }
641
642   return (0);
643 } /* }}} int o_config_add_database */
644
645 static int o_config (oconfig_item_t *ci) /* {{{ */
646 {
647   int i;
648
649   for (i = 0; i < ci->children_num; i++)
650   {
651     oconfig_item_t *child = ci->children + i;
652     if (strcasecmp ("Query", child->key) == 0)
653       o_config_add_query (child);
654     else if (strcasecmp ("Database", child->key) == 0)
655       o_config_add_database (child);
656     else
657     {
658       WARNING ("snmp plugin: Ignoring unknown config option `%s'.", child->key);
659     }
660   } /* for (ci->children) */
661
662   return (0);
663 } /* }}} int o_config */
664
665 /* }}} End of configuration handling functions */
666
667 static int o_init (void) /* {{{ */
668 {
669   int status;
670
671   if (oci_env != NULL)
672     return (0);
673
674   status = OCIEnvCreate (&oci_env,
675       /* mode = */ OCI_THREADED,
676       /* context        = */ NULL,
677       /* malloc         = */ NULL,
678       /* realloc        = */ NULL,
679       /* free           = */ NULL,
680       /* user_data_size = */ 0,
681       /* user_data_ptr  = */ NULL);
682   if (status != 0)
683   {
684     ERROR ("oracle plugin: OCIEnvCreate failed with status %i.", status);
685     return (-1);
686   }
687
688   status = OCIHandleAlloc (oci_env, (void *) &oci_error, OCI_HTYPE_ERROR,
689       /* user_data_size = */ 0, /* user_data = */ NULL);
690   if (status != OCI_SUCCESS)
691   {
692     ERROR ("oracle plugin: OCIHandleAlloc (OCI_HTYPE_ERROR) failed "
693         "with status %i.", status);
694     return (-1);
695   }
696
697   return (0);
698 } /* }}} int o_init */
699
700 static void o_submit (o_database_t *db, o_result_t *r, /* {{{ */
701     const data_set_t *ds, char **buffer_instances, char **buffer_values)
702 {
703   value_list_t vl = VALUE_LIST_INIT;
704   size_t i;
705
706   assert (((size_t) ds->ds_num) == r->values_num);
707
708   vl.values = (value_t *) malloc (sizeof (value_t) * r->values_num);
709   if (vl.values == NULL)
710   {
711     ERROR ("oracle plugin: malloc failed.");
712     return;
713   }
714   vl.values_len = ds->ds_num;
715
716   for (i = 0; i < r->values_num; i++)
717   {
718     char *endptr;
719
720     endptr = NULL;
721     errno = 0;
722     if (ds->ds[i].type == DS_TYPE_COUNTER)
723       vl.values[i].counter = (counter_t) strtoll (buffer_values[i],
724           &endptr, /* base = */ 0);
725     else if (ds->ds[i].type == DS_TYPE_GAUGE)
726       vl.values[i].gauge = (gauge_t) strtod (buffer_values[i], &endptr);
727     else
728       errno = EINVAL;
729
730     if ((endptr == buffer_values[i]) || (errno != 0))
731     {
732       WARNING ("oracle plugin: o_submit: Parsing `%s' as %s failed.",
733           buffer_values[i],
734           (ds->ds[i].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
735       vl.values[i].gauge = NAN;
736     }
737   }
738
739   vl.time = time (NULL);
740   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
741   sstrncpy (vl.plugin, "oracle", sizeof (vl.plugin));
742   sstrncpy (vl.plugin_instance, db->name, sizeof (vl.type_instance));
743   sstrncpy (vl.type, r->type, sizeof (vl.type));
744   strjoin (vl.type_instance, sizeof (vl.type_instance),
745       buffer_instances, r->instances_num, "-");
746   vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
747
748   plugin_dispatch_values (&vl);
749 } /* }}} void o_submit */
750
751 static int o_handle_query_result (o_database_t *db, /* {{{ */
752     o_query_t *q, o_result_t *r,
753     char **column_names, char **column_values, size_t column_num)
754 {
755   const data_set_t *ds;
756   char **instances;
757   char **values;
758   size_t i;
759
760   instances = NULL;
761   values = NULL;
762
763 #define BAIL_OUT(status) \
764   sfree (instances); \
765   sfree (values); \
766   return (status)
767
768   /* Read `ds' and check number of values {{{ */
769   ds = plugin_get_ds (r->type);
770   if (ds == NULL)
771   {
772     ERROR ("oracle plugin: o_handle_query_result (%s, %s): Type `%s' is not "
773         "known by the daemon. See types.db(5) for details.",
774         db->name, q->name, r->type);
775     BAIL_OUT (-1);
776   }
777
778   if (((size_t) ds->ds_num) != r->values_num)
779   {
780     ERROR ("oracle plugin: o_handle_query_result (%s, %s): The type `%s' "
781         "requires exactly %i value%s, but the configuration specifies %zu.",
782         db->name, q->name, r->type,
783         ds->ds_num, (ds->ds_num == 1) ? "" : "s",
784         r->values_num);
785     BAIL_OUT (-1);
786   }
787   /* }}} */
788
789   /* Allocate `instances' and `values'. {{{ */
790   instances = (char **) calloc (r->instances_num, sizeof (char *));
791   if (instances == NULL)
792   {
793     ERROR ("oracle plugin: o_handle_query_result (%s, %s): malloc failed.",
794         db->name, q->name);
795     BAIL_OUT (-1);
796   }
797
798   values = (char **) calloc (r->values_num, sizeof (char *));
799   if (values == NULL)
800   {
801     sfree (instances);
802     ERROR ("oracle plugin: o_handle_query_result (%s, %s): malloc failed.",
803         db->name, q->name);
804     BAIL_OUT (-1);
805   }
806   /* }}} */
807
808   /* Fill `instances' with pointers to the appropriate strings from
809    * `column_values' */
810   for (i = 0; i < r->instances_num; i++) /* {{{ */
811   {
812     size_t j;
813
814     instances[i] = NULL;
815     for (j = 0; j < column_num; j++)
816     {
817       if (strcasecmp (r->instances[i], column_names[j]) == 0)
818       {
819         instances[i] = column_values[j];
820         break;
821       }
822     }
823
824     if (instances[i] == NULL)
825     {
826       ERROR ("oracle plugin: o_handle_query_result (%s, %s): "
827           "Cannot find column `%s'. Is the statement correct?",
828           db->name, q->name, r->instances[i]);
829       BAIL_OUT (-1);
830     }
831   } /* }}} */
832
833   /* Fill `values' with pointers to the appropriate strings from
834    * `column_values' */
835   for (i = 0; i < r->values_num; i++) /* {{{ */
836   {
837     size_t j;
838
839     values[i] = NULL;
840     for (j = 0; j < column_num; j++)
841     {
842       if (strcasecmp (r->values[i], column_names[j]) == 0)
843       {
844         values[i] = column_values[j];
845         break;
846       }
847     }
848
849     if (values[i] == NULL)
850     {
851       ERROR ("oracle plugin: o_handle_query_result (%s, %s): "
852           "Cannot find column `%s'. Is the statement correct?",
853           db->name, q->name, r->values[i]);
854       BAIL_OUT (-1);
855     }
856   } /* }}} */
857
858   o_submit (db, r, ds, instances, values);
859
860   BAIL_OUT (0);
861 #undef BAIL_OUT
862 } /* }}} int o_handle_query_result */
863
864 static int o_read_database_query (o_database_t *db, /* {{{ */
865     o_query_t *q)
866 {
867   char **column_names;
868   char **column_values;
869   size_t column_num;
870
871   /* List of `OCIDefine' pointers. These defines map columns to the buffer
872    * space declared above. */
873   OCIDefine **oci_defines;
874
875   int status;
876   size_t i;
877
878   /* Prepare the statement */
879   if (q->oci_statement == NULL) /* {{{ */
880   {
881     status = OCIHandleAlloc (oci_env, (void *) &q->oci_statement,
882         OCI_HTYPE_STMT, /* user_data_size = */ 0, /* user_data = */ NULL);
883     if (status != OCI_SUCCESS)
884     {
885       o_report_error ("o_read_database_query", "OCIHandleAlloc", oci_error);
886       q->oci_statement = NULL;
887       return (-1);
888     }
889
890     status = OCIStmtPrepare (q->oci_statement, oci_error,
891         (text *) q->statement, (ub4) strlen (q->statement),
892         /* language = */ OCI_NTV_SYNTAX,
893         /* mode     = */ OCI_DEFAULT);
894     if (status != OCI_SUCCESS)
895     {
896       o_report_error ("o_read_database_query", "OCIStmtPrepare", oci_error);
897       OCIHandleFree (q->oci_statement, OCI_HTYPE_STMT);
898       q->oci_statement = NULL;
899       return (-1);
900     }
901     assert (q->oci_statement != NULL);
902   } /* }}} */
903
904   /* Execute the statement */
905   status = OCIStmtExecute (db->oci_service_context, /* {{{ */
906       q->oci_statement,
907       oci_error,
908       /* iters = */ 0,
909       /* rowoff = */ 0,
910       /* snap_in = */ NULL, /* snap_out = */ NULL,
911       /* mode = */ OCI_DEFAULT);
912   if (status != OCI_SUCCESS)
913   {
914     o_report_error ("o_read_database_query", "OCIStmtExecute", oci_error);
915     ERROR ("oracle plugin: o_read_database_query: "
916         "Failing statement was: %s", q->statement);
917     return (-1);
918   } /* }}} */
919
920   /* Acquire the number of columns returned. */
921   do /* {{{ */
922   {
923     ub4 param_counter = 0;
924     status = OCIAttrGet (q->oci_statement, OCI_HTYPE_STMT, /* {{{ */
925         &param_counter, /* size pointer = */ NULL, 
926         OCI_ATTR_PARAM_COUNT, oci_error);
927     if (status != OCI_SUCCESS)
928     {
929       o_report_error ("o_read_database_query", "OCIAttrGet", oci_error);
930       return (-1);
931     } /* }}} */
932
933     column_num = (size_t) param_counter;
934   } while (0); /* }}} */
935
936   /* Allocate the following buffers:
937    * 
938    *  +---------------+-----------------------------------+
939    *  ! Name          ! Size                              !
940    *  +---------------+-----------------------------------+
941    *  ! column_names  ! column_num x DATA_MAX_NAME_LEN    !
942    *  ! column_values ! column_num x DATA_MAX_NAME_LEN    !
943    *  ! oci_defines   ! column_num x sizeof (OCIDefine *) !
944    *  +---------------+-----------------------------------+
945    *
946    * {{{ */
947 #define NUMBER_BUFFER_SIZE 64
948
949 #define FREE_ALL \
950   if (column_names != NULL) { \
951     sfree (column_names[0]); \
952     sfree (column_names); \
953   } \
954   if (column_values != NULL) { \
955     sfree (column_values[0]); \
956     sfree (column_values); \
957   } \
958   sfree (oci_defines)
959
960 #define ALLOC_OR_FAIL(ptr, ptr_size) \
961   do { \
962     size_t alloc_size = (size_t) ((ptr_size)); \
963     (ptr) = malloc (alloc_size); \
964     if ((ptr) == NULL) { \
965       FREE_ALL; \
966       ERROR ("oracle plugin: o_read_database_query: malloc failed."); \
967       return (-1); \
968     } \
969     memset ((ptr), 0, alloc_size); \
970   } while (0)
971
972   /* Initialize everything to NULL so the above works. */
973   column_names  = NULL;
974   column_values = NULL;
975   oci_defines   = NULL;
976
977   ALLOC_OR_FAIL (column_names, column_num * sizeof (char *));
978   ALLOC_OR_FAIL (column_names[0], column_num * DATA_MAX_NAME_LEN
979       * sizeof (char));
980   for (i = 1; i < column_num; i++)
981     column_names[i] = column_names[i - 1] + DATA_MAX_NAME_LEN;
982
983   ALLOC_OR_FAIL (column_values, column_num * sizeof (char *));
984   ALLOC_OR_FAIL (column_values[0], column_num * DATA_MAX_NAME_LEN
985       * sizeof (char));
986   for (i = 1; i < column_num; i++)
987     column_values[i] = column_values[i - 1] + DATA_MAX_NAME_LEN;
988
989   ALLOC_OR_FAIL (oci_defines, column_num * sizeof (OCIDefine *));
990   /* }}} End of buffer allocations. */
991
992   /* ``Define'' the returned data, i. e. bind the columns to the buffers
993    * allocated above. */
994   for (i = 0; i < column_num; i++) /* {{{ */
995   {
996     char *column_name;
997     size_t column_name_length;
998     char column_name_copy[DATA_MAX_NAME_LEN];
999     OCIParam *oci_param;
1000
1001     oci_param = NULL;
1002
1003     status = OCIParamGet (q->oci_statement, OCI_HTYPE_STMT, oci_error,
1004         (void *) &oci_param, (ub4) (i + 1));
1005     if (status != OCI_SUCCESS)
1006     {
1007       /* This is probably alright */
1008       DEBUG ("oracle plugin: o_read_database_query: status = %#x (= %i);", status, status);
1009       o_report_error ("o_read_database_query", "OCIParamGet", oci_error);
1010       status = OCI_SUCCESS;
1011       break;
1012     }
1013
1014     column_name = NULL;
1015     column_name_length = 0;
1016     status = OCIAttrGet (oci_param, OCI_DTYPE_PARAM,
1017         &column_name, &column_name_length, OCI_ATTR_NAME, oci_error);
1018     if (status != OCI_SUCCESS)
1019     {
1020       o_report_error ("o_read_database_query", "OCIAttrGet (OCI_ATTR_NAME)",
1021           oci_error);
1022       continue;
1023     }
1024
1025     /* Copy the name to column_names. Warning: The ``string'' returned by OCI
1026      * may not be null terminated! */
1027     memset (column_names[i], 0, DATA_MAX_NAME_LEN);
1028     if (column_name_length >= DATA_MAX_NAME_LEN)
1029       column_name_length = DATA_MAX_NAME_LEN - 1;
1030     memcpy (column_names[i], column_name, column_name_length);
1031     column_names[i][column_name_length] = 0;
1032
1033     DEBUG ("oracle plugin: o_read_database_query: column_names[%zu] = %s; "
1034         "column_name_length = %zu;",
1035         i, column_name_copy, column_name_length);
1036
1037     status = OCIDefineByPos (q->oci_statement,
1038         &oci_defines[i], oci_error, (ub4) (i + 1),
1039         column_values[i], DATA_MAX_NAME_LEN, SQLT_STR,
1040         NULL, NULL, NULL, OCI_DEFAULT);
1041     if (status != OCI_SUCCESS)
1042     {
1043       o_report_error ("o_read_database_query", "OCIDefineByPos", oci_error);
1044       continue;
1045     }
1046   } /* for (j = 1; j <= param_counter; j++) */
1047   /* }}} End of the ``define'' stuff. */
1048
1049   /* Fetch and handle all the rows that matched the query. */
1050   while (42) /* {{{ */
1051   {
1052     o_result_t *r;
1053
1054     status = OCIStmtFetch2 (q->oci_statement, oci_error,
1055         /* nrows = */ 1, /* orientation = */ OCI_FETCH_NEXT,
1056         /* fetch offset = */ 0, /* mode = */ OCI_DEFAULT);
1057     if (status == OCI_NO_DATA)
1058     {
1059       status = OCI_SUCCESS;
1060       break;
1061     }
1062     else if ((status != OCI_SUCCESS) && (status != OCI_SUCCESS_WITH_INFO))
1063     {
1064       o_report_error ("o_read_database_query", "OCIStmtFetch2", oci_error);
1065       break;
1066     }
1067
1068     for (i = 0; i < column_num; i++)
1069     {
1070       DEBUG ("oracle plugin: o_read_database_query: [%zu] %s = %s;",
1071            i, column_names[i], column_values[i]);
1072     }
1073
1074     for (r = q->results; r != NULL; r = r->next)
1075       o_handle_query_result (db, q, r, column_names, column_values, column_num);
1076   } /* }}} while (42) */
1077
1078   /* DEBUG ("oracle plugin: o_read_database_query: This statement succeeded: %s", q->statement); */
1079   FREE_ALL;
1080
1081   return (0);
1082 #undef FREE_ALL
1083 #undef ALLOC_OR_FAIL
1084 } /* }}} int o_read_database_query */
1085
1086 static int o_read_database (o_database_t *db) /* {{{ */
1087 {
1088   size_t i;
1089   int status;
1090
1091   if (db->oci_service_context == NULL)
1092   {
1093     status = OCILogon (oci_env, oci_error,
1094         &db->oci_service_context,
1095         (OraText *) db->username, (ub4) strlen (db->username),
1096         (OraText *) db->password, (ub4) strlen (db->password),
1097         (OraText *) db->connect_id, (ub4) strlen (db->connect_id));
1098     if (status != OCI_SUCCESS)
1099     {
1100       o_report_error ("o_read_database", "OCILogon", oci_error);
1101       DEBUG ("oracle plugin: OCILogon (%s): db->oci_service_context = %p;",
1102           db->connect_id, db->oci_service_context);
1103       db->oci_service_context = NULL;
1104       return (-1);
1105     }
1106     assert (db->oci_service_context != NULL);
1107   }
1108
1109   DEBUG ("oracle plugin: o_read_database: db->connect_id = %s; db->oci_service_context = %p;",
1110       db->connect_id, db->oci_service_context);
1111
1112   for (i = 0; i < db->queries_num; i++)
1113     o_read_database_query (db, db->queries[i]);
1114
1115   return (0);
1116 } /* }}} int o_read_database */
1117
1118 static int o_read (void) /* {{{ */
1119 {
1120   size_t i;
1121
1122   for (i = 0; i < databases_num; i++)
1123     o_read_database (databases[i]);
1124
1125   return (0);
1126 } /* }}} int o_read */
1127
1128 static int o_shutdown (void) /* {{{ */
1129 {
1130   size_t i;
1131
1132   for (i = 0; i < databases_num; i++)
1133     if (databases[i]->oci_service_context != NULL)
1134     {
1135       OCIHandleFree (databases[i]->oci_service_context, OCI_HTYPE_SVCCTX);
1136       databases[i]->oci_service_context = NULL;
1137     }
1138   
1139   for (i = 0; i < queries_num; i++)
1140     if (queries[i]->oci_statement != NULL)
1141     {
1142       OCIHandleFree (queries[i]->oci_statement, OCI_HTYPE_STMT);
1143       queries[i]->oci_statement = NULL;
1144     }
1145   
1146   OCIHandleFree (oci_env, OCI_HTYPE_ENV);
1147   return (0);
1148 } /* }}} int o_shutdown */
1149
1150 void module_register (void) /* {{{ */
1151 {
1152   plugin_register_complex_config ("oracle", o_config);
1153   plugin_register_init ("oracle", o_init);
1154   plugin_register_read ("oracle", o_read);
1155   plugin_register_shutdown ("oracle", o_shutdown);
1156 } /* }}} void module_register */
1157
1158 /*
1159  * vim: shiftwidth=2 softtabstop=2 et fdm=marker
1160  */