fce358894cc6dcea5f4d7a384fa2c7e73dd35b21
[collectd.git] / src / postgresql.c
1 /**
2  * collectd - src/postgresql.c
3  * Copyright (C) 2008, 2009  Sebastian Harl
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  * Author:
19  *   Sebastian Harl <sh at tokkee.org>
20  **/
21
22 /*
23  * This module collects PostgreSQL database statistics.
24  */
25
26 #include "collectd.h"
27 #include "common.h"
28
29 #include "configfile.h"
30 #include "plugin.h"
31
32 #include "utils_complain.h"
33
34 #include <pg_config_manual.h>
35 #include <libpq-fe.h>
36
37 #define log_err(...) ERROR ("postgresql: " __VA_ARGS__)
38 #define log_warn(...) WARNING ("postgresql: " __VA_ARGS__)
39 #define log_info(...) INFO ("postgresql: " __VA_ARGS__)
40
41 #ifndef C_PSQL_DEFAULT_CONF
42 # define C_PSQL_DEFAULT_CONF PKGDATADIR "/postgresql_default.conf"
43 #endif
44
45 /* Appends the (parameter, value) pair to the string
46  * pointed to by 'buf' suitable to be used as argument
47  * for PQconnectdb(). If value equals NULL, the pair
48  * is ignored. */
49 #define C_PSQL_PAR_APPEND(buf, buf_len, parameter, value) \
50         if ((0 < (buf_len)) && (NULL != (value)) && ('\0' != *(value))) { \
51                 int s = ssnprintf (buf, buf_len, " %s = '%s'", parameter, value); \
52                 if (0 < s) { \
53                         buf     += s; \
54                         buf_len -= s; \
55                 } \
56         }
57
58 /* Returns the tuple (major, minor, patchlevel)
59  * for the given version number. */
60 #define C_PSQL_SERVER_VERSION3(server_version) \
61         (server_version) / 10000, \
62         (server_version) / 100 - (int)((server_version) / 10000) * 100, \
63         (server_version) - (int)((server_version) / 100) * 100
64
65 /* Returns true if the given host specifies a
66  * UNIX domain socket. */
67 #define C_PSQL_IS_UNIX_DOMAIN_SOCKET(host) \
68         ((NULL == (host)) || ('\0' == *(host)) || ('/' == *(host)))
69
70 /* Returns the tuple (host, delimiter, port) for a
71  * given (host, port) pair. Depending on the value of
72  * 'host' a UNIX domain socket or a TCP socket is
73  * assumed. */
74 #define C_PSQL_SOCKET3(host, port) \
75         ((NULL == (host)) || ('\0' == *(host))) ? DEFAULT_PGSOCKET_DIR : host, \
76         C_PSQL_IS_UNIX_DOMAIN_SOCKET (host) ? "/.s.PGSQL." : ":", \
77         port
78
79 typedef enum {
80         C_PSQL_PARAM_HOST = 1,
81         C_PSQL_PARAM_DB,
82         C_PSQL_PARAM_USER,
83         C_PSQL_PARAM_INTERVAL,
84 } c_psql_param_t;
85
86 typedef struct {
87         char  *type;
88         char  *instance_prefix;
89         char **instances_str;
90         int   *instances;
91         int    instances_num;
92         char **values_str; /* may be NULL, even if values_num != 0 in
93                               case the "Column" option has been used */
94         int   *values;
95         int   *ds_types;
96         int    values_num;
97 } c_psql_result_t;
98
99 typedef struct {
100         char *name;
101         char *stmt;
102
103         c_psql_param_t *params;
104         int             params_num;
105
106         c_psql_result_t *results;
107         int              results_num;
108
109         int min_pg_version;
110         int max_pg_version;
111 } c_psql_query_t;
112
113 typedef struct {
114         PGconn      *conn;
115         c_complain_t conn_complaint;
116
117         int proto_version;
118
119         int max_params_num;
120
121         /* user configuration */
122         c_psql_query_t **queries;
123         int             *hidden_queries;
124         int              queries_num;
125
126         char *host;
127         char *port;
128         char *database;
129         char *user;
130         char *password;
131
132         char *sslmode;
133
134         char *krbsrvname;
135
136         char *service;
137 } c_psql_database_t;
138
139 static char *def_queries[] = {
140         "backends",
141         "transactions",
142         "queries",
143         "query_plans",
144         "table_states",
145         "disk_io",
146         "disk_usage"
147 };
148 static int def_queries_num = STATIC_ARRAY_SIZE (def_queries);
149
150 static c_psql_query_t *queries          = NULL;
151 static int             queries_num      = 0;
152
153 static c_psql_database_t *databases     = NULL;
154 static int                databases_num = 0;
155
156 static c_psql_result_t *c_psql_result_new (c_psql_query_t *query)
157 {
158         c_psql_result_t *res;
159
160         ++query->results_num;
161         if (NULL == (query->results = (c_psql_result_t *)realloc (query->results,
162                                         query->results_num * sizeof (*query->results)))) {
163                 log_err ("Out of memory.");
164                 exit (5);
165         }
166         res = query->results + query->results_num - 1;
167
168         res->type    = NULL;
169
170         res->instance_prefix = NULL;
171         res->instances_str   = NULL;
172         res->instances       = NULL;
173         res->instances_num   = 0;
174
175         res->values_str = NULL;
176         res->values     = NULL;
177         res->ds_types   = NULL;
178         res->values_num = 0;
179         return res;
180 } /* c_psql_result_new */
181
182 static void c_psql_result_delete (c_psql_result_t *res)
183 {
184         int i;
185
186         sfree (res->type);
187
188         sfree (res->instance_prefix);
189
190         for (i = 0; i < res->instances_num; ++i)
191                 sfree (res->instances_str[i]);
192         sfree (res->instances_str);
193         sfree (res->instances);
194         res->instances_num = 0;
195
196         for (i = 0; (NULL != res->values_str) && (i < res->values_num); ++i)
197                 sfree (res->values_str[i]);
198         sfree (res->values_str);
199         sfree (res->values);
200         sfree (res->ds_types);
201         res->values_num = 0;
202 } /* c_psql_result_delete */
203
204 static c_psql_query_t *c_psql_query_new (const char *name)
205 {
206         c_psql_query_t *query;
207
208         ++queries_num;
209         if (NULL == (queries = (c_psql_query_t *)realloc (queries,
210                                 queries_num * sizeof (*queries)))) {
211                 log_err ("Out of memory.");
212                 exit (5);
213         }
214         query = queries + queries_num - 1;
215
216         query->name = sstrdup (name);
217         query->stmt = NULL;
218
219         query->params     = NULL;
220         query->params_num = 0;
221
222         query->results     = NULL;
223         query->results_num = 0;
224
225         query->min_pg_version = 0;
226         query->max_pg_version = INT_MAX;
227         return query;
228 } /* c_psql_query_new */
229
230 static int c_psql_query_init (c_psql_query_t *query)
231 {
232         int i;
233
234         /* Get the data set definitions for each query definition. */
235         for (i = 0; i < query->results_num; ++i) {
236                 c_psql_result_t  *res = query->results + i;
237                 const data_set_t *ds;
238
239                 int j;
240
241                 ds = plugin_get_ds (res->type);
242                 if (NULL == ds) {
243                         log_err ("Result: Unknown type \"%s\".", res->type);
244                         return -1;
245                 }
246
247                 if (res->values_num != ds->ds_num) {
248                         log_err ("Result: Invalid type \"%s\" - "
249                                         "expected %i data source%s, got %i.",
250                                         res->type, res->values_num,
251                                         (1 == res->values_num) ? "" : "s",
252                                         ds->ds_num);
253                         return -1;
254                 }
255
256                 for (j = 0; j < res->values_num; ++j)
257                         res->ds_types[j] = ds->ds[j].type;
258         }
259         return 0;
260 } /* c_psql_query_init */
261
262 static void c_psql_query_delete (c_psql_query_t *query)
263 {
264         int i;
265
266         sfree (query->name);
267         sfree (query->stmt);
268
269         sfree (query->params);
270         query->params_num = 0;
271
272         for (i = 0; i < query->results_num; ++i)
273                 c_psql_result_delete (query->results + i);
274         sfree (query->results);
275         query->results_num = 0;
276         return;
277 } /* c_psql_query_delete */
278
279 static c_psql_query_t *c_psql_query_get (const char *name, int server_version)
280 {
281         int i;
282
283         for (i = 0; i < queries_num; ++i)
284                 if (0 == strcasecmp (name, queries[i].name)
285                                 && ((-1 == server_version)
286                                         || ((queries[i].min_pg_version <= server_version)
287                                                 && (server_version <= queries[i].max_pg_version))))
288                         return queries + i;
289         return NULL;
290 } /* c_psql_query_get */
291
292 static c_psql_database_t *c_psql_database_new (const char *name)
293 {
294         c_psql_database_t *db;
295
296         ++databases_num;
297         if (NULL == (databases = (c_psql_database_t *)realloc (databases,
298                                 databases_num * sizeof (*databases)))) {
299                 log_err ("Out of memory.");
300                 exit (5);
301         }
302
303         db = databases + (databases_num - 1);
304
305         db->conn = NULL;
306
307         C_COMPLAIN_INIT (&db->conn_complaint);
308
309         db->proto_version = 0;
310
311         db->max_params_num = 0;
312
313         db->queries        = NULL;
314         db->hidden_queries = NULL;
315         db->queries_num    = 0;
316
317         db->database   = sstrdup (name);
318         db->host       = NULL;
319         db->port       = NULL;
320         db->user       = NULL;
321         db->password   = NULL;
322
323         db->sslmode    = NULL;
324
325         db->krbsrvname = NULL;
326
327         db->service    = NULL;
328         return db;
329 } /* c_psql_database_new */
330
331 static void c_psql_database_init (c_psql_database_t *db, int server_version)
332 {
333         int i;
334
335         /* Get the right version of each query definition. */
336         for (i = 0; i < db->queries_num; ++i) {
337                 c_psql_query_t *tmp;
338
339                 tmp = c_psql_query_get (db->queries[i]->name, server_version);
340
341                 if (tmp == db->queries[i])
342                         continue;
343
344                 if (NULL == tmp) {
345                         log_err ("Query \"%s\" not found for server version %i - "
346                                         "please check your configuration.",
347                                         db->queries[i]->name, server_version);
348                         /* By hiding the query (rather than removing it from the list) we
349                          * don't lose it in case a reconnect to an available version
350                          * happens at a later time. */
351                         db->hidden_queries[i] = 1;
352                         continue;
353                 }
354
355                 db->hidden_queries[i] = 0;
356                 db->queries[i] = tmp;
357         }
358 } /* c_psql_database_init */
359
360 static void c_psql_database_delete (c_psql_database_t *db)
361 {
362         PQfinish (db->conn);
363         db->conn = NULL;
364
365         sfree (db->queries);
366         sfree (db->hidden_queries);
367         db->queries_num = 0;
368
369         sfree (db->database);
370         sfree (db->host);
371         sfree (db->port);
372         sfree (db->user);
373         sfree (db->password);
374
375         sfree (db->sslmode);
376
377         sfree (db->krbsrvname);
378
379         sfree (db->service);
380         return;
381 } /* c_psql_database_delete */
382
383 static void submit (const c_psql_database_t *db, const c_psql_result_t *res,
384                 char **instances, value_t *values)
385 {
386         value_list_t vl = VALUE_LIST_INIT;
387
388         int instances_num = res->instances_num;
389
390         if (NULL != res->instance_prefix)
391                 ++instances_num;
392
393         vl.values     = values;
394         vl.values_len = res->values_num;
395
396         if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
397                         || (0 == strcmp (db->host, "localhost")))
398                 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
399         else
400                 sstrncpy (vl.host, db->host, sizeof (vl.host));
401
402         sstrncpy (vl.plugin, "postgresql", sizeof (vl.plugin));
403         sstrncpy (vl.plugin_instance, db->database, sizeof (vl.plugin_instance));
404
405         sstrncpy (vl.type, res->type, sizeof (vl.type));
406
407         if (0 < instances_num) {
408                 vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
409                 strjoin (vl.type_instance, sizeof (vl.type_instance),
410                                 instances, instances_num, "-");
411
412                 if ('\0' != vl.type_instance[sizeof (vl.type_instance) - 1]) {
413                         vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
414                         log_warn ("Truncated type instance: %s.", vl.type_instance);
415                 }
416         }
417
418         plugin_dispatch_values (&vl);
419         return;
420 } /* submit */
421
422 static int c_psql_get_colnum (PGresult *pgres,
423                 char **strings, int *numbers, int idx)
424 {
425         int colnum;
426
427         if (0 <= numbers[idx])
428                 return numbers[idx];
429
430         colnum = PQfnumber (pgres, strings[idx]);
431         if (0 > colnum)
432                 log_err ("No such column: %s.", strings[idx]);
433
434         numbers[idx] = colnum;
435         return colnum;
436 } /* c_psql_get_colnum */
437
438 static void c_psql_dispatch_row (c_psql_database_t *db, c_psql_query_t *query,
439                 PGresult *pgres, int row)
440 {
441         int i;
442
443         for (i = 0; i < query->results_num; ++i) {
444                 c_psql_result_t *res = query->results + i;
445
446                 char   *instances[res->instances_num + 1];
447                 value_t values[res->values_num];
448
449                 int offset = 0, status = 0, j;
450
451                 /* get the instance name */
452                 if (NULL != res->instance_prefix) {
453                         instances[0] = res->instance_prefix;
454                         offset = 1;
455                 }
456
457                 for (j = 0; (0 == status) && (j < res->instances_num); ++j) {
458                         int col = c_psql_get_colnum (pgres,
459                                         res->instances_str, res->instances, j);
460
461                         if (0 > col) {
462                                 status = -1;
463                                 break;
464                         }
465
466                         instances[j + offset] = PQgetvalue (pgres, row, col);
467                         if (NULL == instances[j + offset])
468                                 instances[j + offset] = "";
469                 }
470
471                 /* get the values */
472                 for (j = 0; (0 == status) && (j < res->values_num); ++j) {
473                         int col = c_psql_get_colnum (pgres,
474                                         res->values_str, res->values, j);
475
476                         char *value_str;
477                         char *endptr = NULL;
478
479                         if (0 > col) {
480                                 status = -1;
481                                 break;
482                         }
483
484                         value_str = PQgetvalue (pgres, row, col);
485                         if ((NULL == value_str) || ('\0' == *value_str))
486                                 value_str = "0";
487
488                         if (res->ds_types[j] == DS_TYPE_COUNTER)
489                                 values[j].counter = (counter_t)strtoll (value_str, &endptr, 0);
490                         else if (res->ds_types[j] == DS_TYPE_GAUGE)
491                                 values[j].gauge = (gauge_t)strtod (value_str, &endptr);
492                         else {
493                                 log_err ("Invalid type \"%s\" (%i).",
494                                                 res->type, res->ds_types[j]);
495                         }
496
497                         if (value_str == endptr) {
498                                 log_err ("Failed to parse string as number: %s.", value_str);
499                                 status = -1;
500                                 break;
501                         }
502                         else if ((NULL != endptr) && ('\0' != *endptr))
503                                 log_warn ("Ignoring trailing garbage after number: %s.",
504                                                 endptr);
505                 }
506
507                 if (0 != status)
508                         continue;
509
510                 submit (db, res, instances, values);
511         }
512 } /* c_psql_dispatch_row */
513
514 static int c_psql_check_connection (c_psql_database_t *db)
515 {
516         /* "ping" */
517         PQclear (PQexec (db->conn, "SELECT 42;"));
518
519         if (CONNECTION_OK != PQstatus (db->conn)) {
520                 PQreset (db->conn);
521
522                 /* trigger c_release() */
523                 if (0 == db->conn_complaint.interval)
524                         db->conn_complaint.interval = 1;
525
526                 if (CONNECTION_OK != PQstatus (db->conn)) {
527                         c_complain (LOG_ERR, &db->conn_complaint,
528                                         "Failed to connect to database %s: %s",
529                                         db->database, PQerrorMessage (db->conn));
530                         return -1;
531                 }
532
533                 db->proto_version = PQprotocolVersion (db->conn);
534                 if (3 > db->proto_version)
535                         log_warn ("Protocol version %d does not support parameters.",
536                                         db->proto_version);
537         }
538
539         /* We might have connected to a different PostgreSQL version, so we
540          * need to reinitialize stuff. */
541         if (c_would_release (&db->conn_complaint))
542                 c_psql_database_init (db, PQserverVersion (db->conn));
543
544         c_release (LOG_INFO, &db->conn_complaint,
545                         "Successfully reconnected to database %s", PQdb (db->conn));
546         return 0;
547 } /* c_psql_check_connection */
548
549 static PGresult *c_psql_exec_query_params (c_psql_database_t *db,
550                 c_psql_query_t *query)
551 {
552         char *params[db->max_params_num];
553         char  interval[64];
554         int   i;
555
556         assert (db->max_params_num >= query->params_num);
557
558         for (i = 0; i < query->params_num; ++i) {
559                 switch (query->params[i]) {
560                         case C_PSQL_PARAM_HOST:
561                                 params[i] = C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
562                                         ? "localhost" : db->host;
563                                 break;
564                         case C_PSQL_PARAM_DB:
565                                 params[i] = db->database;
566                                 break;
567                         case C_PSQL_PARAM_USER:
568                                 params[i] = db->user;
569                                 break;
570                         case C_PSQL_PARAM_INTERVAL:
571                                 ssnprintf (interval, sizeof (interval), "%i", interval_g);
572                                 params[i] = interval;
573                                 break;
574                         default:
575                                 assert (0);
576                 }
577         }
578
579         return PQexecParams (db->conn, query->stmt, query->params_num, NULL,
580                         (const char *const *)((0 == query->params_num) ? NULL : params),
581                         NULL, NULL, /* return text data */ 0);
582 } /* c_psql_exec_query_params */
583
584 static PGresult *c_psql_exec_query_noparams (c_psql_database_t *db,
585                 c_psql_query_t *query)
586 {
587         return PQexec (db->conn, query->stmt);
588 } /* c_psql_exec_query_noparams */
589
590 static int c_psql_exec_query (c_psql_database_t *db, int idx)
591 {
592         c_psql_query_t *query;
593         PGresult       *res;
594
595         int rows, cols;
596         int i;
597
598         if (idx >= db->queries_num)
599                 return -1;
600
601         if (0 != db->hidden_queries[idx])
602                 return 0;
603
604         query = db->queries[idx];
605
606         if (3 <= db->proto_version)
607                 res = c_psql_exec_query_params (db, query);
608         else if (0 == query->params_num)
609                 res = c_psql_exec_query_noparams (db, query);
610         else {
611                 log_err ("Connection to database \"%s\" does not support parameters "
612                                 "(protocol version %d) - cannot execute query \"%s\".",
613                                 db->database, db->proto_version, query->name);
614                 return -1;
615         }
616
617         if (PGRES_TUPLES_OK != PQresultStatus (res)) {
618                 log_err ("Failed to execute SQL query: %s",
619                                 PQerrorMessage (db->conn));
620                 log_info ("SQL query was: %s", query->stmt);
621                 PQclear (res);
622                 return -1;
623         }
624
625         rows = PQntuples (res);
626         if (1 > rows) {
627                 PQclear (res);
628                 return 0;
629         }
630
631         cols = PQnfields (res);
632
633         for (i = 0; i < rows; ++i)
634                 c_psql_dispatch_row (db, query, res, i);
635         PQclear (res);
636         return 0;
637 } /* c_psql_exec_query */
638
639 static int c_psql_read (void)
640 {
641         int success = 0;
642         int i;
643
644         for (i = 0; i < databases_num; ++i) {
645                 c_psql_database_t *db = databases + i;
646
647                 int j;
648
649                 assert (NULL != db->database);
650
651                 if (0 != c_psql_check_connection (db))
652                         continue;
653
654                 for (j = 0; j < db->queries_num; ++j)
655                         c_psql_exec_query (db, j);
656
657                 ++success;
658         }
659
660         if (! success)
661                 return -1;
662         return 0;
663 } /* c_psql_read */
664
665 static int c_psql_shutdown (void)
666 {
667         int i;
668
669         if ((NULL == databases) || (0 == databases_num))
670                 return 0;
671
672         plugin_unregister_read ("postgresql");
673         plugin_unregister_shutdown ("postgresql");
674
675         for (i = 0; i < databases_num; ++i) {
676                 c_psql_database_t *db = databases + i;
677                 c_psql_database_delete (db);
678         }
679
680         sfree (databases);
681         databases_num = 0;
682
683         for (i = 0; i < queries_num; ++i) {
684                 c_psql_query_t *query = queries + i;
685                 c_psql_query_delete (query);
686         }
687
688         sfree (queries);
689         queries_num = 0;
690         return 0;
691 } /* c_psql_shutdown */
692
693 static int c_psql_init (void)
694 {
695         int i;
696
697         if ((NULL == databases) || (0 == databases_num))
698                 return 0;
699
700         for (i = 0; i < queries_num; ++i)
701                 if (0 != c_psql_query_init (queries + i)) {
702                         c_psql_shutdown ();
703                         return -1;
704                 }
705
706         for (i = 0; i < databases_num; ++i) {
707                 c_psql_database_t *db = databases + i;
708
709                 char  conninfo[4096];
710                 char *buf     = conninfo;
711                 int   buf_len = sizeof (conninfo);
712                 int   status;
713
714                 char *server_host;
715                 int   server_version;
716
717                 /* this will happen during reinitialization */
718                 if (NULL != db->conn) {
719                         c_psql_check_connection (db);
720                         continue;
721                 }
722
723                 status = ssnprintf (buf, buf_len, "dbname = '%s'", db->database);
724                 if (0 < status) {
725                         buf     += status;
726                         buf_len -= status;
727                 }
728
729                 C_PSQL_PAR_APPEND (buf, buf_len, "host",       db->host);
730                 C_PSQL_PAR_APPEND (buf, buf_len, "port",       db->port);
731                 C_PSQL_PAR_APPEND (buf, buf_len, "user",       db->user);
732                 C_PSQL_PAR_APPEND (buf, buf_len, "password",   db->password);
733                 C_PSQL_PAR_APPEND (buf, buf_len, "sslmode",    db->sslmode);
734                 C_PSQL_PAR_APPEND (buf, buf_len, "krbsrvname", db->krbsrvname);
735                 C_PSQL_PAR_APPEND (buf, buf_len, "service",    db->service);
736
737                 db->conn = PQconnectdb (conninfo);
738                 if (0 != c_psql_check_connection (db))
739                         continue;
740
741                 db->proto_version = PQprotocolVersion (db->conn);
742
743                 server_host    = PQhost (db->conn);
744                 server_version = PQserverVersion (db->conn);
745                 log_info ("Sucessfully connected to database %s (user %s) "
746                                 "at server %s%s%s (server version: %d.%d.%d, "
747                                 "protocol version: %d, pid: %d)",
748                                 PQdb (db->conn), PQuser (db->conn),
749                                 C_PSQL_SOCKET3 (server_host, PQport (db->conn)),
750                                 C_PSQL_SERVER_VERSION3 (server_version),
751                                 db->proto_version, PQbackendPID (db->conn));
752
753                 if (3 > db->proto_version)
754                         log_warn ("Protocol version %d does not support parameters.",
755                                         db->proto_version);
756
757                 c_psql_database_init (db, server_version);
758         }
759
760         plugin_register_read ("postgresql", c_psql_read);
761         plugin_register_shutdown ("postgresql", c_psql_shutdown);
762         return 0;
763 } /* c_psql_init */
764
765 static int config_set_s (char *name, char **var, const oconfig_item_t *ci)
766 {
767         if ((0 != ci->children_num) || (1 != ci->values_num)
768                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
769                 log_err ("%s expects a single string argument.", name);
770                 return 1;
771         }
772
773         sfree (*var);
774         *var = sstrdup (ci->values[0].value.string);
775         return 0;
776 } /* config_set_s */
777
778 static int config_set_i (char *name, int *var, const oconfig_item_t *ci)
779 {
780         if ((0 != ci->children_num) || (1 != ci->values_num)
781                         || (OCONFIG_TYPE_NUMBER != ci->values[0].type)) {
782                 log_err ("%s expects a single number argument.", name);
783                 return 1;
784         }
785
786         *var = (int)ci->values[0].value.number;
787         return 0;
788 } /* config_set_i */
789
790 static int config_append_array_s (char *name, char ***var, int *len,
791                 const oconfig_item_t *ci)
792 {
793         int i;
794
795         if ((0 != ci->children_num) || (1 > ci->values_num)) {
796                 log_err ("%s expects at least one argument.", name);
797                 return 1;
798         }
799
800         for (i = 0; i < ci->values_num; ++i) {
801                 if (OCONFIG_TYPE_STRING != ci->values[i].type) {
802                         log_err ("%s expects string arguments.", name);
803                         return 1;
804                 }
805         }
806
807         *len += ci->values_num;
808         if (NULL == (*var = (char **)realloc (*var, *len * sizeof (**var)))) {
809                 log_err ("Out of memory.");
810                 exit (5);
811         }
812
813         for (i = *len - ci->values_num; i < *len; ++i)
814                 (*var)[i] = sstrdup (ci->values[i].value.string);
815         return 0;
816 } /* config_append_array_s */
817
818 static int config_set_param (c_psql_query_t *query, const oconfig_item_t *ci)
819 {
820         c_psql_param_t param;
821         char          *param_str;
822
823         if ((0 != ci->children_num) || (1 != ci->values_num)
824                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
825                 log_err ("Param expects a single string argument.");
826                 return 1;
827         }
828
829         param_str = ci->values[0].value.string;
830         if (0 == strcasecmp (param_str, "hostname"))
831                 param = C_PSQL_PARAM_HOST;
832         else if (0 == strcasecmp (param_str, "database"))
833                 param = C_PSQL_PARAM_DB;
834         else if (0 == strcasecmp (param_str, "username"))
835                 param = C_PSQL_PARAM_USER;
836         else if (0 == strcasecmp (param_str, "interval"))
837                 param = C_PSQL_PARAM_INTERVAL;
838         else {
839                 log_err ("Invalid parameter \"%s\".", param_str);
840                 return 1;
841         }
842
843         ++query->params_num;
844         if (NULL == (query->params = (c_psql_param_t *)realloc (query->params,
845                                 query->params_num * sizeof (*query->params)))) {
846                 log_err ("Out of memory.");
847                 exit (5);
848         }
849
850         query->params[query->params_num - 1] = param;
851         return 0;
852 } /* config_set_param */
853
854 static int config_set_result (c_psql_query_t *query, const oconfig_item_t *ci)
855 {
856         c_psql_result_t *res;
857
858         int status = 0, i;
859
860         if (0 != ci->values_num) {
861                 log_err ("<Result> does not expect any arguments.");
862                 return 1;
863         }
864
865         res = c_psql_result_new (query);
866
867         for (i = 0; i < ci->children_num; ++i) {
868                 oconfig_item_t *c = ci->children + i;
869
870                 if (0 == strcasecmp (c->key, "Type"))
871                         config_set_s ("Type", &res->type, c);
872                 else if (0 == strcasecmp (c->key, "InstancePrefix"))
873                         config_set_s ("InstancePrefix", &res->instance_prefix, c);
874                 else if (0 == strcasecmp (c->key, "InstancesFrom"))
875                         config_append_array_s ("InstancesFrom",
876                                         &res->instances_str, &res->instances_num, c);
877                 else if (0 == strcasecmp (c->key, "ValuesFrom"))
878                         config_append_array_s ("ValuesFrom",
879                                         &res->values_str, &res->values_num, c);
880                 else
881                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
882         }
883
884         if (NULL == res->type) {
885                 log_warn ("Query \"%s\": Missing Type option in <Result> block.",
886                                 query->name);
887                 status = 1;
888         }
889
890         if (NULL == res->values_str) {
891                 log_warn ("Query \"%s\": Missing ValuesFrom option in <Result> block.",
892                                 query->name);
893                 status = 1;
894         }
895
896         if (0 != status) {
897                 c_psql_result_delete (res);
898                 --query->results_num;
899                 return status;
900         }
901
902         /* preallocate memory to cache the column numbers and data types */
903         res->values = (int *)smalloc (res->values_num * sizeof (*res->values));
904         for (i = 0; i < res->values_num; ++i)
905                 res->values[i] = -1;
906
907         res->instances = (int *)smalloc (res->instances_num
908                         * sizeof (*res->instances));
909         for (i = 0; i < res->instances_num; ++i)
910                 res->instances[i] = -1;
911
912         res->ds_types = (int *)smalloc (res->values_num
913                         * sizeof (*res->ds_types));
914         for (i = 0; i < res->values_num; ++i)
915                 res->ds_types[i] = -1;
916         return 0;
917 } /* config_set_result */
918
919 static int config_set_column (c_psql_query_t *query, int col_num,
920                 const oconfig_item_t *ci)
921 {
922         c_psql_result_t *res;
923
924         int i;
925
926         if ((0 != ci->children_num)
927                         || (1 > ci->values_num) || (2 < ci->values_num)) {
928                 log_err ("Column expects either one or two arguments.");
929                 return 1;
930         }
931
932         for (i = 0; i < ci->values_num; ++i) {
933                 if (OCONFIG_TYPE_STRING != ci->values[i].type) {
934                         log_err ("Column expects either one or two string arguments.");
935                         return 1;
936                 }
937         }
938
939         res = c_psql_result_new (query);
940
941         res->type = sstrdup (ci->values[0].value.string);
942
943         if (2 == ci->values_num)
944                 res->instance_prefix = sstrdup (ci->values[1].value.string);
945
946         res->values     = (int *)smalloc (sizeof (*res->values));
947         res->values[0]  = col_num;
948         res->ds_types   = (int *)smalloc (sizeof (*res->ds_types));
949         res->values_num = 1;
950         return 0;
951 } /* config_set_column */
952
953 static int set_query (c_psql_database_t *db, const char *name)
954 {
955         c_psql_query_t *query;
956
957         query = c_psql_query_get (name, -1);
958         if (NULL == query) {
959                 log_err ("Query \"%s\" not found - please check your configuration.",
960                                 name);
961                 return 1;
962         }
963
964         ++db->queries_num;
965         if (NULL == (db->queries = (c_psql_query_t **)realloc (db->queries,
966                                 db->queries_num * sizeof (*db->queries)))) {
967                 log_err ("Out of memory.");
968                 exit (5);
969         }
970
971         if (query->params_num > db->max_params_num)
972                 db->max_params_num = query->params_num;
973
974         db->queries[db->queries_num - 1] = query;
975         return 0;
976 } /* set_query */
977
978 static int config_set_query (c_psql_database_t *db, const oconfig_item_t *ci)
979 {
980         if ((0 != ci->children_num) || (1 != ci->values_num)
981                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
982                 log_err ("Query expects a single string argument.");
983                 return 1;
984         }
985         return set_query (db, ci->values[0].value.string);
986 } /* config_set_query */
987
988 static int c_psql_config_query (oconfig_item_t *ci)
989 {
990         c_psql_query_t *query;
991
992         int status = 0, col_num = 0, i;
993
994         if ((1 != ci->values_num)
995                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
996                 log_err ("<Query> expects a single string argument.");
997                 return 1;
998         }
999
1000         query = c_psql_query_new (ci->values[0].value.string);
1001
1002         for (i = 0; i < ci->children_num; ++i) {
1003                 oconfig_item_t *c = ci->children + i;
1004
1005                 if (0 == strcasecmp (c->key, "Statement"))
1006                         config_set_s ("Statement", &query->stmt, c);
1007                 /* backwards compat for versions < 4.6 */
1008                 else if (0 == strcasecmp (c->key, "Query")) {
1009                         log_warn ("<Query>: 'Query' is deprecated - use 'Statement' instead.");
1010                         config_set_s ("Query", &query->stmt, c);
1011                 }
1012                 else if (0 == strcasecmp (c->key, "Param"))
1013                         config_set_param (query, c);
1014                 else if (0 == strcasecmp (c->key, "Result"))
1015                         config_set_result (query, c);
1016                 /* backwards compat for versions < 4.6 */
1017                 else if (0 == strcasecmp (c->key, "Column")) {
1018                         log_warn ("<Query>: 'Column' is deprecated - "
1019                                         "use a <Result> block instead.");
1020                         config_set_column (query, col_num, c);
1021                         ++col_num;
1022                 }
1023                 else if (0 == strcasecmp (c->key, "MinPGVersion"))
1024                         config_set_i ("MinPGVersion", &query->min_pg_version, c);
1025                 else if (0 == strcasecmp (c->key, "MaxPGVersion"))
1026                         config_set_i ("MaxPGVersion", &query->max_pg_version, c);
1027                 else
1028                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1029         }
1030
1031         for (i = 0; i < queries_num - 1; ++i) {
1032                 c_psql_query_t *q = queries + i;
1033
1034                 if ((0 == strcasecmp (q->name, query->name))
1035                                 && (q->min_pg_version <= query->max_pg_version)
1036                                 && (query->min_pg_version <= q->max_pg_version)) {
1037                         log_err ("Ignoring redefinition (with overlapping version ranges) "
1038                                         "of query \"%s\".", query->name);
1039                         status = 1;
1040                         break;
1041                 }
1042         }
1043
1044         if (query->min_pg_version > query->max_pg_version) {
1045                 log_err ("Query \"%s\": MinPGVersion > MaxPGVersion.",
1046                                 query->name);
1047                 status = 1;
1048         }
1049
1050         if (NULL == query->stmt) {
1051                 log_err ("Query \"%s\" does not include an SQL query statement - "
1052                                 "please check your configuration.", query->name);
1053                 status = 1;
1054         }
1055
1056         if (0 != status) {
1057                 c_psql_query_delete (query);
1058                 --queries_num;
1059                 return status;
1060         }
1061         return 0;
1062 } /* c_psql_config_query */
1063
1064 static int c_psql_config_database (oconfig_item_t *ci)
1065 {
1066         c_psql_database_t *db;
1067
1068         int i;
1069
1070         if ((1 != ci->values_num)
1071                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
1072                 log_err ("<Database> expects a single string argument.");
1073                 return 1;
1074         }
1075
1076         db = c_psql_database_new (ci->values[0].value.string);
1077
1078         for (i = 0; i < ci->children_num; ++i) {
1079                 oconfig_item_t *c = ci->children + i;
1080
1081                 if (0 == strcasecmp (c->key, "Host"))
1082                         config_set_s ("Host", &db->host, c);
1083                 else if (0 == strcasecmp (c->key, "Port"))
1084                         config_set_s ("Port", &db->port, c);
1085                 else if (0 == strcasecmp (c->key, "User"))
1086                         config_set_s ("User", &db->user, c);
1087                 else if (0 == strcasecmp (c->key, "Password"))
1088                         config_set_s ("Password", &db->password, c);
1089                 else if (0 == strcasecmp (c->key, "SSLMode"))
1090                         config_set_s ("SSLMode", &db->sslmode, c);
1091                 else if (0 == strcasecmp (c->key, "KRBSrvName"))
1092                         config_set_s ("KRBSrvName", &db->krbsrvname, c);
1093                 else if (0 == strcasecmp (c->key, "Service"))
1094                         config_set_s ("Service", &db->service, c);
1095                 else if (0 == strcasecmp (c->key, "Query"))
1096                         config_set_query (db, c);
1097                 else
1098                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1099         }
1100
1101         if (NULL == db->queries) {
1102                 for (i = 0; i < def_queries_num; ++i)
1103                         set_query (db, def_queries[i]);
1104         }
1105
1106         db->hidden_queries = (int *)calloc (db->queries_num,
1107                         sizeof (*db->hidden_queries));
1108         if (NULL == db->hidden_queries) {
1109                 log_err ("Out of memory.");
1110                 exit (5);
1111         }
1112         return 0;
1113 } /* c_psql_config_database */
1114
1115 static int c_psql_config (oconfig_item_t *ci)
1116 {
1117         static int have_def_config = 0;
1118
1119         int i;
1120
1121         if (0 == have_def_config) {
1122                 oconfig_item_t *c;
1123
1124                 have_def_config = 1;
1125
1126                 c = oconfig_parse_file (C_PSQL_DEFAULT_CONF);
1127                 if (NULL == c)
1128                         log_err ("Failed to read default config ("C_PSQL_DEFAULT_CONF").");
1129                 else
1130                         c_psql_config (c);
1131
1132                 if (NULL == queries)
1133                         log_err ("Default config ("C_PSQL_DEFAULT_CONF") did not define "
1134                                         "any queries - please check your installation.");
1135         }
1136
1137         for (i = 0; i < ci->children_num; ++i) {
1138                 oconfig_item_t *c = ci->children + i;
1139
1140                 if (0 == strcasecmp (c->key, "Query"))
1141                         c_psql_config_query (c);
1142                 else if (0 == strcasecmp (c->key, "Database"))
1143                         c_psql_config_database (c);
1144                 else
1145                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1146         }
1147         return 0;
1148 } /* c_psql_config */
1149
1150 void module_register (void)
1151 {
1152         plugin_register_complex_config ("postgresql", c_psql_config);
1153         plugin_register_init ("postgresql", c_psql_init);
1154 } /* module_register */
1155
1156 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
1157