fa7e72fa60fe83fef2d82c76198d48e657c3b16a
[collectd.git] / src / oracle.c
1 /**
2  * collectd - src/oracle.c
3  * Copyright (C) 2008  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_query_s
57 {
58   char    *name;
59   char    *statement;
60   char    *type;
61   char   **instances;
62   size_t   instances_num;
63   char   **values;
64   size_t   values_num;
65
66   OCIStmt *oci_statement;
67 };
68 typedef struct o_query_s o_query_t;
69
70 struct o_database_s
71 {
72   char *name;
73   char *connect_id;
74   char *username;
75   char *password;
76
77   o_query_t **queries;
78   size_t      queries_num;
79
80   OCISvcCtx *oci_service_context;
81 };
82 typedef struct o_database_s o_database_t;
83
84 /*
85  * Global variables
86  */
87 static o_query_t    **queries       = NULL;
88 static size_t         queries_num   = 0;
89 static o_database_t **databases     = NULL;
90 static size_t         databases_num = 0;
91
92 OCIEnv   *oci_env = NULL;
93 OCIError *oci_error = NULL;
94
95 /*
96  * Functions
97  */
98 static void o_report_error (const char *where, /* {{{ */
99     const char *what, OCIError *eh)
100 {
101   char buffer[2048];
102   sb4 error_code;
103   int status;
104
105   status = OCIErrorGet (eh, /* record number = */ 1,
106       /* sqlstate = */ NULL,
107       &error_code,
108       (text *) &buffer[0],
109       (ub4) sizeof (buffer),
110       OCI_HTYPE_ERROR);
111   buffer[sizeof (buffer) - 1] = 0;
112
113   if (status == OCI_SUCCESS)
114   {
115     size_t buffer_length;
116
117     buffer_length = strlen (buffer);
118     while ((buffer_length > 0) && (buffer[buffer_length - 1] < 32))
119     {
120       buffer_length--;
121       buffer[buffer_length] = 0;
122     }
123
124     ERROR ("oracle plugin: %s: %s failed: %s",
125         where, what, buffer);
126   }
127   else
128   {
129     ERROR ("oracle plugin: %s: %s failed. Additionally, OCIErrorGet failed with status %i.",
130         where, what, status);
131   }
132 } /* }}} void o_report_error */
133
134 static void o_query_free (o_query_t *q) /* {{{ */
135 {
136   size_t i;
137
138   if (q == NULL)
139     return;
140
141   sfree (q->name);
142   sfree (q->statement);
143   sfree (q->type);
144
145   for (i = 0; i < q->instances_num; i++)
146     sfree (q->instances[i]);
147   sfree (q->instances);
148
149   for (i = 0; i < q->values_num; i++)
150     sfree (q->values[i]);
151   sfree (q->values);
152
153   sfree (q);
154 } /* }}} void o_query_free */
155
156 static void o_database_free (o_database_t *db) /* {{{ */
157 {
158   if (db == NULL)
159     return;
160
161   sfree (db->name);
162   sfree (db->connect_id);
163   sfree (db->username);
164   sfree (db->password);
165   sfree (db->queries);
166
167   sfree (db);
168 } /* }}} void o_database_free */
169
170 /* Configuration handling functions {{{
171  *
172  * <Plugin oracle>
173  *   <Query "plugin_instance0">
174  *     Statement "SELECT name, value FROM table"
175  *     Type "gauge"
176  *     InstancesFrom "name"
177  *     ValuesFrom "value"
178  *   </Query>
179  *     
180  *   <Database "plugin_instance1">
181  *     ConnectID "db01"
182  *     Username "oracle"
183  *     Password "secret"
184  *     Query "plugin_instance0"
185  *   </Database>
186  * </Plugin>
187  */
188
189 static int o_config_set_string (char **ret_string, /* {{{ */
190     oconfig_item_t *ci)
191 {
192   char *string;
193
194   if ((ci->values_num != 1)
195       || (ci->values[0].type != OCONFIG_TYPE_STRING))
196   {
197     WARNING ("oracle plugin: The `%s' config option "
198         "needs exactly one string argument.", ci->key);
199     return (-1);
200   }
201
202   string = strdup (ci->values[0].value.string);
203   if (string == NULL)
204   {
205     ERROR ("oracle plugin: strdup failed.");
206     return (-1);
207   }
208
209   if (*ret_string != NULL)
210     free (*ret_string);
211   *ret_string = string;
212
213   return (0);
214 } /* }}} int o_config_set_string */
215
216 static int o_config_add_string (char ***ret_array, /* {{{ */
217     size_t *ret_array_len, oconfig_item_t *ci)
218 {
219   char **array;
220   size_t array_len;
221   int i;
222
223   if (ci->values_num < 1)
224   {
225     WARNING ("oracle plugin: The `%s' config option "
226         "needs at least one argument.", ci->key);
227     return (-1);
228   }
229
230   for (i = 0; i < ci->values_num; i++)
231   {
232     if (ci->values[i].type != OCONFIG_TYPE_STRING)
233     {
234       WARNING ("oracle plugin: Argument %i to the `%s' option "
235           "is not a string.", i + 1, ci->key);
236       return (-1);
237     }
238   }
239
240   array_len = *ret_array_len;
241   array = (char **) realloc (*ret_array,
242       sizeof (char *) * (array_len + ci->values_num));
243   if (array == NULL)
244   {
245     ERROR ("oracle plugin: realloc failed.");
246     return (-1);
247   }
248   *ret_array = array;
249
250   for (i = 0; i < ci->values_num; i++)
251   {
252     array[array_len] = strdup (ci->values[i].value.string);
253     if (array[array_len] == NULL)
254     {
255       ERROR ("oracle plugin: strdup failed.");
256       *ret_array_len = array_len;
257       return (-1);
258     }
259     array_len++;
260   }
261
262   *ret_array_len = array_len;
263   return (0);
264 } /* }}} int o_config_add_string */
265
266 static int o_config_add_query (oconfig_item_t *ci) /* {{{ */
267 {
268   o_query_t *q;
269   int status;
270   int i;
271
272   if ((ci->values_num != 1)
273       || (ci->values[0].type != OCONFIG_TYPE_STRING))
274   {
275     WARNING ("oracle plugin: The `Query' block "
276         "needs exactly one string argument.");
277     return (-1);
278   }
279
280   q = (o_query_t *) malloc (sizeof (*q));
281   if (q == NULL)
282   {
283     ERROR ("oracle plugin: malloc failed.");
284     return (-1);
285   }
286   memset (q, 0, sizeof (*q));
287
288   status = o_config_set_string (&q->name, ci);
289   if (status != 0)
290   {
291     sfree (q);
292     return (status);
293   }
294
295   /* Fill the `o_query_t' structure.. */
296   for (i = 0; i < ci->children_num; i++)
297   {
298     oconfig_item_t *child = ci->children + i;
299
300     if (strcasecmp ("Statement", child->key) == 0)
301       status = o_config_set_string (&q->statement, child);
302     else if (strcasecmp ("Type", child->key) == 0)
303       status = o_config_set_string (&q->type, child);
304     else if (strcasecmp ("InstancesFrom", child->key) == 0)
305       status = o_config_add_string (&q->instances, &q->instances_num, child);
306     else if (strcasecmp ("ValuesFrom", child->key) == 0)
307       status = o_config_add_string (&q->values, &q->values_num, child);
308     else
309     {
310       WARNING ("oracle plugin: Option `%s' not allowed here.", child->key);
311       status = -1;
312     }
313
314     if (status != 0)
315       break;
316   }
317
318   /* Check that all necessary options have been given. */
319   while (status == 0)
320   {
321     if (q->statement == NULL)
322     {
323       WARNING ("oracle plugin: `Statement' not given for query `%s'", q->name);
324       status = -1;
325     }
326     if (q->type == NULL)
327     {
328       WARNING ("oracle plugin: `Type' not given for query `%s'", q->name);
329       status = -1;
330     }
331     if (q->instances == NULL)
332     {
333       WARNING ("oracle plugin: `InstancesFrom' not given for query `%s'", q->name);
334       status = -1;
335     }
336     if (q->values == NULL)
337     {
338       WARNING ("oracle plugin: `ValuesFrom' not given for query `%s'", q->name);
339       status = -1;
340     }
341
342     break;
343   } /* while (status == 0) */
344
345   /* If all went well, add this query to the list of queries within the
346    * database structure. */
347   if (status == 0)
348   {
349     o_query_t **temp;
350
351     temp = (o_query_t **) realloc (queries,
352         sizeof (*queries) * (queries_num + 1));
353     if (temp == NULL)
354     {
355       ERROR ("oracle plugin: realloc failed");
356       status = -1;
357     }
358     else
359     {
360       queries = temp;
361       queries[queries_num] = q;
362       queries_num++;
363     }
364   }
365
366   if (status != 0)
367   {
368     o_query_free (q);
369     return (-1);
370   }
371
372   return (0);
373 } /* }}} int o_config_add_query */
374
375 static int o_config_add_database_query (o_database_t *db, /* {{{ */
376     oconfig_item_t *ci)
377 {
378   o_query_t *q;
379   o_query_t **temp;
380   size_t i;
381
382   if ((ci->values_num != 1)
383       || (ci->values[0].type != OCONFIG_TYPE_STRING))
384   {
385     WARNING ("oracle plugin: The `Query' config option "
386         "needs exactly one string argument.");
387     return (-1);
388   }
389
390   q = NULL;
391   for (i = 0; i < queries_num; i++)
392   {
393     if (strcasecmp (queries[i]->name, ci->values[0].value.string) == 0)
394     {
395       q = queries[i];
396       break;
397     }
398   }
399
400   if (q == NULL)
401   {
402     WARNING ("oracle plugin: Database `%s': Unknown query `%s'. "
403         "Please make sure that the <Query \"%s\"> block comes before "
404         "the <Database \"%s\"> block.",
405         db->name, ci->values[0].value.string,
406         ci->values[0].value.string, db->name);
407     return (-1);
408   }
409
410   temp = (o_query_t **) realloc (db->queries,
411       sizeof (*db->queries) * (db->queries_num + 1));
412   if (temp == NULL)
413   {
414     ERROR ("oracle plugin: realloc failed");
415     return (-1);
416   }
417   else
418   {
419     db->queries = temp;
420     db->queries[db->queries_num] = q;
421     db->queries_num++;
422   }
423
424   return (0);
425 } /* }}} int o_config_add_database_query */
426
427 static int o_config_add_database (oconfig_item_t *ci) /* {{{ */
428 {
429   o_database_t *db;
430   int status;
431   int i;
432
433   if ((ci->values_num != 1)
434       || (ci->values[0].type != OCONFIG_TYPE_STRING))
435   {
436     WARNING ("oracle plugin: The `Database' block "
437         "needs exactly one string argument.");
438     return (-1);
439   }
440
441   db = (o_database_t *) malloc (sizeof (*db));
442   if (db == NULL)
443   {
444     ERROR ("oracle plugin: malloc failed.");
445     return (-1);
446   }
447   memset (db, 0, sizeof (*db));
448
449   status = o_config_set_string (&db->name, ci);
450   if (status != 0)
451   {
452     sfree (db);
453     return (status);
454   }
455
456   /* Fill the `o_database_t' structure.. */
457   for (i = 0; i < ci->children_num; i++)
458   {
459     oconfig_item_t *child = ci->children + i;
460
461     if (strcasecmp ("ConnectID", child->key) == 0)
462       status = o_config_set_string (&db->connect_id, child);
463     else if (strcasecmp ("Username", child->key) == 0)
464       status = o_config_set_string (&db->username, child);
465     else if (strcasecmp ("Password", child->key) == 0)
466       status = o_config_set_string (&db->password, child);
467     else if (strcasecmp ("Query", child->key) == 0)
468       status = o_config_add_database_query (db, child);
469     else
470     {
471       WARNING ("oracle plugin: Option `%s' not allowed here.", child->key);
472       status = -1;
473     }
474
475     if (status != 0)
476       break;
477   }
478
479   /* Check that all necessary options have been given. */
480   while (status == 0)
481   {
482     if (db->connect_id == NULL)
483     {
484       WARNING ("oracle plugin: `ConnectID' not given for query `%s'", db->name);
485       status = -1;
486     }
487     if (db->username == NULL)
488     {
489       WARNING ("oracle plugin: `Username' not given for query `%s'", db->name);
490       status = -1;
491     }
492     if (db->password == NULL)
493     {
494       WARNING ("oracle plugin: `Password' not given for query `%s'", db->name);
495       status = -1;
496     }
497
498     break;
499   } /* while (status == 0) */
500
501   /* If all went well, add this query to the list of queries within the
502    * database structure. */
503   if (status == 0)
504   {
505     o_database_t **temp;
506
507     temp = (o_database_t **) realloc (databases,
508         sizeof (*databases) * (databases_num + 1));
509     if (temp == NULL)
510     {
511       ERROR ("oracle plugin: realloc failed");
512       status = -1;
513     }
514     else
515     {
516       databases = temp;
517       databases[databases_num] = db;
518       databases_num++;
519     }
520   }
521
522   if (status != 0)
523   {
524     o_database_free (db);
525     return (-1);
526   }
527
528   return (0);
529 } /* }}} int o_config_add_database */
530
531 static int o_config (oconfig_item_t *ci) /* {{{ */
532 {
533   int i;
534
535   for (i = 0; i < ci->children_num; i++)
536   {
537     oconfig_item_t *child = ci->children + i;
538     if (strcasecmp ("Query", child->key) == 0)
539       o_config_add_query (child);
540     else if (strcasecmp ("Database", child->key) == 0)
541       o_config_add_database (child);
542     else
543     {
544       WARNING ("snmp plugin: Ignoring unknown config option `%s'.", child->key);
545     }
546   } /* for (ci->children) */
547
548   return (0);
549 } /* }}} int o_config */
550
551 /* }}} End of configuration handling functions */
552
553 static int o_init (void) /* {{{ */
554 {
555   int status;
556
557   if (oci_env != NULL)
558     return (0);
559
560   status = OCIEnvCreate (&oci_env,
561       /* mode = */ OCI_THREADED,
562       /* context        = */ NULL,
563       /* malloc         = */ NULL,
564       /* realloc        = */ NULL,
565       /* free           = */ NULL,
566       /* user_data_size = */ 0,
567       /* user_data_ptr  = */ NULL);
568   if (status != 0)
569   {
570     ERROR ("oracle plugin: OCIEnvCreate failed with status %i.", status);
571     return (-1);
572   }
573
574   status = OCIHandleAlloc (oci_env, (void *) &oci_error, OCI_HTYPE_ERROR,
575       /* user_data_size = */ 0, /* user_data = */ NULL);
576   if (status != OCI_SUCCESS)
577   {
578     ERROR ("oracle plugin: OCIHandleAlloc (OCI_HTYPE_ERROR) failed "
579         "with status %i.", status);
580     return (-1);
581   }
582
583   return (0);
584 } /* }}} int o_init */
585
586 static void o_submit (o_database_t *db, o_query_t *q, /* {{{ */
587     const data_set_t *ds, char **buffer_instances, char **buffer_values)
588 {
589   value_list_t vl = VALUE_LIST_INIT;
590   size_t i;
591
592   assert (((size_t) ds->ds_num) == q->values_num);
593
594   vl.values = (value_t *) malloc (sizeof (value_t) * q->values_num);
595   if (vl.values == NULL)
596   {
597     ERROR ("oracle plugin: malloc failed.");
598     return;
599   }
600   vl.values_len = ds->ds_num;
601
602   for (i = 0; i < q->values_num; i++)
603   {
604     char *endptr;
605
606     endptr = NULL;
607     errno = 0;
608     if (ds->ds[i].type == DS_TYPE_COUNTER)
609       vl.values[i].counter = (counter_t) strtoll (buffer_values[i],
610           &endptr, /* base = */ 0);
611     else if (ds->ds[i].type == DS_TYPE_GAUGE)
612       vl.values[i].gauge = (gauge_t) strtod (buffer_values[i], &endptr);
613     else
614       errno = EINVAL;
615
616     if ((endptr == buffer_values[i]) || (errno != 0))
617     {
618       WARNING ("oracle plugin: o_submit: Parsing `%s' as %s failed.",
619           buffer_values[i],
620           (ds->ds[i].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
621       vl.values[i].gauge = NAN;
622     }
623   }
624
625   vl.time = time (NULL);
626   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
627   sstrncpy (vl.plugin, "oracle", sizeof (vl.plugin));
628   sstrncpy (vl.plugin_instance, db->name, sizeof (vl.type_instance));
629   sstrncpy (vl.type, q->type, sizeof (vl.type));
630   strjoin (vl.type_instance, sizeof (vl.type_instance),
631       buffer_instances, q->instances_num, "-");
632   vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
633
634   plugin_dispatch_values (&vl);
635 } /* }}} void o_submit */
636
637 static int o_read_database_query (o_database_t *db, /* {{{ */
638     o_query_t *q)
639 {
640   const data_set_t *ds;
641   ub4 param_counter; /* == number of columns */
642   int status;
643   size_t i;
644   ub4 j;
645
646   /* Scratch area for OCI to write values to */
647   char **buffer_instances;
648   char **buffer_values;
649
650   /* List of indizes of the instance and value columns. Only used for error
651    * checking. */
652   size_t *index_instances;
653   size_t *index_values;
654
655   /* List of `OCIDefine' pointers. These defines map columns to the buffer
656  * space declared above. */
657   OCIDefine **oci_defines;
658
659   ds = plugin_get_ds (q->type); /* {{{ */
660   if (ds == NULL)
661   {
662     WARNING ("oracle plugin: o_read_database_query (%s, %s): "
663         "plugin_get_ds (%s) failed. Please check if the type exists, "
664         "see types.db(5).",
665         db->name, q->name, q->type);
666     return (-1);
667   } /* }}} */
668
669   if (((size_t) ds->ds_num) != q->values_num)
670   {
671     ERROR ("oracle plugin: o_read_database_query (%s, %s): "
672         "The query `%s' uses the type `%s' which requires exactly "
673         "%i value%s, but you specified %zu value-column%s. "
674         "See types.db(5) for details.",
675         db->name, q->name,
676         q->name, q->type,
677         ds->ds_num, (ds->ds_num == 1) ? "" : "s",
678         q->values_num, (q->values_num == 1) ? "" : "s");
679     return (-1);
680   }
681
682   /* Prepare the statement */
683   if (q->oci_statement == NULL) /* {{{ */
684   {
685     status = OCIHandleAlloc (oci_env, (void *) &q->oci_statement,
686         OCI_HTYPE_STMT, /* user_data_size = */ 0, /* user_data = */ NULL);
687     if (status != OCI_SUCCESS)
688     {
689       o_report_error ("o_read_database_query", "OCIHandleAlloc", oci_error);
690       q->oci_statement = NULL;
691       return (-1);
692     }
693
694     status = OCIStmtPrepare (q->oci_statement, oci_error,
695         (text *) q->statement, (ub4) strlen (q->statement),
696         /* language = */ OCI_NTV_SYNTAX,
697         /* mode     = */ OCI_DEFAULT);
698     if (status != OCI_SUCCESS)
699     {
700       o_report_error ("o_read_database_query", "OCIStmtPrepare", oci_error);
701       OCIHandleFree (q->oci_statement, OCI_HTYPE_STMT);
702       q->oci_statement = NULL;
703       return (-1);
704     }
705     assert (q->oci_statement != NULL);
706   } /* }}} */
707
708   /* Execute the statement */
709   status = OCIStmtExecute (db->oci_service_context, /* {{{ */
710       q->oci_statement,
711       oci_error,
712       /* iters = */ 0,
713       /* rowoff = */ 0,
714       /* snap_in = */ NULL, /* snap_out = */ NULL,
715       /* mode = */ OCI_DEFAULT);
716   if (status != OCI_SUCCESS)
717   {
718     o_report_error ("o_read_database_query", "OCIStmtExecute", oci_error);
719     ERROR ("oracle plugin: o_read_database_query: "
720         "Failing statement was: %s", q->statement);
721     return (-1);
722   } /* }}} */
723
724   /* Acquire the number of columns returned. */
725   param_counter = 0;
726   status = OCIAttrGet (q->oci_statement, OCI_HTYPE_STMT, /* {{{ */
727       &param_counter, /* size pointer = */ NULL, 
728       OCI_ATTR_PARAM_COUNT, oci_error);
729   if (status != OCI_SUCCESS)
730   {
731     o_report_error ("o_read_database_query", "OCIAttrGet", oci_error);
732     return (-1);
733   } /* }}} */
734
735   /* Allocate the following buffers:
736    * 
737    *  - buffer_instances    q->instances_num x DATA_MAX_NAME_LEN
738    *  - buffer_values       q->values_num    x NUMBER_BUFFER_SIZE
739    *  - index_instances     q->instances_num
740    *  - index_values        q->values_num
741    *  - oci_defines         q->instances_num+q->values_num
742    * {{{ */
743 #define NUMBER_BUFFER_SIZE 64
744
745 #define FREE_ALL \
746   if (buffer_instances != NULL) { \
747     sfree (buffer_instances[0]); \
748     sfree (buffer_instances); \
749   } \
750   if (buffer_values != NULL) { \
751     sfree (buffer_values[0]); \
752     sfree (buffer_values); \
753   } \
754   sfree (index_instances); \
755   sfree (index_values); \
756   sfree (oci_defines)
757
758 #define ALLOC_OR_FAIL(ptr, ptr_size) \
759   do { \
760     size_t alloc_size = (size_t) ((ptr_size)); \
761     (ptr) = malloc (alloc_size); \
762     if ((ptr) == NULL) { \
763       FREE_ALL; \
764       ERROR ("oracle plugin: o_read_database_query: malloc failed."); \
765       return (-1); \
766     } \
767     memset ((ptr), 0, alloc_size); \
768   } while (0)
769
770   /* Initialize everything to NULL so the above works. */
771   buffer_instances = NULL;
772   buffer_values    = NULL;
773   index_instances  = NULL;
774   index_values     = NULL;
775   oci_defines      = NULL;
776
777   ALLOC_OR_FAIL (buffer_instances, q->instances_num * sizeof (char *));
778   ALLOC_OR_FAIL (buffer_instances[0], q->instances_num * DATA_MAX_NAME_LEN
779       * sizeof (char));
780   for (i = 1; i < q->instances_num; i++)
781     buffer_instances[i] = buffer_instances[i - 1] + DATA_MAX_NAME_LEN;
782
783   ALLOC_OR_FAIL (buffer_values, q->values_num * sizeof (char *));
784   ALLOC_OR_FAIL (buffer_values[0], q->values_num * NUMBER_BUFFER_SIZE
785       * sizeof (char));
786   for (i = 1; i < q->values_num; i++)
787     buffer_values[i] = buffer_values[i - 1] + NUMBER_BUFFER_SIZE;
788
789   ALLOC_OR_FAIL (index_instances, q->instances_num * sizeof (size_t));
790   ALLOC_OR_FAIL (index_values, q->values_num * sizeof (size_t));
791
792   ALLOC_OR_FAIL (oci_defines, (q->instances_num + q->values_num)
793       * sizeof (OCIDefine *));
794   /* }}} End of buffer allocations. */
795
796   /* ``Define'' the returned data, i. e. bind the columns to the buffers
797    * returned above. */
798   for (j = 1; j <= param_counter; j++) /* {{{ */
799   {
800     char *column_name;
801     size_t column_name_length;
802     char column_name_copy[DATA_MAX_NAME_LEN];
803     size_t i;
804     OCIParam *oci_param;
805
806     oci_param = NULL;
807
808     status = OCIParamGet (q->oci_statement, OCI_HTYPE_STMT, oci_error,
809         (void *) &oci_param, j);
810     if (status != OCI_SUCCESS)
811     {
812       /* This is probably alright */
813       DEBUG ("oracle plugin: o_read_database_query: status = %#x (= %i);", status, status);
814       o_report_error ("o_read_database_query", "OCIParamGet", oci_error);
815       status = OCI_SUCCESS;
816       break;
817     }
818
819     column_name = NULL;
820     column_name_length = 0;
821     status = OCIAttrGet (oci_param, OCI_DTYPE_PARAM,
822         &column_name, &column_name_length, OCI_ATTR_NAME, oci_error);
823     if (status != OCI_SUCCESS)
824     {
825       o_report_error ("o_read_database_query", "OCIAttrGet (OCI_ATTR_NAME)",
826           oci_error);
827       continue;
828     }
829
830     /* Ensure null-termination. */
831     memset (column_name_copy, 0, sizeof (column_name_copy));
832     if (column_name_length >= sizeof (column_name_copy))
833       column_name_length = sizeof (column_name_copy) - 1;
834     memcpy (column_name_copy, column_name, column_name_length);
835     column_name_copy[column_name_length] = 0;
836
837     DEBUG ("oracle plugin: o_read_database_query: column_name[%u] = %s; column_name_length = %zu;",
838         (unsigned int) j, column_name_copy, column_name_length);
839
840     for (i = 0; i < q->instances_num; i++)
841     {
842       if (strcasecmp (q->instances[i], column_name_copy) != 0)
843         continue;
844
845       status = OCIDefineByPos (q->oci_statement,
846           &oci_defines[i], oci_error, j,
847           buffer_instances[i], DATA_MAX_NAME_LEN, SQLT_STR,
848           NULL, NULL, NULL, OCI_DEFAULT);
849       index_instances[i] = j;
850
851       DEBUG ("oracle plugin: o_read_database_query: column[%u] (%s) -> instances[%zu]",
852           (unsigned int) j, column_name_copy, i);
853       break;
854     }
855
856     for (i = 0; i < q->values_num; i++)
857     {
858       if (strcasecmp (q->values[i], column_name_copy) != 0)
859         continue;
860
861       status = OCIDefineByPos (q->oci_statement,
862           &oci_defines[q->instances_num + i], oci_error, j,
863           buffer_values[i], NUMBER_BUFFER_SIZE, SQLT_STR,
864           NULL, NULL, NULL, OCI_DEFAULT);
865       index_values[i] = j;
866
867       DEBUG ("oracle plugin: o_read_database_query: column[%u] (%s) -> values[%zu]",
868           (unsigned int) j, column_name_copy, i);
869       break;
870     }
871   } /* for (j = 1; j <= param_counter; j++) */
872   /* }}} End of the ``define'' stuff. */
873
874   /* Iterate over all indizes and check that all columns from which we're
875    * supposed to read instances or values have been found. */
876   /* {{{ */
877   status = 0;
878   for (i = 0; i < q->instances_num; i++)
879   {
880     if (index_instances[i] > 0)
881       continue;
882
883     ERROR ("oracle plugin: o_read_database_query (%s, %s): "
884         "Instance %zu of the `%s' query should be read from the column `%s', "
885         "but that column wasn't returned by the SQL statement. Please check "
886         "your configuration.",
887         db->name, q->name, (i + 1), q->name, q->instances[i]);
888     status++;
889   }
890
891   for (i = 0; i < q->values_num; i++)
892   {
893     if (index_values[i] > 0)
894       continue;
895
896     ERROR ("oracle plugin: o_read_database_query (%s, %s): "
897         "Value %zu of the `%s' query should be read from the column `%s', "
898         "but that column wasn't returned by the SQL statement. Please check "
899         "your configuration.",
900         db->name, q->name, (i + 1), q->name, q->values[i]);
901     status++;
902   }
903
904   if (status != 0)
905   {
906     FREE_ALL;
907     return (-1);
908   }
909   /* }}} */
910
911   /* Fetch and handle all the rows that matched the query. */
912   while (42) /* {{{ */
913   {
914     status = OCIStmtFetch2 (q->oci_statement, oci_error,
915         /* nrows = */ 1, /* orientation = */ OCI_FETCH_NEXT,
916         /* fetch offset = */ 0, /* mode = */ OCI_DEFAULT);
917     if (status == OCI_NO_DATA)
918     {
919       status = OCI_SUCCESS;
920       break;
921     }
922     else if ((status != OCI_SUCCESS) && (status != OCI_SUCCESS_WITH_INFO))
923     {
924       o_report_error ("o_read_database_query", "OCIStmtFetch2", oci_error);
925       break;
926     }
927
928     for (i = 0; i < q->instances_num; i++)
929     {
930       DEBUG ("oracle plugin: o_read_database_query: "
931           "buffer_instances[%zu] = %s;",
932            i, buffer_instances[i]);
933     }
934
935     for (i = 0; i < q->values_num; i++)
936     {
937       DEBUG ("oracle plugin: o_read_database_query: "
938           "buffer_values[%zu] = %s;",
939            i, buffer_values[i]);
940     }
941
942     o_submit (db, q, ds, buffer_instances, buffer_values);
943   } /* }}} while (42) */
944
945   /* DEBUG ("oracle plugin: o_read_database_query: This statement succeeded: %s", q->statement); */
946   FREE_ALL;
947
948   return (0);
949 #undef FREE_ALL
950 #undef ALLOC_OR_FAIL
951 } /* }}} int o_read_database_query */
952
953 static int o_read_database (o_database_t *db) /* {{{ */
954 {
955   size_t i;
956   int status;
957
958   if (db->oci_service_context == NULL)
959   {
960     status = OCILogon (oci_env, oci_error,
961         &db->oci_service_context,
962         (OraText *) db->username, (ub4) strlen (db->username),
963         (OraText *) db->password, (ub4) strlen (db->password),
964         (OraText *) db->connect_id, (ub4) strlen (db->connect_id));
965     if (status != OCI_SUCCESS)
966     {
967       o_report_error ("o_read_database", "OCILogon", oci_error);
968       DEBUG ("oracle plugin: OCILogon (%s): db->oci_service_context = %p;",
969           db->connect_id, db->oci_service_context);
970       db->oci_service_context = NULL;
971       return (-1);
972     }
973     assert (db->oci_service_context != NULL);
974   }
975
976   DEBUG ("oracle plugin: o_read_database: db->connect_id = %s; db->oci_service_context = %p;",
977       db->connect_id, db->oci_service_context);
978
979   for (i = 0; i < db->queries_num; i++)
980     o_read_database_query (db, db->queries[i]);
981
982   return (0);
983 } /* }}} int o_read_database */
984
985 static int o_read (void) /* {{{ */
986 {
987   size_t i;
988
989   for (i = 0; i < databases_num; i++)
990     o_read_database (databases[i]);
991
992   return (0);
993 } /* }}} int o_read */
994
995 static int o_shutdown (void) /* {{{ */
996 {
997   size_t i;
998
999   for (i = 0; i < databases_num; i++)
1000     if (databases[i]->oci_service_context != NULL)
1001     {
1002       OCIHandleFree (databases[i]->oci_service_context, OCI_HTYPE_SVCCTX);
1003       databases[i]->oci_service_context = NULL;
1004     }
1005   
1006   for (i = 0; i < queries_num; i++)
1007     if (queries[i]->oci_statement != NULL)
1008     {
1009       OCIHandleFree (queries[i]->oci_statement, OCI_HTYPE_STMT);
1010       queries[i]->oci_statement = NULL;
1011     }
1012   
1013   OCIHandleFree (oci_env, OCI_HTYPE_ENV);
1014   return (0);
1015 } /* }}} int o_shutdown */
1016
1017 void module_register (void) /* {{{ */
1018 {
1019   plugin_register_complex_config ("oracle", o_config);
1020   plugin_register_init ("oracle", o_init);
1021   plugin_register_read ("oracle", o_read);
1022   plugin_register_shutdown ("oracle", o_shutdown);
1023 } /* }}} void module_register */
1024
1025 /*
1026  * vim: shiftwidth=2 softtabstop=2 et fdm=marker
1027  */