postgresql plugin: Moved pg_stat_database query to postgresql_default.conf.
[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_t;
84
85 typedef struct {
86         char *type;
87         char *type_instance;
88         int   ds_type;
89 } c_psql_col_t;
90
91 typedef struct {
92         char *name;
93         char *query;
94
95         c_psql_param_t *params;
96         int             params_num;
97
98         c_psql_col_t *cols;
99         int           cols_num;
100 } c_psql_query_t;
101
102 typedef struct {
103         PGconn      *conn;
104         c_complain_t conn_complaint;
105
106         int max_params_num;
107
108         /* user configuration */
109         c_psql_query_t **queries;
110         int              queries_num;
111
112         char *host;
113         char *port;
114         char *database;
115         char *user;
116         char *password;
117
118         char *sslmode;
119
120         char *krbsrvname;
121
122         char *service;
123 } c_psql_database_t;
124
125 static char *def_queries[] = {
126         "database",
127         "user_tables",
128         "io_user_tables"
129 };
130 static int def_queries_num = STATIC_ARRAY_SIZE (def_queries);
131
132 static c_psql_query_t *queries          = NULL;
133 static int             queries_num      = 0;
134
135 static c_psql_database_t *databases     = NULL;
136 static int                databases_num = 0;
137
138 static c_psql_query_t *c_psql_query_new (const char *name)
139 {
140         c_psql_query_t *query;
141
142         ++queries_num;
143         if (NULL == (queries = (c_psql_query_t *)realloc (queries,
144                                 queries_num * sizeof (*queries)))) {
145                 log_err ("Out of memory.");
146                 exit (5);
147         }
148         query = queries + queries_num - 1;
149
150         query->name  = sstrdup (name);
151         query->query = NULL;
152
153         query->params     = NULL;
154         query->params_num = 0;
155
156         query->cols     = NULL;
157         query->cols_num = 0;
158         return query;
159 } /* c_psql_query_new */
160
161 static void c_psql_query_delete (c_psql_query_t *query)
162 {
163         int i;
164
165         sfree (query->name);
166         sfree (query->query);
167
168         sfree (query->params);
169         query->params_num = 0;
170
171         for (i = 0; i < query->cols_num; ++i) {
172                 sfree (query->cols[i].type);
173                 sfree (query->cols[i].type_instance);
174         }
175         sfree (query->cols);
176         query->cols_num = 0;
177         return;
178 } /* c_psql_query_delete */
179
180 static c_psql_query_t *c_psql_query_get (const char *name)
181 {
182         int i;
183
184         for (i = 0; i < queries_num; ++i)
185                 if (0 == strcasecmp (name, queries[i].name))
186                         return queries + i;
187         return NULL;
188 } /* c_psql_query_get */
189
190 static c_psql_database_t *c_psql_database_new (const char *name)
191 {
192         c_psql_database_t *db;
193
194         ++databases_num;
195         if (NULL == (databases = (c_psql_database_t *)realloc (databases,
196                                 databases_num * sizeof (*databases)))) {
197                 log_err ("Out of memory.");
198                 exit (5);
199         }
200
201         db = databases + (databases_num - 1);
202
203         db->conn = NULL;
204
205         db->conn_complaint.last     = 0;
206         db->conn_complaint.interval = 0;
207
208         db->max_params_num = 0;
209
210         db->queries     = NULL;
211         db->queries_num = 0;
212
213         db->database   = sstrdup (name);
214         db->host       = NULL;
215         db->port       = NULL;
216         db->user       = NULL;
217         db->password   = NULL;
218
219         db->sslmode    = NULL;
220
221         db->krbsrvname = NULL;
222
223         db->service    = NULL;
224         return db;
225 } /* c_psql_database_new */
226
227 static void c_psql_database_delete (c_psql_database_t *db)
228 {
229         PQfinish (db->conn);
230
231         sfree (db->queries);
232         db->queries_num = 0;
233
234         sfree (db->database);
235         sfree (db->host);
236         sfree (db->port);
237         sfree (db->user);
238         sfree (db->password);
239
240         sfree (db->sslmode);
241
242         sfree (db->krbsrvname);
243
244         sfree (db->service);
245         return;
246 } /* c_psql_database_delete */
247
248 static void submit (const c_psql_database_t *db,
249                 const char *type, const char *type_instance,
250                 value_t *values, size_t values_len)
251 {
252         value_list_t vl = VALUE_LIST_INIT;
253
254         vl.values     = values;
255         vl.values_len = values_len;
256         vl.time       = time (NULL);
257
258         if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
259                         || (0 == strcmp (db->host, "localhost")))
260                 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
261         else
262                 sstrncpy (vl.host, db->host, sizeof (vl.host));
263
264         sstrncpy (vl.plugin, "postgresql", sizeof (vl.plugin));
265         sstrncpy (vl.plugin_instance, db->database, sizeof (vl.plugin_instance));
266
267         sstrncpy (vl.type, type, sizeof (vl.type));
268
269         if (NULL != type_instance)
270                 sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
271
272         plugin_dispatch_values (&vl);
273         return;
274 } /* submit */
275
276 static void submit_counter (const c_psql_database_t *db,
277                 const char *type, const char *type_instance,
278                 const char *value)
279 {
280         value_t values[1];
281
282         if ((NULL == value) || ('\0' == *value))
283                 return;
284
285         values[0].counter = atoll (value);
286         submit (db, type, type_instance, values, 1);
287         return;
288 } /* submit_counter */
289
290 static void submit_gauge (const c_psql_database_t *db,
291                 const char *type, const char *type_instance,
292                 const char *value)
293 {
294         value_t values[1];
295
296         if ((NULL == value) || ('\0' == *value))
297                 return;
298
299         values[0].gauge = atof (value);
300         submit (db, type, type_instance, values, 1);
301         return;
302 } /* submit_gauge */
303
304 static int c_psql_check_connection (c_psql_database_t *db)
305 {
306         /* "ping" */
307         PQclear (PQexec (db->conn, "SELECT 42;"));
308
309         if (CONNECTION_OK != PQstatus (db->conn)) {
310                 PQreset (db->conn);
311
312                 /* trigger c_release() */
313                 if (0 == db->conn_complaint.interval)
314                         db->conn_complaint.interval = 1;
315
316                 if (CONNECTION_OK != PQstatus (db->conn)) {
317                         c_complain (LOG_ERR, &db->conn_complaint,
318                                         "Failed to connect to database %s: %s",
319                                         db->database, PQerrorMessage (db->conn));
320                         return -1;
321                 }
322         }
323
324         c_release (LOG_INFO, &db->conn_complaint,
325                         "Successfully reconnected to database %s", PQdb (db->conn));
326         return 0;
327 } /* c_psql_check_connection */
328
329 static int c_psql_exec_query (c_psql_database_t *db, int idx)
330 {
331         c_psql_query_t *query;
332         PGresult       *res;
333
334         char *params[db->max_params_num];
335
336         int rows, cols;
337         int i;
338
339         if (idx >= db->queries_num)
340                 return -1;
341
342         query = db->queries[idx];
343
344         assert (db->max_params_num >= query->params_num);
345
346         for (i = 0; i < query->params_num; ++i) {
347                 switch (query->params[i]) {
348                         case C_PSQL_PARAM_HOST:
349                                 params[i] = C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
350                                         ? "localhost" : db->host;
351                                 break;
352                         case C_PSQL_PARAM_DB:
353                                 params[i] = db->database;
354                                 break;
355                         case C_PSQL_PARAM_USER:
356                                 params[i] = db->user;
357                                 break;
358                         default:
359                                 assert (0);
360                 }
361         }
362
363         res = PQexecParams (db->conn, query->query, query->params_num, NULL,
364                         (const char *const *)((0 == query->params_num) ? NULL : params),
365                         NULL, NULL, /* return text data */ 0);
366
367         if (PGRES_TUPLES_OK != PQresultStatus (res)) {
368                 log_err ("Failed to execute SQL query: %s",
369                                 PQerrorMessage (db->conn));
370                 log_info ("SQL query was: %s", query->query);
371                 PQclear (res);
372                 return -1;
373         }
374
375         rows = PQntuples (res);
376         if (1 > rows)
377                 return 0;
378
379         cols = PQnfields (res);
380         if (query->cols_num != cols) {
381                 log_err ("SQL query returned wrong number of fields "
382                                 "(expected: %i, got: %i)", query->cols_num, cols);
383                 log_info ("SQL query was: %s", query->query);
384                 return -1;
385         }
386
387         for (i = 0; i < rows; ++i) {
388                 int j;
389
390                 for (j = 0; j < cols; ++j) {
391                         c_psql_col_t col = query->cols[j];
392
393                         char *value = PQgetvalue (res, i, j);
394
395                         if (col.ds_type == DS_TYPE_COUNTER)
396                                 submit_counter (db, col.type, col.type_instance, value);
397                         else if (col.ds_type == DS_TYPE_GAUGE)
398                                 submit_gauge (db, col.type, col.type_instance, value);
399                 }
400         }
401         return 0;
402 } /* c_psql_exec_query */
403
404 static int c_psql_read (void)
405 {
406         int success = 0;
407         int i;
408
409         for (i = 0; i < databases_num; ++i) {
410                 c_psql_database_t *db = databases + i;
411
412                 int j;
413
414                 assert (NULL != db->database);
415
416                 if (0 != c_psql_check_connection (db))
417                         continue;
418
419                 for (j = 0; j < db->queries_num; ++j)
420                         c_psql_exec_query (db, j);
421
422                 ++success;
423         }
424
425         if (! success)
426                 return -1;
427         return 0;
428 } /* c_psql_read */
429
430 static int c_psql_shutdown (void)
431 {
432         int i;
433
434         if ((NULL == databases) || (0 == databases_num))
435                 return 0;
436
437         plugin_unregister_read ("postgresql");
438         plugin_unregister_shutdown ("postgresql");
439
440         for (i = 0; i < databases_num; ++i) {
441                 c_psql_database_t *db = databases + i;
442                 c_psql_database_delete (db);
443         }
444
445         sfree (databases);
446         databases_num = 0;
447
448         for (i = 0; i < queries_num; ++i) {
449                 c_psql_query_t *query = queries + i;
450                 c_psql_query_delete (query);
451         }
452
453         sfree (queries);
454         queries_num = 0;
455         return 0;
456 } /* c_psql_shutdown */
457
458 static int c_psql_init (void)
459 {
460         int i;
461
462         if ((NULL == databases) || (0 == databases_num))
463                 return 0;
464
465         for (i = 0; i < queries_num; ++i) {
466                 c_psql_query_t *query = queries + i;
467                 int j;
468
469                 for (j = 0; j < query->cols_num; ++j) {
470                         c_psql_col_t     *col = query->cols + j;
471                         const data_set_t *ds;
472
473                         ds = plugin_get_ds (col->type);
474                         if (NULL == ds) {
475                                 log_err ("Column: Unknown type \"%s\".", col->type);
476                                 c_psql_shutdown ();
477                                 return -1;
478                         }
479
480                         if (1 != ds->ds_num) {
481                                 log_err ("Column: Invalid type \"%s\" - types defining "
482                                                 "one data source are supported only (got: %i).",
483                                                 col->type, ds->ds_num);
484                                 c_psql_shutdown ();
485                                 return -1;
486                         }
487
488                         col->ds_type = ds->ds[0].type;
489                 }
490         }
491
492         for (i = 0; i < databases_num; ++i) {
493                 c_psql_database_t *db = databases + i;
494
495                 char  conninfo[4096];
496                 char *buf     = conninfo;
497                 int   buf_len = sizeof (conninfo);
498                 int   status;
499
500                 char *server_host;
501                 int   server_version;
502
503                 status = ssnprintf (buf, buf_len, "dbname = '%s'", db->database);
504                 if (0 < status) {
505                         buf     += status;
506                         buf_len -= status;
507                 }
508
509                 C_PSQL_PAR_APPEND (buf, buf_len, "host",       db->host);
510                 C_PSQL_PAR_APPEND (buf, buf_len, "port",       db->port);
511                 C_PSQL_PAR_APPEND (buf, buf_len, "user",       db->user);
512                 C_PSQL_PAR_APPEND (buf, buf_len, "password",   db->password);
513                 C_PSQL_PAR_APPEND (buf, buf_len, "sslmode",    db->sslmode);
514                 C_PSQL_PAR_APPEND (buf, buf_len, "krbsrvname", db->krbsrvname);
515                 C_PSQL_PAR_APPEND (buf, buf_len, "service",    db->service);
516
517                 db->conn = PQconnectdb (conninfo);
518                 if (0 != c_psql_check_connection (db))
519                         continue;
520
521                 server_host    = PQhost (db->conn);
522                 server_version = PQserverVersion (db->conn);
523                 log_info ("Sucessfully connected to database %s (user %s) "
524                                 "at server %s%s%s (server version: %d.%d.%d, "
525                                 "protocol version: %d, pid: %d)",
526                                 PQdb (db->conn), PQuser (db->conn),
527                                 C_PSQL_SOCKET3 (server_host, PQport (db->conn)),
528                                 C_PSQL_SERVER_VERSION3 (server_version),
529                                 PQprotocolVersion (db->conn), PQbackendPID (db->conn));
530         }
531
532         plugin_register_read ("postgresql", c_psql_read);
533         plugin_register_shutdown ("postgresql", c_psql_shutdown);
534         return 0;
535 } /* c_psql_init */
536
537 static int config_set (char *name, char **var, const oconfig_item_t *ci)
538 {
539         if ((0 != ci->children_num) || (1 != ci->values_num)
540                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
541                 log_err ("%s expects a single string argument.", name);
542                 return 1;
543         }
544
545         sfree (*var);
546         *var = sstrdup (ci->values[0].value.string);
547         return 0;
548 } /* config_set */
549
550 static int config_set_param (c_psql_query_t *query, const oconfig_item_t *ci)
551 {
552         c_psql_param_t param;
553         char          *param_str;
554
555         if ((0 != ci->children_num) || (1 != ci->values_num)
556                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
557                 log_err ("Param expects a single string argument.");
558                 return 1;
559         }
560
561         param_str = ci->values[0].value.string;
562         if (0 == strcasecmp (param_str, "hostname"))
563                 param = C_PSQL_PARAM_HOST;
564         else if (0 == strcasecmp (param_str, "database"))
565                 param = C_PSQL_PARAM_DB;
566         else if (0 == strcasecmp (param_str, "username"))
567                 param = C_PSQL_PARAM_USER;
568         else {
569                 log_err ("Invalid parameter \"%s\".", param_str);
570                 return 1;
571         }
572
573         ++query->params_num;
574         if (NULL == (query->params = (c_psql_param_t *)realloc (query->params,
575                                 query->params_num * sizeof (*query->params)))) {
576                 log_err ("Out of memory.");
577                 exit (5);
578         }
579
580         query->params[query->params_num - 1] = param;
581         return 0;
582 } /* config_set_param */
583
584 static int config_set_column (c_psql_query_t *query, const oconfig_item_t *ci)
585 {
586         c_psql_col_t *col;
587
588         int i;
589
590         if ((0 != ci->children_num)
591                         || (1 > ci->values_num) || (2 < ci->values_num)) {
592                 log_err ("Column expects either one or two arguments.");
593                 return 1;
594         }
595
596         for (i = 0; i < ci->values_num; ++i) {
597                 if (OCONFIG_TYPE_STRING != ci->values[i].type) {
598                         log_err ("Column expects either one or two string arguments.");
599                         return 1;
600                 }
601         }
602
603         ++query->cols_num;
604         if (NULL == (query->cols = (c_psql_col_t *)realloc (query->cols,
605                                 query->cols_num * sizeof (*query->cols)))) {
606                 log_err ("Out of memory.");
607                 exit (5);
608         }
609
610         col = query->cols + query->cols_num - 1;
611
612         col->ds_type = -1;
613
614         col->type = sstrdup (ci->values[0].value.string);
615         col->type_instance = (2 == ci->values_num)
616                 ? sstrdup (ci->values[1].value.string) : NULL;
617         return 0;
618 } /* config_set_column */
619
620 static int config_set_query (c_psql_database_t *db, const oconfig_item_t *ci)
621 {
622         c_psql_query_t *query;
623
624         if ((0 != ci->children_num) || (1 != ci->values_num)
625                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
626                 log_err ("Query expects a single string argument.");
627                 return 1;
628         }
629
630         query = c_psql_query_get (ci->values[0].value.string);
631         if (NULL == query) {
632                 log_err ("Query \"%s\" not found - please check your configuration.",
633                                 ci->values[0].value.string);
634                 return 1;
635         }
636
637         ++db->queries_num;
638         if (NULL == (db->queries = (c_psql_query_t **)realloc (db->queries,
639                                 db->queries_num * sizeof (*db->queries)))) {
640                 log_err ("Out of memory.");
641                 exit (5);
642         }
643
644         if (query->params_num > db->max_params_num)
645                 db->max_params_num = query->params_num;
646
647         db->queries[db->queries_num - 1] = query;
648         return 0;
649 } /* config_set_query */
650
651 static int c_psql_config_query (oconfig_item_t *ci)
652 {
653         c_psql_query_t *query;
654
655         int i;
656
657         if ((1 != ci->values_num)
658                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
659                 log_err ("<Query> expects a single string argument.");
660                 return 1;
661         }
662
663         query = c_psql_query_new (ci->values[0].value.string);
664
665         for (i = 0; i < ci->children_num; ++i) {
666                 oconfig_item_t *c = ci->children + i;
667
668                 if (0 == strcasecmp (c->key, "Query"))
669                         config_set ("Query", &query->query, c);
670                 else if (0 == strcasecmp (c->key, "Param"))
671                         config_set_param (query, c);
672                 else if (0 == strcasecmp (c->key, "Column"))
673                         config_set_column (query, c);
674                 else
675                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
676         }
677         return 0;
678 } /* c_psql_config_query */
679
680 static int c_psql_config_database (oconfig_item_t *ci)
681 {
682         c_psql_database_t *db;
683
684         int i;
685
686         if ((1 != ci->values_num)
687                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
688                 log_err ("<Database> expects a single string argument.");
689                 return 1;
690         }
691
692         db = c_psql_database_new (ci->values[0].value.string);
693
694         for (i = 0; i < ci->children_num; ++i) {
695                 oconfig_item_t *c = ci->children + i;
696
697                 if (0 == strcasecmp (c->key, "Host"))
698                         config_set ("Host", &db->host, c);
699                 else if (0 == strcasecmp (c->key, "Port"))
700                         config_set ("Port", &db->port, c);
701                 else if (0 == strcasecmp (c->key, "User"))
702                         config_set ("User", &db->user, c);
703                 else if (0 == strcasecmp (c->key, "Password"))
704                         config_set ("Password", &db->password, c);
705                 else if (0 == strcasecmp (c->key, "SSLMode"))
706                         config_set ("SSLMode", &db->sslmode, c);
707                 else if (0 == strcasecmp (c->key, "KRBSrvName"))
708                         config_set ("KRBSrvName", &db->krbsrvname, c);
709                 else if (0 == strcasecmp (c->key, "Service"))
710                         config_set ("Service", &db->service, c);
711                 else if (0 == strcasecmp (c->key, "Query"))
712                         config_set_query (db, c);
713                 else
714                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
715         }
716
717         if (NULL == db->queries) {
718                 db->queries = (c_psql_query_t **)malloc (def_queries_num
719                                 * sizeof (*db->queries));
720
721                 for (i = 0; i < def_queries_num; ++i) {
722                         db->queries[i] = c_psql_query_get (def_queries[i]);
723                         if (NULL == db->queries[i])
724                                 log_err ("Query \"%s\" not found - "
725                                                 "please check your installation.",
726                                                 def_queries[i]);
727                         else
728                                 ++db->queries_num;
729                 }
730         }
731         return 0;
732 }
733
734 static int c_psql_config (oconfig_item_t *ci)
735 {
736         static int have_def_config = 0;
737
738         int i;
739
740         if (0 == have_def_config) {
741                 oconfig_item_t *c;
742
743                 have_def_config = 1;
744
745                 c = oconfig_parse_file (C_PSQL_DEFAULT_CONF);
746                 if (NULL == c)
747                         log_err ("Failed to read default config ("C_PSQL_DEFAULT_CONF").");
748                 else
749                         c_psql_config (c);
750
751                 if (NULL == queries)
752                         log_err ("Default config ("C_PSQL_DEFAULT_CONF") did not define "
753                                         "any queries - please check your installation.");
754         }
755
756         for (i = 0; i < ci->children_num; ++i) {
757                 oconfig_item_t *c = ci->children + i;
758
759                 if (0 == strcasecmp (c->key, "Query"))
760                         c_psql_config_query (c);
761                 else if (0 == strcasecmp (c->key, "Database"))
762                         c_psql_config_database (c);
763                 else
764                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
765         }
766         return 0;
767 } /* c_psql_config */
768
769 void module_register (void)
770 {
771         plugin_register_complex_config ("postgresql", c_psql_config);
772         plugin_register_init ("postgresql", c_psql_init);
773 } /* module_register */
774
775 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
776