2814cc4ad6f3263ff7b10ca239662514b65d2d94
[collectd.git] / src / postgresql.c
1 /**
2  * collectd - src/postgresql.c
3  * Copyright (C) 2008  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         vl.time       = time (NULL);
396
397         if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
398                         || (0 == strcmp (db->host, "localhost")))
399                 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
400         else
401                 sstrncpy (vl.host, db->host, sizeof (vl.host));
402
403         sstrncpy (vl.plugin, "postgresql", sizeof (vl.plugin));
404         sstrncpy (vl.plugin_instance, db->database, sizeof (vl.plugin_instance));
405
406         sstrncpy (vl.type, res->type, sizeof (vl.type));
407
408         if (0 < instances_num) {
409                 vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
410                 strjoin (vl.type_instance, sizeof (vl.type_instance),
411                                 instances, instances_num, "-");
412
413                 if ('\0' != vl.type_instance[sizeof (vl.type_instance) - 1]) {
414                         vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
415                         log_warn ("Truncated type instance: %s.", vl.type_instance);
416                 }
417         }
418
419         plugin_dispatch_values (&vl);
420         return;
421 } /* submit */
422
423 static int c_psql_get_colnum (PGresult *pgres,
424                 char **strings, int *numbers, int idx)
425 {
426         int colnum;
427
428         if (0 <= numbers[idx])
429                 return numbers[idx];
430
431         colnum = PQfnumber (pgres, strings[idx]);
432         if (0 > colnum)
433                 log_err ("No such column: %s.", strings[idx]);
434
435         numbers[idx] = colnum;
436         return colnum;
437 } /* c_psql_get_colnum */
438
439 static void c_psql_dispatch_row (c_psql_database_t *db, c_psql_query_t *query,
440                 PGresult *pgres, int row)
441 {
442         int i;
443
444         for (i = 0; i < query->results_num; ++i) {
445                 c_psql_result_t *res = query->results + i;
446
447                 char   *instances[res->instances_num + 1];
448                 value_t values[res->values_num];
449
450                 int offset = 0, status = 0, j;
451
452                 /* get the instance name */
453                 if (NULL != res->instance_prefix) {
454                         instances[0] = res->instance_prefix;
455                         offset = 1;
456                 }
457
458                 for (j = 0; (0 == status) && (j < res->instances_num); ++j) {
459                         int col = c_psql_get_colnum (pgres,
460                                         res->instances_str, res->instances, j);
461
462                         if (0 > col) {
463                                 status = -1;
464                                 break;
465                         }
466
467                         instances[j + offset] = PQgetvalue (pgres, row, col);
468                         if (NULL == instances[j + offset])
469                                 instances[j + offset] = "";
470                 }
471
472                 /* get the values */
473                 for (j = 0; (0 == status) && (j < res->values_num); ++j) {
474                         int col = c_psql_get_colnum (pgres,
475                                         res->values_str, res->values, j);
476
477                         char *value_str;
478                         char *endptr = NULL;
479
480                         if (0 > col) {
481                                 status = -1;
482                                 break;
483                         }
484
485                         value_str = PQgetvalue (pgres, row, col);
486                         if ((NULL == value_str) || ('\0' == *value_str))
487                                 value_str = "0";
488
489                         if (res->ds_types[j] == DS_TYPE_COUNTER)
490                                 values[j].counter = (counter_t)strtoll (value_str, &endptr, 0);
491                         else if (res->ds_types[j] == DS_TYPE_GAUGE)
492                                 values[j].gauge = (gauge_t)strtod (value_str, &endptr);
493                         else {
494                                 log_err ("Invalid type \"%s\" (%i).",
495                                                 res->type, res->ds_types[j]);
496                         }
497
498                         if (value_str == endptr) {
499                                 log_err ("Failed to parse string as number: %s.", value_str);
500                                 status = -1;
501                                 break;
502                         }
503                         else if ((NULL != endptr) && ('\0' != *endptr))
504                                 log_warn ("Ignoring trailing garbage after number: %s.",
505                                                 endptr);
506                 }
507
508                 if (0 != status)
509                         continue;
510
511                 submit (db, res, instances, values);
512         }
513 } /* c_psql_dispatch_row */
514
515 static int c_psql_check_connection (c_psql_database_t *db)
516 {
517         /* "ping" */
518         PQclear (PQexec (db->conn, "SELECT 42;"));
519
520         if (CONNECTION_OK != PQstatus (db->conn)) {
521                 PQreset (db->conn);
522
523                 /* trigger c_release() */
524                 if (0 == db->conn_complaint.interval)
525                         db->conn_complaint.interval = 1;
526
527                 if (CONNECTION_OK != PQstatus (db->conn)) {
528                         c_complain (LOG_ERR, &db->conn_complaint,
529                                         "Failed to connect to database %s: %s",
530                                         db->database, PQerrorMessage (db->conn));
531                         return -1;
532                 }
533
534                 db->proto_version = PQprotocolVersion (db->conn);
535                 if (3 > db->proto_version)
536                         log_warn ("Protocol version %d does not support parameters.",
537                                         db->proto_version);
538         }
539
540         /* We might have connected to a different PostgreSQL version, so we
541          * need to reinitialize stuff. */
542         if (c_would_release (&db->conn_complaint))
543                 c_psql_database_init (db, PQserverVersion (db->conn));
544
545         c_release (LOG_INFO, &db->conn_complaint,
546                         "Successfully reconnected to database %s", PQdb (db->conn));
547         return 0;
548 } /* c_psql_check_connection */
549
550 static PGresult *c_psql_exec_query_params (c_psql_database_t *db,
551                 c_psql_query_t *query)
552 {
553         char *params[db->max_params_num];
554         char  interval[64];
555         int   i;
556
557         assert (db->max_params_num >= query->params_num);
558
559         for (i = 0; i < query->params_num; ++i) {
560                 switch (query->params[i]) {
561                         case C_PSQL_PARAM_HOST:
562                                 params[i] = C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
563                                         ? "localhost" : db->host;
564                                 break;
565                         case C_PSQL_PARAM_DB:
566                                 params[i] = db->database;
567                                 break;
568                         case C_PSQL_PARAM_USER:
569                                 params[i] = db->user;
570                                 break;
571                         case C_PSQL_PARAM_INTERVAL:
572                                 ssnprintf (interval, sizeof (interval), "%i", interval_g);
573                                 params[i] = interval;
574                                 break;
575                         default:
576                                 assert (0);
577                 }
578         }
579
580         return PQexecParams (db->conn, query->stmt, query->params_num, NULL,
581                         (const char *const *)((0 == query->params_num) ? NULL : params),
582                         NULL, NULL, /* return text data */ 0);
583 } /* c_psql_exec_query_params */
584
585 static PGresult *c_psql_exec_query_noparams (c_psql_database_t *db,
586                 c_psql_query_t *query)
587 {
588         return PQexec (db->conn, query->stmt);
589 } /* c_psql_exec_query_noparams */
590
591 static int c_psql_exec_query (c_psql_database_t *db, int idx)
592 {
593         c_psql_query_t *query;
594         PGresult       *res;
595
596         int rows, cols;
597         int i;
598
599         if (idx >= db->queries_num)
600                 return -1;
601
602         if (0 != db->hidden_queries[idx])
603                 return 0;
604
605         query = db->queries[idx];
606
607         if (3 <= db->proto_version)
608                 res = c_psql_exec_query_params (db, query);
609         else if (0 == query->params_num)
610                 res = c_psql_exec_query_noparams (db, query);
611         else {
612                 log_err ("Connection to database \"%s\" does not support parameters "
613                                 "(protocol version %d) - cannot execute query \"%s\".",
614                                 db->database, db->proto_version, query->name);
615                 return -1;
616         }
617
618         if (PGRES_TUPLES_OK != PQresultStatus (res)) {
619                 log_err ("Failed to execute SQL query: %s",
620                                 PQerrorMessage (db->conn));
621                 log_info ("SQL query was: %s", query->stmt);
622                 PQclear (res);
623                 return -1;
624         }
625
626         rows = PQntuples (res);
627         if (1 > rows) {
628                 PQclear (res);
629                 return 0;
630         }
631
632         cols = PQnfields (res);
633
634         for (i = 0; i < rows; ++i)
635                 c_psql_dispatch_row (db, query, res, i);
636         PQclear (res);
637         return 0;
638 } /* c_psql_exec_query */
639
640 static int c_psql_read (void)
641 {
642         int success = 0;
643         int i;
644
645         for (i = 0; i < databases_num; ++i) {
646                 c_psql_database_t *db = databases + i;
647
648                 int j;
649
650                 assert (NULL != db->database);
651
652                 if (0 != c_psql_check_connection (db))
653                         continue;
654
655                 for (j = 0; j < db->queries_num; ++j)
656                         c_psql_exec_query (db, j);
657
658                 ++success;
659         }
660
661         if (! success)
662                 return -1;
663         return 0;
664 } /* c_psql_read */
665
666 static int c_psql_shutdown (void)
667 {
668         int i;
669
670         if ((NULL == databases) || (0 == databases_num))
671                 return 0;
672
673         plugin_unregister_read ("postgresql");
674         plugin_unregister_shutdown ("postgresql");
675
676         for (i = 0; i < databases_num; ++i) {
677                 c_psql_database_t *db = databases + i;
678                 c_psql_database_delete (db);
679         }
680
681         sfree (databases);
682         databases_num = 0;
683
684         for (i = 0; i < queries_num; ++i) {
685                 c_psql_query_t *query = queries + i;
686                 c_psql_query_delete (query);
687         }
688
689         sfree (queries);
690         queries_num = 0;
691         return 0;
692 } /* c_psql_shutdown */
693
694 static int c_psql_init (void)
695 {
696         int i;
697
698         if ((NULL == databases) || (0 == databases_num))
699                 return 0;
700
701         for (i = 0; i < queries_num; ++i)
702                 if (0 != c_psql_query_init (queries + i)) {
703                         c_psql_shutdown ();
704                         return -1;
705                 }
706
707         for (i = 0; i < databases_num; ++i) {
708                 c_psql_database_t *db = databases + i;
709
710                 char  conninfo[4096];
711                 char *buf     = conninfo;
712                 int   buf_len = sizeof (conninfo);
713                 int   status;
714
715                 char *server_host;
716                 int   server_version;
717
718                 /* this will happen during reinitialization */
719                 if (NULL != db->conn) {
720                         c_psql_check_connection (db);
721                         continue;
722                 }
723
724                 status = ssnprintf (buf, buf_len, "dbname = '%s'", db->database);
725                 if (0 < status) {
726                         buf     += status;
727                         buf_len -= status;
728                 }
729
730                 C_PSQL_PAR_APPEND (buf, buf_len, "host",       db->host);
731                 C_PSQL_PAR_APPEND (buf, buf_len, "port",       db->port);
732                 C_PSQL_PAR_APPEND (buf, buf_len, "user",       db->user);
733                 C_PSQL_PAR_APPEND (buf, buf_len, "password",   db->password);
734                 C_PSQL_PAR_APPEND (buf, buf_len, "sslmode",    db->sslmode);
735                 C_PSQL_PAR_APPEND (buf, buf_len, "krbsrvname", db->krbsrvname);
736                 C_PSQL_PAR_APPEND (buf, buf_len, "service",    db->service);
737
738                 db->conn = PQconnectdb (conninfo);
739                 if (0 != c_psql_check_connection (db))
740                         continue;
741
742                 db->proto_version = PQprotocolVersion (db->conn);
743
744                 server_host    = PQhost (db->conn);
745                 server_version = PQserverVersion (db->conn);
746                 log_info ("Sucessfully connected to database %s (user %s) "
747                                 "at server %s%s%s (server version: %d.%d.%d, "
748                                 "protocol version: %d, pid: %d)",
749                                 PQdb (db->conn), PQuser (db->conn),
750                                 C_PSQL_SOCKET3 (server_host, PQport (db->conn)),
751                                 C_PSQL_SERVER_VERSION3 (server_version),
752                                 db->proto_version, PQbackendPID (db->conn));
753
754                 if (3 > db->proto_version)
755                         log_warn ("Protocol version %d does not support parameters.",
756                                         db->proto_version);
757
758                 c_psql_database_init (db, server_version);
759         }
760
761         plugin_register_read ("postgresql", c_psql_read);
762         plugin_register_shutdown ("postgresql", c_psql_shutdown);
763         return 0;
764 } /* c_psql_init */
765
766 static int config_set_s (char *name, char **var, const oconfig_item_t *ci)
767 {
768         if ((0 != ci->children_num) || (1 != ci->values_num)
769                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
770                 log_err ("%s expects a single string argument.", name);
771                 return 1;
772         }
773
774         sfree (*var);
775         *var = sstrdup (ci->values[0].value.string);
776         return 0;
777 } /* config_set_s */
778
779 static int config_set_i (char *name, int *var, const oconfig_item_t *ci)
780 {
781         if ((0 != ci->children_num) || (1 != ci->values_num)
782                         || (OCONFIG_TYPE_NUMBER != ci->values[0].type)) {
783                 log_err ("%s expects a single number argument.", name);
784                 return 1;
785         }
786
787         *var = (int)ci->values[0].value.number;
788         return 0;
789 } /* config_set_i */
790
791 static int config_append_array_s (char *name, char ***var, int *len,
792                 const oconfig_item_t *ci)
793 {
794         int i;
795
796         if ((0 != ci->children_num) || (1 > ci->values_num)) {
797                 log_err ("%s expects at least one argument.", name);
798                 return 1;
799         }
800
801         for (i = 0; i < ci->values_num; ++i) {
802                 if (OCONFIG_TYPE_STRING != ci->values[i].type) {
803                         log_err ("%s expects string arguments.", name);
804                         return 1;
805                 }
806         }
807
808         *len += ci->values_num;
809         if (NULL == (*var = (char **)realloc (*var, *len * sizeof (**var)))) {
810                 log_err ("Out of memory.");
811                 exit (5);
812         }
813
814         for (i = *len - ci->values_num; i < *len; ++i)
815                 (*var)[i] = sstrdup (ci->values[i].value.string);
816         return 0;
817 } /* config_append_array_s */
818
819 static int config_set_param (c_psql_query_t *query, const oconfig_item_t *ci)
820 {
821         c_psql_param_t param;
822         char          *param_str;
823
824         if ((0 != ci->children_num) || (1 != ci->values_num)
825                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
826                 log_err ("Param expects a single string argument.");
827                 return 1;
828         }
829
830         param_str = ci->values[0].value.string;
831         if (0 == strcasecmp (param_str, "hostname"))
832                 param = C_PSQL_PARAM_HOST;
833         else if (0 == strcasecmp (param_str, "database"))
834                 param = C_PSQL_PARAM_DB;
835         else if (0 == strcasecmp (param_str, "username"))
836                 param = C_PSQL_PARAM_USER;
837         else if (0 == strcasecmp (param_str, "interval"))
838                 param = C_PSQL_PARAM_INTERVAL;
839         else {
840                 log_err ("Invalid parameter \"%s\".", param_str);
841                 return 1;
842         }
843
844         ++query->params_num;
845         if (NULL == (query->params = (c_psql_param_t *)realloc (query->params,
846                                 query->params_num * sizeof (*query->params)))) {
847                 log_err ("Out of memory.");
848                 exit (5);
849         }
850
851         query->params[query->params_num - 1] = param;
852         return 0;
853 } /* config_set_param */
854
855 static int config_set_result (c_psql_query_t *query, const oconfig_item_t *ci)
856 {
857         c_psql_result_t *res;
858
859         int status = 0, i;
860
861         if (0 != ci->values_num) {
862                 log_err ("<Result> does not expect any arguments.");
863                 return 1;
864         }
865
866         res = c_psql_result_new (query);
867
868         for (i = 0; i < ci->children_num; ++i) {
869                 oconfig_item_t *c = ci->children + i;
870
871                 if (0 == strcasecmp (c->key, "Type"))
872                         config_set_s ("Type", &res->type, c);
873                 else if (0 == strcasecmp (c->key, "InstancePrefix"))
874                         config_set_s ("InstancePrefix", &res->instance_prefix, c);
875                 else if (0 == strcasecmp (c->key, "InstancesFrom"))
876                         config_append_array_s ("InstancesFrom",
877                                         &res->instances_str, &res->instances_num, c);
878                 else if (0 == strcasecmp (c->key, "ValuesFrom"))
879                         config_append_array_s ("ValuesFrom",
880                                         &res->values_str, &res->values_num, c);
881                 else
882                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
883         }
884
885         if (NULL == res->type) {
886                 log_warn ("Query \"%s\": Missing Type option in <Result> block.",
887                                 query->name);
888                 status = 1;
889         }
890
891         if (NULL == res->values_str) {
892                 log_warn ("Query \"%s\": Missing ValuesFrom option in <Result> block.",
893                                 query->name);
894                 status = 1;
895         }
896
897         if (0 != status) {
898                 c_psql_result_delete (res);
899                 --query->results_num;
900                 return status;
901         }
902
903         /* preallocate memory to cache the column numbers and data types */
904         res->values = (int *)smalloc (res->values_num * sizeof (*res->values));
905         for (i = 0; i < res->values_num; ++i)
906                 res->values[i] = -1;
907
908         res->instances = (int *)smalloc (res->instances_num
909                         * sizeof (*res->instances));
910         for (i = 0; i < res->instances_num; ++i)
911                 res->instances[i] = -1;
912
913         res->ds_types = (int *)smalloc (res->values_num
914                         * sizeof (*res->ds_types));
915         for (i = 0; i < res->values_num; ++i)
916                 res->ds_types[i] = -1;
917         return 0;
918 } /* config_set_result */
919
920 static int config_set_column (c_psql_query_t *query, int col_num,
921                 const oconfig_item_t *ci)
922 {
923         c_psql_result_t *res;
924
925         int i;
926
927         if ((0 != ci->children_num)
928                         || (1 > ci->values_num) || (2 < ci->values_num)) {
929                 log_err ("Column expects either one or two arguments.");
930                 return 1;
931         }
932
933         for (i = 0; i < ci->values_num; ++i) {
934                 if (OCONFIG_TYPE_STRING != ci->values[i].type) {
935                         log_err ("Column expects either one or two string arguments.");
936                         return 1;
937                 }
938         }
939
940         res = c_psql_result_new (query);
941
942         res->type = sstrdup (ci->values[0].value.string);
943
944         if (2 == ci->values_num)
945                 res->instance_prefix = sstrdup (ci->values[1].value.string);
946
947         res->values     = (int *)smalloc (sizeof (*res->values));
948         res->values[0]  = col_num;
949         res->ds_types   = (int *)smalloc (sizeof (*res->ds_types));
950         res->values_num = 1;
951         return 0;
952 } /* config_set_column */
953
954 static int set_query (c_psql_database_t *db, const char *name)
955 {
956         c_psql_query_t *query;
957
958         query = c_psql_query_get (name, -1);
959         if (NULL == query) {
960                 log_err ("Query \"%s\" not found - please check your configuration.",
961                                 name);
962                 return 1;
963         }
964
965         ++db->queries_num;
966         if (NULL == (db->queries = (c_psql_query_t **)realloc (db->queries,
967                                 db->queries_num * sizeof (*db->queries)))) {
968                 log_err ("Out of memory.");
969                 exit (5);
970         }
971
972         if (query->params_num > db->max_params_num)
973                 db->max_params_num = query->params_num;
974
975         db->queries[db->queries_num - 1] = query;
976         return 0;
977 } /* set_query */
978
979 static int config_set_query (c_psql_database_t *db, const oconfig_item_t *ci)
980 {
981         if ((0 != ci->children_num) || (1 != ci->values_num)
982                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
983                 log_err ("Query expects a single string argument.");
984                 return 1;
985         }
986         return set_query (db, ci->values[0].value.string);
987 } /* config_set_query */
988
989 static int c_psql_config_query (oconfig_item_t *ci)
990 {
991         c_psql_query_t *query;
992
993         int status = 0, col_num = 0, i;
994
995         if ((1 != ci->values_num)
996                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
997                 log_err ("<Query> expects a single string argument.");
998                 return 1;
999         }
1000
1001         query = c_psql_query_new (ci->values[0].value.string);
1002
1003         for (i = 0; i < ci->children_num; ++i) {
1004                 oconfig_item_t *c = ci->children + i;
1005
1006                 if (0 == strcasecmp (c->key, "Statement"))
1007                         config_set_s ("Statement", &query->stmt, c);
1008                 /* backwards compat for versions < 4.6 */
1009                 else if (0 == strcasecmp (c->key, "Query")) {
1010                         log_warn ("<Query>: 'Query' is deprecated - use 'Statement' instead.");
1011                         config_set_s ("Query", &query->stmt, c);
1012                 }
1013                 else if (0 == strcasecmp (c->key, "Param"))
1014                         config_set_param (query, c);
1015                 else if (0 == strcasecmp (c->key, "Result"))
1016                         config_set_result (query, c);
1017                 /* backwards compat for versions < 4.6 */
1018                 else if (0 == strcasecmp (c->key, "Column")) {
1019                         log_warn ("<Query>: 'Column' is deprecated - "
1020                                         "use a <Result> block instead.");
1021                         config_set_column (query, col_num, c);
1022                         ++col_num;
1023                 }
1024                 else if (0 == strcasecmp (c->key, "MinPGVersion"))
1025                         config_set_i ("MinPGVersion", &query->min_pg_version, c);
1026                 else if (0 == strcasecmp (c->key, "MaxPGVersion"))
1027                         config_set_i ("MaxPGVersion", &query->max_pg_version, c);
1028                 else
1029                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1030         }
1031
1032         for (i = 0; i < queries_num - 1; ++i) {
1033                 c_psql_query_t *q = queries + i;
1034
1035                 if ((0 == strcasecmp (q->name, query->name))
1036                                 && (q->min_pg_version <= query->max_pg_version)
1037                                 && (query->min_pg_version <= q->max_pg_version)) {
1038                         log_err ("Ignoring redefinition (with overlapping version ranges) "
1039                                         "of query \"%s\".", query->name);
1040                         status = 1;
1041                         break;
1042                 }
1043         }
1044
1045         if (query->min_pg_version > query->max_pg_version) {
1046                 log_err ("Query \"%s\": MinPGVersion > MaxPGVersion.",
1047                                 query->name);
1048                 status = 1;
1049         }
1050
1051         if (NULL == query->stmt) {
1052                 log_err ("Query \"%s\" does not include an SQL query statement - "
1053                                 "please check your configuration.", query->name);
1054                 status = 1;
1055         }
1056
1057         if (0 != status) {
1058                 c_psql_query_delete (query);
1059                 --queries_num;
1060                 return status;
1061         }
1062         return 0;
1063 } /* c_psql_config_query */
1064
1065 static int c_psql_config_database (oconfig_item_t *ci)
1066 {
1067         c_psql_database_t *db;
1068
1069         int i;
1070
1071         if ((1 != ci->values_num)
1072                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
1073                 log_err ("<Database> expects a single string argument.");
1074                 return 1;
1075         }
1076
1077         db = c_psql_database_new (ci->values[0].value.string);
1078
1079         for (i = 0; i < ci->children_num; ++i) {
1080                 oconfig_item_t *c = ci->children + i;
1081
1082                 if (0 == strcasecmp (c->key, "Host"))
1083                         config_set_s ("Host", &db->host, c);
1084                 else if (0 == strcasecmp (c->key, "Port"))
1085                         config_set_s ("Port", &db->port, c);
1086                 else if (0 == strcasecmp (c->key, "User"))
1087                         config_set_s ("User", &db->user, c);
1088                 else if (0 == strcasecmp (c->key, "Password"))
1089                         config_set_s ("Password", &db->password, c);
1090                 else if (0 == strcasecmp (c->key, "SSLMode"))
1091                         config_set_s ("SSLMode", &db->sslmode, c);
1092                 else if (0 == strcasecmp (c->key, "KRBSrvName"))
1093                         config_set_s ("KRBSrvName", &db->krbsrvname, c);
1094                 else if (0 == strcasecmp (c->key, "Service"))
1095                         config_set_s ("Service", &db->service, c);
1096                 else if (0 == strcasecmp (c->key, "Query"))
1097                         config_set_query (db, c);
1098                 else
1099                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1100         }
1101
1102         if (NULL == db->queries) {
1103                 for (i = 0; i < def_queries_num; ++i)
1104                         set_query (db, def_queries[i]);
1105         }
1106
1107         db->hidden_queries = (int *)calloc (db->queries_num,
1108                         sizeof (*db->hidden_queries));
1109         if (NULL == db->hidden_queries) {
1110                 log_err ("Out of memory.");
1111                 exit (5);
1112         }
1113         return 0;
1114 } /* c_psql_config_database */
1115
1116 static int c_psql_config (oconfig_item_t *ci)
1117 {
1118         static int have_def_config = 0;
1119
1120         int i;
1121
1122         if (0 == have_def_config) {
1123                 oconfig_item_t *c;
1124
1125                 have_def_config = 1;
1126
1127                 c = oconfig_parse_file (C_PSQL_DEFAULT_CONF);
1128                 if (NULL == c)
1129                         log_err ("Failed to read default config ("C_PSQL_DEFAULT_CONF").");
1130                 else
1131                         c_psql_config (c);
1132
1133                 if (NULL == queries)
1134                         log_err ("Default config ("C_PSQL_DEFAULT_CONF") did not define "
1135                                         "any queries - please check your installation.");
1136         }
1137
1138         for (i = 0; i < ci->children_num; ++i) {
1139                 oconfig_item_t *c = ci->children + i;
1140
1141                 if (0 == strcasecmp (c->key, "Query"))
1142                         c_psql_config_query (c);
1143                 else if (0 == strcasecmp (c->key, "Database"))
1144                         c_psql_config_database (c);
1145                 else
1146                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
1147         }
1148         return 0;
1149 } /* c_psql_config */
1150
1151 void module_register (void)
1152 {
1153         plugin_register_complex_config ("postgresql", c_psql_config);
1154         plugin_register_init ("postgresql", c_psql_init);
1155 } /* module_register */
1156
1157 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
1158