Mon Sep 20 2010 00:20:24

Asterisk developer's documentation


cdr_pgsql.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2003 - 2006
00005  *
00006  * Matthew D. Hardeman <mhardemn@papersoft.com>
00007  * Adapted from the MySQL CDR logger originally by James Sharp
00008  *
00009  * Modified September 2003
00010  * Matthew D. Hardeman <mhardemn@papersoft.com>
00011  *
00012  * See http://www.asterisk.org for more information about
00013  * the Asterisk project. Please do not directly contact
00014  * any of the maintainers of this project for assistance;
00015  * the project provides a web site, mailing lists and IRC
00016  * channels for your use.
00017  *
00018  * This program is free software, distributed under the terms of
00019  * the GNU General Public License Version 2. See the LICENSE file
00020  * at the top of the source tree.
00021  */
00022 
00023 /*! \file
00024  *
00025  * \brief PostgreSQL CDR logger
00026  *
00027  * \author Matthew D. Hardeman <mhardemn@papersoft.com>
00028  * \extref PostgreSQL http://www.postgresql.org/
00029  *
00030  * See also
00031  * \arg \ref Config_cdr
00032  * \arg http://www.postgresql.org/
00033  * \ingroup cdr_drivers
00034  */
00035 
00036 /*** MODULEINFO
00037    <depend>pgsql</depend>
00038  ***/
00039 
00040 #include "asterisk.h"
00041 
00042 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 253620 $")
00043 
00044 #include <time.h>
00045 
00046 #include <libpq-fe.h>
00047 
00048 #include "asterisk/config.h"
00049 #include "asterisk/channel.h"
00050 #include "asterisk/cdr.h"
00051 #include "asterisk/module.h"
00052 
00053 #define DATE_FORMAT "'%Y-%m-%d %T'"
00054 
00055 static char *name = "pgsql";
00056 static char *config = "cdr_pgsql.conf";
00057 static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL;
00058 static int connected = 0;
00059 static int maxsize = 512, maxsize2 = 512;
00060 
00061 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
00062 
00063 static PGconn  *conn = NULL;
00064 
00065 struct columns {
00066    char *name;
00067    char *type;
00068    int len;
00069    unsigned int notnull:1;
00070    unsigned int hasdefault:1;
00071    AST_RWLIST_ENTRY(columns) list;
00072 };
00073 
00074 static AST_RWLIST_HEAD_STATIC(psql_columns, columns);
00075 
00076 #define LENGTHEN_BUF1(size)                                               \
00077          do {                                                          \
00078             /* Lengthen buffer, if necessary */                       \
00079             if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
00080                if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) { \
00081                   ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR failed.\n"); \
00082                   ast_free(sql);                                    \
00083                   ast_free(sql2);                                   \
00084                   AST_RWLIST_UNLOCK(&psql_columns);                 \
00085                   return -1;                                        \
00086                }                                                     \
00087             }                                                         \
00088          } while (0)
00089 
00090 #define LENGTHEN_BUF2(size)                               \
00091          do {                                          \
00092             if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) {  \
00093                if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) {  \
00094                   ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR failed.\n");  \
00095                   ast_free(sql);                    \
00096                   ast_free(sql2);                   \
00097                   AST_RWLIST_UNLOCK(&psql_columns); \
00098                   return -1;                        \
00099                }                                     \
00100             }                                         \
00101          } while (0)
00102 
00103 static int pgsql_log(struct ast_cdr *cdr)
00104 {
00105    struct ast_tm tm;
00106    char *pgerror;
00107    PGresult *result;
00108 
00109    ast_mutex_lock(&pgsql_lock);
00110 
00111    if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
00112       conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
00113       if (PQstatus(conn) != CONNECTION_BAD) {
00114          connected = 1;
00115       } else {
00116          pgerror = PQerrorMessage(conn);
00117          ast_log(LOG_ERROR, "Unable to connect to database server %s.  Calls will not be logged!\n", pghostname);
00118          ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00119          PQfinish(conn);
00120          conn = NULL;
00121       }
00122    }
00123 
00124    if (connected) {
00125       struct columns *cur;
00126       struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
00127       char buf[257], escapebuf[513], *value;
00128       int first = 1;
00129   
00130       if (!sql || !sql2) {
00131          if (sql) {
00132             ast_free(sql);
00133          }
00134          if (sql2) {
00135             ast_free(sql2);
00136          }
00137          return -1;
00138       }
00139 
00140       ast_str_set(&sql, 0, "INSERT INTO %s (", table);
00141       ast_str_set(&sql2, 0, " VALUES (");
00142 
00143       AST_RWLIST_RDLOCK(&psql_columns);
00144       AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
00145          /* For fields not set, simply skip them */
00146          ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00147          if (strcmp(cur->name, "calldate") == 0 && !value) {
00148             ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0);
00149          }
00150          if (!value) {
00151             if (cur->notnull && !cur->hasdefault) {
00152                /* Field is NOT NULL (but no default), must include it anyway */
00153                LENGTHEN_BUF1(strlen(cur->name) + 2);
00154                ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
00155                LENGTHEN_BUF2(3);
00156                ast_str_append(&sql2, 0, "%s''", first ? "" : ",");
00157                first = 0;
00158             }
00159             continue;
00160          }
00161 
00162          LENGTHEN_BUF1(strlen(cur->name) + 2);
00163          ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
00164 
00165          if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
00166             if (strncmp(cur->type, "int", 3) == 0) {
00167                LENGTHEN_BUF2(13);
00168                ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->start.tv_sec);
00169             } else if (strncmp(cur->type, "float", 5) == 0) {
00170                LENGTHEN_BUF2(31);
00171                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
00172             } else {
00173                /* char, hopefully */
00174                LENGTHEN_BUF2(31);
00175                ast_localtime(&cdr->start, &tm, NULL);
00176                ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
00177                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
00178             }
00179          } else if (strcmp(cur->name, "answer") == 0) {
00180             if (strncmp(cur->type, "int", 3) == 0) {
00181                LENGTHEN_BUF2(13);
00182                ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->answer.tv_sec);
00183             } else if (strncmp(cur->type, "float", 5) == 0) {
00184                LENGTHEN_BUF2(31);
00185                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
00186             } else {
00187                /* char, hopefully */
00188                LENGTHEN_BUF2(31);
00189                ast_localtime(&cdr->start, &tm, NULL);
00190                ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
00191                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
00192             }
00193          } else if (strcmp(cur->name, "end") == 0) {
00194             if (strncmp(cur->type, "int", 3) == 0) {
00195                LENGTHEN_BUF2(13);
00196                ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->end.tv_sec);
00197             } else if (strncmp(cur->type, "float", 5) == 0) {
00198                LENGTHEN_BUF2(31);
00199                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
00200             } else {
00201                /* char, hopefully */
00202                LENGTHEN_BUF2(31);
00203                ast_localtime(&cdr->end, &tm, NULL);
00204                ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
00205                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
00206             }
00207          } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
00208             if (cur->type[0] == 'i') {
00209                /* Get integer, no need to escape anything */
00210                ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00211                LENGTHEN_BUF2(13);
00212                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
00213             } else if (strncmp(cur->type, "float", 5) == 0) {
00214                struct timeval *when = cur->name[0] == 'd' ? &cdr->start : &cdr->answer;
00215                LENGTHEN_BUF2(31);
00216                ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec - when->tv_sec + cdr->end.tv_usec / 1000000.0 - when->tv_usec / 1000000.0);
00217             } else {
00218                /* Char field, probably */
00219                struct timeval *when = cur->name[0] == 'd' ? &cdr->start : &cdr->answer;
00220                LENGTHEN_BUF2(31);
00221                ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double)cdr->end.tv_sec - when->tv_sec + cdr->end.tv_usec / 1000000.0 - when->tv_usec / 1000000.0);
00222             }
00223          } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
00224             if (strncmp(cur->type, "int", 3) == 0) {
00225                /* Integer, no need to escape anything */
00226                ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1);
00227                LENGTHEN_BUF2(13);
00228                ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
00229             } else {
00230                /* Although this is a char field, there are no special characters in the values for these fields */
00231                ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00232                LENGTHEN_BUF2(31);
00233                ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
00234             }
00235          } else {
00236             /* Arbitrary field, could be anything */
00237             ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
00238             if (strncmp(cur->type, "int", 3) == 0) {
00239                long long whatever;
00240                if (value && sscanf(value, "%30lld", &whatever) == 1) {
00241                   LENGTHEN_BUF2(26);
00242                   ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", whatever);
00243                } else {
00244                   LENGTHEN_BUF2(2);
00245                   ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
00246                }
00247             } else if (strncmp(cur->type, "float", 5) == 0) {
00248                long double whatever;
00249                if (value && sscanf(value, "%30Lf", &whatever) == 1) {
00250                   LENGTHEN_BUF2(51);
00251                   ast_str_append(&sql2, 0, "%s%30Lf", first ? "" : ",", whatever);
00252                } else {
00253                   LENGTHEN_BUF2(2);
00254                   ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
00255                }
00256             /* XXX Might want to handle dates, times, and other misc fields here XXX */
00257             } else {
00258                if (value)
00259                   PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
00260                else
00261                   escapebuf[0] = '\0';
00262                LENGTHEN_BUF2(strlen(escapebuf) + 3);
00263                ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", escapebuf);
00264             }
00265          }
00266          first = 0;
00267       }
00268       AST_RWLIST_UNLOCK(&psql_columns);
00269       LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
00270       ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
00271       ast_verb(11, "[%s]\n", ast_str_buffer(sql));
00272 
00273       ast_debug(2, "inserting a CDR record.\n");
00274 
00275       /* Test to be sure we're still connected... */
00276       /* If we're connected, and connection is working, good. */
00277       /* Otherwise, attempt reconnect.  If it fails... sorry... */
00278       if (PQstatus(conn) == CONNECTION_OK) {
00279          connected = 1;
00280       } else {
00281          ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
00282          PQreset(conn);
00283          if (PQstatus(conn) == CONNECTION_OK) {
00284             ast_log(LOG_ERROR, "Connection reestablished.\n");
00285             connected = 1;
00286          } else {
00287             pgerror = PQerrorMessage(conn);
00288             ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
00289             ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00290             PQfinish(conn);
00291             conn = NULL;
00292             connected = 0;
00293             ast_mutex_unlock(&pgsql_lock);
00294             ast_free(sql);
00295             ast_free(sql2);
00296             return -1;
00297          }
00298       }
00299       result = PQexec(conn, ast_str_buffer(sql));
00300       if (PQresultStatus(result) != PGRES_COMMAND_OK) {
00301          pgerror = PQresultErrorMessage(result);
00302          ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
00303          ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00304          ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
00305          PQreset(conn);
00306          if (PQstatus(conn) == CONNECTION_OK) {
00307             ast_log(LOG_ERROR, "Connection reestablished.\n");
00308             connected = 1;
00309             PQclear(result);
00310             result = PQexec(conn, ast_str_buffer(sql));
00311             if (PQresultStatus(result) != PGRES_COMMAND_OK) {
00312                pgerror = PQresultErrorMessage(result);
00313                ast_log(LOG_ERROR, "HARD ERROR!  Attempted reconnection failed.  DROPPING CALL RECORD!\n");
00314                ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00315             }
00316          }
00317          ast_mutex_unlock(&pgsql_lock);
00318          PQclear(result);
00319          ast_free(sql);
00320          ast_free(sql2);
00321          return -1;
00322       }
00323       PQclear(result);
00324       ast_free(sql);
00325       ast_free(sql2);
00326    }
00327    ast_mutex_unlock(&pgsql_lock);
00328    return 0;
00329 }
00330 
00331 static int unload_module(void)
00332 {
00333    struct columns *current;
00334    ast_cdr_unregister(name);
00335 
00336    /* Give all threads time to finish */
00337    usleep(1);
00338    PQfinish(conn);
00339 
00340    if (pghostname)
00341       ast_free(pghostname);
00342    if (pgdbname)
00343       ast_free(pgdbname);
00344    if (pgdbuser)
00345       ast_free(pgdbuser);
00346    if (pgpassword)
00347       ast_free(pgpassword);
00348    if (pgdbport)
00349       ast_free(pgdbport);
00350    if (table)
00351       ast_free(table);
00352 
00353    AST_RWLIST_WRLOCK(&psql_columns);
00354    while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
00355       ast_free(current);
00356    }
00357    AST_RWLIST_UNLOCK(&psql_columns);
00358 
00359    return 0;
00360 }
00361 
00362 static int config_module(int reload)
00363 {
00364    struct ast_variable *var;
00365    char *pgerror;
00366    struct columns *cur;
00367    PGresult *result;
00368    const char *tmp;
00369    struct ast_config *cfg;
00370    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00371 
00372    if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
00373       ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
00374       return -1;
00375    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
00376       return 0;
00377 
00378    if (!(var = ast_variable_browse(cfg, "global"))) {
00379       ast_config_destroy(cfg);
00380       return 0;
00381    }
00382 
00383    if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
00384       ast_log(LOG_WARNING, "PostgreSQL server hostname not specified.  Assuming unix socket connection\n");
00385       tmp = "";   /* connect via UNIX-socket by default */
00386    }
00387 
00388    if (pghostname)
00389       ast_free(pghostname);
00390    if (!(pghostname = ast_strdup(tmp))) {
00391       ast_config_destroy(cfg);
00392       return -1;
00393    }
00394 
00395    if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
00396       ast_log(LOG_WARNING, "PostgreSQL database not specified.  Assuming asterisk\n");
00397       tmp = "asteriskcdrdb";
00398    }
00399 
00400    if (pgdbname)
00401       ast_free(pgdbname);
00402    if (!(pgdbname = ast_strdup(tmp))) {
00403       ast_config_destroy(cfg);
00404       return -1;
00405    }
00406 
00407    if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
00408       ast_log(LOG_WARNING, "PostgreSQL database user not specified.  Assuming asterisk\n");
00409       tmp = "asterisk";
00410    }
00411 
00412    if (pgdbuser)
00413       ast_free(pgdbuser);
00414    if (!(pgdbuser = ast_strdup(tmp))) {
00415       ast_config_destroy(cfg);
00416       return -1;
00417    }
00418 
00419    if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
00420       ast_log(LOG_WARNING, "PostgreSQL database password not specified.  Assuming blank\n");
00421       tmp = "";
00422    }
00423 
00424    if (pgpassword)
00425       ast_free(pgpassword);
00426    if (!(pgpassword = ast_strdup(tmp))) {
00427       ast_config_destroy(cfg);
00428       return -1;
00429    }
00430 
00431    if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
00432       ast_log(LOG_WARNING, "PostgreSQL database port not specified.  Using default 5432.\n");
00433       tmp = "5432";
00434    }
00435 
00436    if (pgdbport)
00437       ast_free(pgdbport);
00438    if (!(pgdbport = ast_strdup(tmp))) {
00439       ast_config_destroy(cfg);
00440       return -1;
00441    }
00442 
00443    if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
00444       ast_log(LOG_WARNING, "CDR table not specified.  Assuming cdr\n");
00445       tmp = "cdr";
00446    }
00447 
00448    if (table)
00449       ast_free(table);
00450    if (!(table = ast_strdup(tmp))) {
00451       ast_config_destroy(cfg);
00452       return -1;
00453    }
00454 
00455    if (option_debug) {
00456       if (ast_strlen_zero(pghostname)) {
00457          ast_debug(1, "using default unix socket\n");
00458       } else {
00459          ast_debug(1, "got hostname of %s\n", pghostname);
00460       }
00461       ast_debug(1, "got port of %s\n", pgdbport);
00462       ast_debug(1, "got user of %s\n", pgdbuser);
00463       ast_debug(1, "got dbname of %s\n", pgdbname);
00464       ast_debug(1, "got password of %s\n", pgpassword);
00465       ast_debug(1, "got sql table name of %s\n", table);
00466    }
00467 
00468    conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
00469    if (PQstatus(conn) != CONNECTION_BAD) {
00470       char sqlcmd[768];
00471       char *fname, *ftype, *flen, *fnotnull, *fdef;
00472       int i, rows, version;
00473       ast_debug(1, "Successfully connected to PostgreSQL database.\n");
00474       connected = 1;
00475       version = PQserverVersion(conn);
00476 
00477       if (version >= 70300) {
00478          char *schemaname, *tablename;
00479          if (strchr(table, '.')) {
00480             schemaname = ast_strdupa(table);
00481             tablename = strchr(schemaname, '.');
00482             *tablename++ = '\0';
00483          } else {
00484             schemaname = "";
00485             tablename = table;
00486          }
00487 
00488          /* Escape special characters in schemaname */
00489          if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
00490             char *tmp = schemaname, *ptr;
00491 
00492             ptr = schemaname = alloca(strlen(tmp) * 2 + 1);
00493             for (; *tmp; tmp++) {
00494                if (strchr("\\'", *tmp)) {
00495                   *ptr++ = *tmp;
00496                }
00497                *ptr++ = *tmp;
00498             }
00499             *ptr = '\0';
00500          }
00501          /* Escape special characters in tablename */
00502          if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
00503             char *tmp = tablename, *ptr;
00504 
00505             ptr = tablename = alloca(strlen(tmp) * 2 + 1);
00506             for (; *tmp; tmp++) {
00507                if (strchr("\\'", *tmp)) {
00508                   *ptr++ = *tmp;
00509                }
00510                *ptr++ = *tmp;
00511             }
00512             *ptr = '\0';
00513          }
00514 
00515          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",
00516             tablename,
00517             ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
00518       } else {
00519          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);
00520       }
00521       /* Query the columns */
00522       result = PQexec(conn, sqlcmd);
00523       if (PQresultStatus(result) != PGRES_TUPLES_OK) {
00524          pgerror = PQresultErrorMessage(result);
00525          ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
00526          PQclear(result);
00527          unload_module();
00528          return AST_MODULE_LOAD_DECLINE;
00529       }
00530 
00531       rows = PQntuples(result);
00532       for (i = 0; i < rows; i++) {
00533          fname = PQgetvalue(result, i, 0);
00534          ftype = PQgetvalue(result, i, 1);
00535          flen = PQgetvalue(result, i, 2);
00536          fnotnull = PQgetvalue(result, i, 3);
00537          fdef = PQgetvalue(result, i, 4);
00538          if (atoi(flen) == -1) {
00539             /* For varchar columns, the maximum length is encoded in a different field */
00540             flen = PQgetvalue(result, i, 5);
00541          }
00542          ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
00543          cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
00544          if (cur) {
00545             sscanf(flen, "%30d", &cur->len);
00546             cur->name = (char *)cur + sizeof(*cur);
00547             cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
00548             strcpy(cur->name, fname);
00549             strcpy(cur->type, ftype);
00550             if (*fnotnull == 't') {
00551                cur->notnull = 1;
00552             } else {
00553                cur->notnull = 0;
00554             }
00555             if (!ast_strlen_zero(fdef)) {
00556                cur->hasdefault = 1;
00557             } else {
00558                cur->hasdefault = 0;
00559             }
00560             AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
00561          }
00562       }
00563       PQclear(result);
00564    } else {
00565       pgerror = PQerrorMessage(conn);
00566       ast_log(LOG_ERROR, "Unable to connect to database server %s.  CALLS WILL NOT BE LOGGED!!\n", pghostname);
00567       ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
00568       connected = 0;
00569    }
00570 
00571    ast_config_destroy(cfg);
00572 
00573    return ast_cdr_register(name, ast_module_info->description, pgsql_log);
00574 }
00575 
00576 static int load_module(void)
00577 {
00578    return config_module(0) ? AST_MODULE_LOAD_DECLINE : 0;
00579 }
00580 
00581 static int reload(void)
00582 {
00583    return config_module(1);
00584 }
00585 
00586 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "PostgreSQL CDR Backend",
00587       .load = load_module,
00588       .unload = unload_module,
00589       .reload = reload,
00590           );