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