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