Merged revisions 329614 via svnmerge from
[asterisk/asterisk.git] / cdr / cdr_pgsql.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2003 - 2006
5  *
6  * Matthew D. Hardeman <mhardemn@papersoft.com>
7  * Adapted from the MySQL CDR logger originally by James Sharp
8  *
9  * Modified September 2003
10  * Matthew D. Hardeman <mhardemn@papersoft.com>
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  * This program is free software, distributed under the terms of
19  * the GNU General Public License Version 2. See the LICENSE file
20  * at the top of the source tree.
21  */
22
23 /*!
24  * \file
25  * \brief PostgreSQL CDR logger
26  *
27  * \author Matthew D. Hardeman <mhardemn@papersoft.com>
28  * \extref PostgreSQL http://www.postgresql.org/
29  *
30  * See also
31  * \arg \ref Config_cdr
32  * \extref PostgreSQL http://www.postgresql.org/
33  * \ingroup cdr_drivers
34  */
35
36 /*** MODULEINFO
37         <depend>pgsql</depend>
38         <support_level>extended</support_level>
39  ***/
40
41 #include "asterisk.h"
42
43 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
44
45 #include <libpq-fe.h>
46
47 #include "asterisk/config.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/cdr.h"
50 #include "asterisk/module.h"
51
52 #define DATE_FORMAT "'%Y-%m-%d %T'"
53
54 static const char name[] = "pgsql";
55 static const char config[] = "cdr_pgsql.conf";
56 static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL, *encoding = NULL, *tz = NULL;
57 static int connected = 0;
58 static int maxsize = 512, maxsize2 = 512;
59
60 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
61
62 static PGconn   *conn = NULL;
63
64 struct columns {
65         char *name;
66         char *type;
67         int len;
68         unsigned int notnull:1;
69         unsigned int hasdefault:1;
70         AST_RWLIST_ENTRY(columns) list;
71 };
72
73 static AST_RWLIST_HEAD_STATIC(psql_columns, columns);
74
75 #define LENGTHEN_BUF1(size)                                               \
76                         do {                                                          \
77                                 /* Lengthen buffer, if necessary */                       \
78                                 if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
79                                         if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) {  \
80                                                 ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR failed.\n"); \
81                                                 ast_free(sql);                                    \
82                                                 ast_free(sql2);                                   \
83                                                 AST_RWLIST_UNLOCK(&psql_columns);                 \
84                                                 return -1;                                        \
85                                         }                                                     \
86                                 }                                                         \
87                         } while (0)
88
89 #define LENGTHEN_BUF2(size)                               \
90                         do {                                          \
91                                 if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) {  \
92                                         if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) {        \
93                                                 ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR failed.\n");      \
94                                                 ast_free(sql);                    \
95                                                 ast_free(sql2);                   \
96                                                 AST_RWLIST_UNLOCK(&psql_columns); \
97                                                 return -1;                        \
98                                         }                                     \
99                                 }                                         \
100                         } while (0)
101
102 static int pgsql_log(struct ast_cdr *cdr)
103 {
104         struct ast_tm tm;
105         char *pgerror;
106         PGresult *result;
107
108         ast_mutex_lock(&pgsql_lock);
109
110         if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
111                 conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
112                 if (PQstatus(conn) != CONNECTION_BAD) {
113                         connected = 1;
114                         if (PQsetClientEncoding(conn, encoding)) {
115 #ifdef HAVE_PGSQL_pg_encoding_to_char
116                                 ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
117 #else
118                                 ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default.\n", encoding);
119 #endif
120                         }
121                 } else {
122                         pgerror = PQerrorMessage(conn);
123                         ast_log(LOG_ERROR, "Unable to connect to database server %s.  Calls will not be logged!\n", pghostname);
124                         ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
125                         PQfinish(conn);
126                         conn = NULL;
127                 }
128         }
129
130         if (connected) {
131                 struct columns *cur;
132                 struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
133                 char buf[257], escapebuf[513], *value;
134                 int first = 1;
135   
136                 if (!sql || !sql2) {
137                         if (sql) {
138                                 ast_free(sql);
139                         }
140                         if (sql2) {
141                                 ast_free(sql2);
142                         }
143                         return -1;
144                 }
145
146                 ast_str_set(&sql, 0, "INSERT INTO %s (", table);
147                 ast_str_set(&sql2, 0, " VALUES (");
148
149                 AST_RWLIST_RDLOCK(&psql_columns);
150                 AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
151                         /* For fields not set, simply skip them */
152                         ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
153                         if (strcmp(cur->name, "calldate") == 0 && !value) {
154                                 ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0);
155                         }
156                         if (!value) {
157                                 if (cur->notnull && !cur->hasdefault) {
158                                         /* Field is NOT NULL (but no default), must include it anyway */
159                                         LENGTHEN_BUF1(strlen(cur->name) + 2);
160                                         ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
161                                         LENGTHEN_BUF2(3);
162                                         ast_str_append(&sql2, 0, "%s''", first ? "" : ",");
163                                         first = 0;
164                                 }
165                                 continue;
166                         }
167
168                         LENGTHEN_BUF1(strlen(cur->name) + 2);
169                         ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
170
171                         if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
172                                 if (strncmp(cur->type, "int", 3) == 0) {
173                                         LENGTHEN_BUF2(13);
174                                         ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->start.tv_sec);
175                                 } else if (strncmp(cur->type, "float", 5) == 0) {
176                                         LENGTHEN_BUF2(31);
177                                         ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
178                                 } else {
179                                         /* char, hopefully */
180                                         LENGTHEN_BUF2(31);
181                                         ast_localtime(&cdr->start, &tm, tz);
182                                         ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
183                                         ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
184                                 }
185                         } else if (strcmp(cur->name, "answer") == 0) {
186                                 if (strncmp(cur->type, "int", 3) == 0) {
187                                         LENGTHEN_BUF2(13);
188                                         ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->answer.tv_sec);
189                                 } else if (strncmp(cur->type, "float", 5) == 0) {
190                                         LENGTHEN_BUF2(31);
191                                         ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
192                                 } else {
193                                         /* char, hopefully */
194                                         LENGTHEN_BUF2(31);
195                                         ast_localtime(&cdr->answer, &tm, tz);
196                                         ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
197                                         ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
198                                 }
199                         } else if (strcmp(cur->name, "end") == 0) {
200                                 if (strncmp(cur->type, "int", 3) == 0) {
201                                         LENGTHEN_BUF2(13);
202                                         ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->end.tv_sec);
203                                 } else if (strncmp(cur->type, "float", 5) == 0) {
204                                         LENGTHEN_BUF2(31);
205                                         ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
206                                 } else {
207                                         /* char, hopefully */
208                                         LENGTHEN_BUF2(31);
209                                         ast_localtime(&cdr->end, &tm, tz);
210                                         ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
211                                         ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
212                                 }
213                         } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
214                                 if (cur->type[0] == 'i') {
215                                         /* Get integer, no need to escape anything */
216                                         ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
217                                         LENGTHEN_BUF2(13);
218                                         ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
219                                 } else if (strncmp(cur->type, "float", 5) == 0) {
220                                         struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
221                                         LENGTHEN_BUF2(31);
222                                         ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
223                                 } else {
224                                         /* Char field, probably */
225                                         struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
226                                         LENGTHEN_BUF2(31);
227                                         ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
228                                 }
229                         } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
230                                 if (strncmp(cur->type, "int", 3) == 0) {
231                                         /* Integer, no need to escape anything */
232                                         ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1);
233                                         LENGTHEN_BUF2(13);
234                                         ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
235                                 } else {
236                                         /* Although this is a char field, there are no special characters in the values for these fields */
237                                         ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
238                                         LENGTHEN_BUF2(31);
239                                         ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
240                                 }
241                         } else {
242                                 /* Arbitrary field, could be anything */
243                                 ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
244                                 if (strncmp(cur->type, "int", 3) == 0) {
245                                         long long whatever;
246                                         if (value && sscanf(value, "%30lld", &whatever) == 1) {
247                                                 LENGTHEN_BUF2(26);
248                                                 ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", whatever);
249                                         } else {
250                                                 LENGTHEN_BUF2(2);
251                                                 ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
252                                         }
253                                 } else if (strncmp(cur->type, "float", 5) == 0) {
254                                         long double whatever;
255                                         if (value && sscanf(value, "%30Lf", &whatever) == 1) {
256                                                 LENGTHEN_BUF2(51);
257                                                 ast_str_append(&sql2, 0, "%s%30Lf", first ? "" : ",", whatever);
258                                         } else {
259                                                 LENGTHEN_BUF2(2);
260                                                 ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
261                                         }
262                                 /* XXX Might want to handle dates, times, and other misc fields here XXX */
263                                 } else {
264                                         if (value)
265                                                 PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
266                                         else
267                                                 escapebuf[0] = '\0';
268                                         LENGTHEN_BUF2(strlen(escapebuf) + 3);
269                                         ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", escapebuf);
270                                 }
271                         }
272                         first = 0;
273                 }
274                 AST_RWLIST_UNLOCK(&psql_columns);
275                 LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
276                 ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
277                 ast_verb(11, "[%s]\n", ast_str_buffer(sql));
278
279                 ast_debug(2, "inserting a CDR record.\n");
280
281                 /* Test to be sure we're still connected... */
282                 /* If we're connected, and connection is working, good. */
283                 /* Otherwise, attempt reconnect.  If it fails... sorry... */
284                 if (PQstatus(conn) == CONNECTION_OK) {
285                         connected = 1;
286                 } else {
287                         ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
288                         PQreset(conn);
289                         if (PQstatus(conn) == CONNECTION_OK) {
290                                 ast_log(LOG_ERROR, "Connection reestablished.\n");
291                                 connected = 1;
292                         } else {
293                                 pgerror = PQerrorMessage(conn);
294                                 ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
295                                 ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
296                                 PQfinish(conn);
297                                 conn = NULL;
298                                 connected = 0;
299                                 ast_mutex_unlock(&pgsql_lock);
300                                 ast_free(sql);
301                                 ast_free(sql2);
302                                 return -1;
303                         }
304                 }
305                 result = PQexec(conn, ast_str_buffer(sql));
306                 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
307                         pgerror = PQresultErrorMessage(result);
308                         ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
309                         ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
310                         ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
311                         PQreset(conn);
312                         if (PQstatus(conn) == CONNECTION_OK) {
313                                 ast_log(LOG_ERROR, "Connection reestablished.\n");
314                                 connected = 1;
315                                 PQclear(result);
316                                 result = PQexec(conn, ast_str_buffer(sql));
317                                 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
318                                         pgerror = PQresultErrorMessage(result);
319                                         ast_log(LOG_ERROR, "HARD ERROR!  Attempted reconnection failed.  DROPPING CALL RECORD!\n");
320                                         ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
321                                 }
322                         }
323                         ast_mutex_unlock(&pgsql_lock);
324                         PQclear(result);
325                         ast_free(sql);
326                         ast_free(sql2);
327                         return -1;
328                 }
329                 PQclear(result);
330                 ast_free(sql);
331                 ast_free(sql2);
332         }
333         ast_mutex_unlock(&pgsql_lock);
334         return 0;
335 }
336
337 static int unload_module(void)
338 {
339         struct columns *current;
340
341         ast_cdr_unregister(name);
342
343         PQfinish(conn);
344
345         if (pghostname) {
346                 ast_free(pghostname);
347         }
348         if (pgdbname) {
349                 ast_free(pgdbname);
350         }
351         if (pgdbuser) {
352                 ast_free(pgdbuser);
353         }
354         if (pgpassword) {
355                 ast_free(pgpassword);
356         }
357         if (pgdbport) {
358                 ast_free(pgdbport);
359         }
360         if (table) {
361                 ast_free(table);
362         }
363         if (encoding) {
364                 ast_free(encoding);
365         }
366         if (tz) {
367                 ast_free(tz);
368         }
369
370         AST_RWLIST_WRLOCK(&psql_columns);
371         while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
372                 ast_free(current);
373         }
374         AST_RWLIST_UNLOCK(&psql_columns);
375
376         return 0;
377 }
378
379 static int config_module(int reload)
380 {
381         struct ast_variable *var;
382         char *pgerror;
383         struct columns *cur;
384         PGresult *result;
385         const char *tmp;
386         struct ast_config *cfg;
387         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
388
389         if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
390                 ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
391                 return -1;
392         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
393                 return 0;
394         }
395
396         if (!(var = ast_variable_browse(cfg, "global"))) {
397                 ast_config_destroy(cfg);
398                 return 0;
399         }
400
401         if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
402                 ast_log(LOG_WARNING, "PostgreSQL server hostname not specified.  Assuming unix socket connection\n");
403                 tmp = "";       /* connect via UNIX-socket by default */
404         }
405
406         if (pghostname) {
407                 ast_free(pghostname);
408         }
409         if (!(pghostname = ast_strdup(tmp))) {
410                 ast_config_destroy(cfg);
411                 return -1;
412         }
413
414         if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
415                 ast_log(LOG_WARNING, "PostgreSQL database not specified.  Assuming asterisk\n");
416                 tmp = "asteriskcdrdb";
417         }
418
419         if (pgdbname) {
420                 ast_free(pgdbname);
421         }
422         if (!(pgdbname = ast_strdup(tmp))) {
423                 ast_config_destroy(cfg);
424                 return -1;
425         }
426
427         if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
428                 ast_log(LOG_WARNING, "PostgreSQL database user not specified.  Assuming asterisk\n");
429                 tmp = "asterisk";
430         }
431
432         if (pgdbuser) {
433                 ast_free(pgdbuser);
434         }
435         if (!(pgdbuser = ast_strdup(tmp))) {
436                 ast_config_destroy(cfg);
437                 return -1;
438         }
439
440         if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
441                 ast_log(LOG_WARNING, "PostgreSQL database password not specified.  Assuming blank\n");
442                 tmp = "";
443         }
444
445         if (pgpassword) {
446                 ast_free(pgpassword);
447         }
448         if (!(pgpassword = ast_strdup(tmp))) {
449                 ast_config_destroy(cfg);
450                 return -1;
451         }
452
453         if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
454                 ast_log(LOG_WARNING, "PostgreSQL database port not specified.  Using default 5432.\n");
455                 tmp = "5432";
456         }
457
458         if (pgdbport) {
459                 ast_free(pgdbport);
460         }
461         if (!(pgdbport = ast_strdup(tmp))) {
462                 ast_config_destroy(cfg);
463                 return -1;
464         }
465
466         if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
467                 ast_log(LOG_WARNING, "CDR table not specified.  Assuming cdr\n");
468                 tmp = "cdr";
469         }
470
471         if (table) {
472                 ast_free(table);
473         }
474         if (!(table = ast_strdup(tmp))) {
475                 ast_config_destroy(cfg);
476                 return -1;
477         }
478
479         if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
480                 ast_log(LOG_WARNING, "Encoding not specified.  Assuming LATIN9\n");
481                 tmp = "LATIN9";
482         }
483
484         if (encoding) {
485                 ast_free(encoding);
486         }
487         if (!(encoding = ast_strdup(tmp))) {
488                 ast_config_destroy(cfg);
489                 return -1;
490         }
491
492         if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
493                 tmp = "";
494         }
495
496         if (tz) {
497                 ast_free(tz);
498                 tz = NULL;
499         }
500         if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
501                 ast_config_destroy(cfg);
502                 return -1;
503         }
504
505         if (option_debug) {
506                 if (ast_strlen_zero(pghostname)) {
507                         ast_debug(1, "using default unix socket\n");
508                 } else {
509                         ast_debug(1, "got hostname of %s\n", pghostname);
510                 }
511                 ast_debug(1, "got port of %s\n", pgdbport);
512                 ast_debug(1, "got user of %s\n", pgdbuser);
513                 ast_debug(1, "got dbname of %s\n", pgdbname);
514                 ast_debug(1, "got password of %s\n", pgpassword);
515                 ast_debug(1, "got sql table name of %s\n", table);
516                 ast_debug(1, "got encoding of %s\n", encoding);
517                 ast_debug(1, "got timezone of %s\n", tz);
518         }
519
520         conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
521         if (PQstatus(conn) != CONNECTION_BAD) {
522                 char sqlcmd[768];
523                 char *fname, *ftype, *flen, *fnotnull, *fdef;
524                 int i, rows, version;
525                 ast_debug(1, "Successfully connected to PostgreSQL database.\n");
526                 connected = 1;
527                 if (PQsetClientEncoding(conn, encoding)) {
528 #ifdef HAVE_PGSQL_pg_encoding_to_char
529                         ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
530 #else
531                         ast_log(LOG_WARNING, "Failed to set encoding to '%s'.  Encoding set to default.\n", encoding);
532 #endif
533                 }
534                 version = PQserverVersion(conn);
535
536                 if (version >= 70300) {
537                         char *schemaname, *tablename;
538                         if (strchr(table, '.')) {
539                                 schemaname = ast_strdupa(table);
540                                 tablename = strchr(schemaname, '.');
541                                 *tablename++ = '\0';
542                         } else {
543                                 schemaname = "";
544                                 tablename = table;
545                         }
546
547                         /* Escape special characters in schemaname */
548                         if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
549                                 char *tmp = schemaname, *ptr;
550
551                                 ptr = schemaname = alloca(strlen(tmp) * 2 + 1);
552                                 for (; *tmp; tmp++) {
553                                         if (strchr("\\'", *tmp)) {
554                                                 *ptr++ = *tmp;
555                                         }
556                                         *ptr++ = *tmp;
557                                 }
558                                 *ptr = '\0';
559                         }
560                         /* Escape special characters in tablename */
561                         if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
562                                 char *tmp = tablename, *ptr;
563
564                                 ptr = tablename = alloca(strlen(tmp) * 2 + 1);
565                                 for (; *tmp; tmp++) {
566                                         if (strchr("\\'", *tmp)) {
567                                                 *ptr++ = *tmp;
568                                         }
569                                         *ptr++ = *tmp;
570                                 }
571                                 *ptr = '\0';
572                         }
573
574                         snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
575                                 tablename,
576                                 ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
577                 } else {
578                         snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", table);
579                 }
580                 /* Query the columns */
581                 result = PQexec(conn, sqlcmd);
582                 if (PQresultStatus(result) != PGRES_TUPLES_OK) {
583                         pgerror = PQresultErrorMessage(result);
584                         ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
585                         PQclear(result);
586                         unload_module();
587                         return AST_MODULE_LOAD_DECLINE;
588                 }
589
590                 rows = PQntuples(result);
591                 if (rows == 0) {
592                         ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
593                         PQclear(result);
594                         unload_module();
595                         return AST_MODULE_LOAD_DECLINE;
596                 }
597
598                 for (i = 0; i < rows; i++) {
599                         fname = PQgetvalue(result, i, 0);
600                         ftype = PQgetvalue(result, i, 1);
601                         flen = PQgetvalue(result, i, 2);
602                         fnotnull = PQgetvalue(result, i, 3);
603                         fdef = PQgetvalue(result, i, 4);
604                         if (atoi(flen) == -1) {
605                                 /* For varchar columns, the maximum length is encoded in a different field */
606                                 flen = PQgetvalue(result, i, 5);
607                         }
608                         ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
609                         cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
610                         if (cur) {
611                                 sscanf(flen, "%30d", &cur->len);
612                                 cur->name = (char *)cur + sizeof(*cur);
613                                 cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
614                                 strcpy(cur->name, fname);
615                                 strcpy(cur->type, ftype);
616                                 if (*fnotnull == 't') {
617                                         cur->notnull = 1;
618                                 } else {
619                                         cur->notnull = 0;
620                                 }
621                                 if (!ast_strlen_zero(fdef)) {
622                                         cur->hasdefault = 1;
623                                 } else {
624                                         cur->hasdefault = 0;
625                                 }
626                                 AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
627                         }
628                 }
629                 PQclear(result);
630         } else {
631                 pgerror = PQerrorMessage(conn);
632                 ast_log(LOG_ERROR, "Unable to connect to database server %s.  CALLS WILL NOT BE LOGGED!!\n", pghostname);
633                 ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
634                 connected = 0;
635         }
636
637         ast_config_destroy(cfg);
638
639         return ast_cdr_register(name, ast_module_info->description, pgsql_log);
640 }
641
642 static int load_module(void)
643 {
644         return config_module(0) ? AST_MODULE_LOAD_DECLINE : 0;
645 }
646
647 static int reload(void)
648 {
649         return config_module(1);
650 }
651
652 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
653                 .load = load_module,
654                 .unload = unload_module,
655                 .reload = reload,
656                 .load_pri = AST_MODPRI_CDR_DRIVER,
657                );