2003-10-01 15:03:30 +00:00
/*
2005-09-14 20:46:50 +00:00
* Asterisk -- An open source telephony toolkit.
2003-10-01 15:03:30 +00:00
*
2012-02-07 15:29:14 +00:00
* Copyright (C) 2003 - 2012
2005-09-15 15:44:26 +00:00
*
2008-11-20 17:48:58 +00:00
* Matthew D. Hardeman <mhardemn@papersoft.com>
* Adapted from the MySQL CDR logger originally by James Sharp
2003-10-01 15:03:30 +00:00
*
* Modified September 2003
* Matthew D. Hardeman <mhardemn@papersoft.com>
*
2005-09-14 20:46:50 +00:00
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
2003-10-01 15:03:30 +00:00
* This program is free software, distributed under the terms of
2005-09-14 20:46:50 +00:00
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
2010-02-26 08:45:11 +00:00
/*!
* \file
2008-11-20 17:48:58 +00:00
* \brief PostgreSQL CDR logger
*
* \author Matthew D. Hardeman <mhardemn@papersoft.com>
2012-09-21 17:14:59 +00:00
* PostgreSQL http://www.postgresql.org/
2003-10-01 15:03:30 +00:00
*
2005-10-26 13:03:17 +00:00
* See also
* \arg \ref Config_cdr
2012-09-21 17:14:59 +00:00
* PostgreSQL http://www.postgresql.org/
2005-11-06 15:09:47 +00:00
* \ingroup cdr_drivers
2003-10-01 15:03:30 +00:00
*/
2012-10-14 21:46:45 +00:00
/*! \li \ref cdr_pgsql.c uses the configuration file \ref cdr_pgsql.conf
* \addtogroup configuration_file Configuration Files
*/
/*!
* \page cdr_pgsql.conf cdr_pgsql.conf
* \verbinclude cdr_pgsql.conf.sample
*/
2006-04-24 17:11:45 +00:00
/*** MODULEINFO
<depend>pgsql</depend>
2011-07-14 20:28:54 +00:00
<support_level>extended</support_level>
2006-04-24 17:11:45 +00:00
***/
2006-06-07 18:54:56 +00:00
#include "asterisk.h"
ASTERISK_FILE_VERSION ( __FILE__ , "$Revision$" )
2003-10-01 15:03:30 +00:00
#include <libpq-fe.h>
2005-06-06 21:09:59 +00:00
#include "asterisk/config.h"
#include "asterisk/channel.h"
#include "asterisk/cdr.h"
2011-09-29 12:03:23 +00:00
#include "asterisk/cli.h"
2005-06-06 21:09:59 +00:00
#include "asterisk/module.h"
2008-02-25 23:04:20 +00:00
#define DATE_FORMAT "'%Y-%m-%d %T'"
2003-10-01 15:03:30 +00:00
2010-02-26 08:04:07 +00:00
static const char name [] = "pgsql" ;
static const char config [] = "cdr_pgsql.conf" ;
2010-10-11 03:20:17 +00:00
static char * pghostname = NULL , * pgdbname = NULL , * pgdbuser = NULL , * pgpassword = NULL , * pgdbport = NULL , * table = NULL , * encoding = NULL , * tz = NULL ;
2003-10-01 15:03:30 +00:00
static int connected = 0 ;
2008-02-25 23:04:20 +00:00
static int maxsize = 512 , maxsize2 = 512 ;
2011-09-29 12:03:23 +00:00
static time_t connect_time = 0 ;
static int totalrecords = 0 ;
static int records ;
static char * handle_cdr_pgsql_status ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a );
static struct ast_cli_entry cdr_pgsql_status_cli [] = {
AST_CLI_DEFINE ( handle_cdr_pgsql_status , "Show connection status of the PostgreSQL CDR driver (cdr_pgsql)" ),
};
2003-10-01 15:03:30 +00:00
2004-06-09 01:45:08 +00:00
AST_MUTEX_DEFINE_STATIC ( pgsql_lock );
2003-10-01 15:03:30 +00:00
2006-11-13 21:32:13 +00:00
static PGconn * conn = NULL ;
2003-10-01 15:03:30 +00:00
2008-02-25 23:04:20 +00:00
struct columns {
char * name ;
char * type ;
int len ;
2008-04-11 23:26:56 +00:00
unsigned int notnull : 1 ;
unsigned int hasdefault : 1 ;
2008-02-25 23:04:20 +00:00
AST_RWLIST_ENTRY ( columns ) list ;
};
static AST_RWLIST_HEAD_STATIC ( psql_columns , columns );
2008-12-13 08:36:35 +00:00
#define LENGTHEN_BUF1(size) \
do { \
/* Lengthen buffer, if necessary */ \
if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) { \
2008-02-25 23:04:20 +00:00
ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR failed.\n"); \
2008-12-13 08:36:35 +00:00
ast_free(sql); \
ast_free(sql2); \
AST_RWLIST_UNLOCK(&psql_columns); \
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock); \
2008-12-13 08:36:35 +00:00
return -1; \
} \
} \
2008-02-25 23:04:20 +00:00
} while (0)
2008-12-13 08:36:35 +00:00
#define LENGTHEN_BUF2(size) \
do { \
if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) { \
if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) { \
2008-02-25 23:04:20 +00:00
ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR failed.\n"); \
2008-12-13 08:36:35 +00:00
ast_free(sql); \
ast_free(sql2); \
AST_RWLIST_UNLOCK(&psql_columns); \
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock); \
2008-12-13 08:36:35 +00:00
return -1; \
} \
} \
2008-02-25 23:04:20 +00:00
} while (0)
2011-09-29 12:03:23 +00:00
/*! \brief Handle the CLI command cdr show pgsql status */
static char * handle_cdr_pgsql_status ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
{
switch ( cmd ) {
case CLI_INIT :
e -> command = "cdr show pgsql status" ;
e -> usage =
"Usage: cdr show pgsql status \n "
" Shows current connection status for cdr_pgsql \n " ;
return NULL ;
case CLI_GENERATE :
return NULL ;
}
if ( a -> argc != 3 )
return CLI_SHOWUSAGE ;
if ( connected ) {
char status [ 256 ], status2 [ 100 ] = "" ;
int ctime = time ( NULL ) - connect_time ;
if ( pgdbport ) {
snprintf ( status , 255 , "Connected to %s@%s, port %s" , pgdbname , pghostname , pgdbport );
} else {
snprintf ( status , 255 , "Connected to %s@%s" , pgdbname , pghostname );
}
if ( pgdbuser && * pgdbuser ) {
snprintf ( status2 , 99 , " with username %s" , pgdbuser );
}
if ( table && * table ) {
snprintf ( status2 , 99 , " using table %s" , table );
}
if ( ctime > 31536000 ) {
ast_cli ( a -> fd , "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds. \n " , status , status2 , ctime / 31536000 , ( ctime % 31536000 ) / 86400 , ( ctime % 86400 ) / 3600 , ( ctime % 3600 ) / 60 , ctime % 60 );
} else if ( ctime > 86400 ) {
ast_cli ( a -> fd , "%s%s for %d days, %d hours, %d minutes, %d seconds. \n " , status , status2 , ctime / 86400 , ( ctime % 86400 ) / 3600 , ( ctime % 3600 ) / 60 , ctime % 60 );
} else if ( ctime > 3600 ) {
ast_cli ( a -> fd , "%s%s for %d hours, %d minutes, %d seconds. \n " , status , status2 , ctime / 3600 , ( ctime % 3600 ) / 60 , ctime % 60 );
} else if ( ctime > 60 ) {
ast_cli ( a -> fd , "%s%s for %d minutes, %d seconds. \n " , status , status2 , ctime / 60 , ctime % 60 );
} else {
ast_cli ( a -> fd , "%s%s for %d seconds. \n " , status , status2 , ctime );
}
if ( records == totalrecords ) {
ast_cli ( a -> fd , " Wrote %d records since last restart. \n " , totalrecords );
} else {
ast_cli ( a -> fd , " Wrote %d records since last restart and %d records since last reconnect. \n " , totalrecords , records );
}
} else {
ast_cli ( a -> fd , "Not currently connected to a PgSQL server. \n " );
}
return CLI_SUCCESS ;
}
2003-10-01 15:03:30 +00:00
static int pgsql_log ( struct ast_cdr * cdr )
{
2007-07-18 19:47:20 +00:00
struct ast_tm tm ;
2004-07-09 16:19:00 +00:00
char * pgerror ;
2007-06-12 19:40:41 +00:00
PGresult * result ;
2003-10-01 15:03:30 +00:00
ast_mutex_lock ( & pgsql_lock );
if (( ! connected ) && pghostname && pgdbuser && pgpassword && pgdbname ) {
conn = PQsetdbLogin ( pghostname , pgdbport , NULL , NULL , pgdbname , pgdbuser , pgpassword );
if ( PQstatus ( conn ) != CONNECTION_BAD ) {
connected = 1 ;
2011-09-29 12:03:23 +00:00
connect_time = time ( NULL );
records = 0 ;
2010-09-22 15:18:49 +00:00
if ( PQsetClientEncoding ( conn , encoding )) {
2010-09-24 03:41:02 +00:00
#ifdef HAVE_PGSQL_pg_encoding_to_char
2010-09-22 15:18:49 +00:00
ast_log ( LOG_WARNING , "Failed to set encoding to '%s'. Encoding set to default '%s' \n " , encoding , pg_encoding_to_char ( PQclientEncoding ( conn )));
2010-09-24 03:41:02 +00:00
#else
ast_log ( LOG_WARNING , "Failed to set encoding to '%s'. Encoding set to default. \n " , encoding );
#endif
2010-09-22 15:18:49 +00:00
}
2003-10-01 15:03:30 +00:00
} else {
2003-11-05 05:56:27 +00:00
pgerror = PQerrorMessage ( conn );
2008-04-20 14:52:07 +00:00
ast_log ( LOG_ERROR , "Unable to connect to database server %s. Calls will not be logged! \n " , pghostname );
ast_log ( LOG_ERROR , "Reason: %s \n " , pgerror );
2007-07-27 17:05:18 +00:00
PQfinish ( conn );
2007-07-26 18:31:28 +00:00
conn = NULL ;
2003-10-01 15:03:30 +00:00
}
}
if ( connected ) {
2008-02-25 23:04:20 +00:00
struct columns * cur ;
2008-08-24 16:23:15 +00:00
struct ast_str * sql = ast_str_create ( maxsize ), * sql2 = ast_str_create ( maxsize2 );
char buf [ 257 ], escapebuf [ 513 ], * value ;
2008-12-13 08:36:35 +00:00
int first = 1 ;
2012-02-07 15:29:14 +00:00
2008-08-24 16:23:15 +00:00
if ( ! sql || ! sql2 ) {
2012-02-07 15:29:14 +00:00
ast_free ( sql );
ast_free ( sql2 );
2008-08-24 16:23:15 +00:00
return - 1 ;
}
ast_str_set ( & sql , 0 , "INSERT INTO %s (" , table );
ast_str_set ( & sql2 , 0 , " VALUES (" );
2008-11-20 17:48:58 +00:00
2008-02-25 23:04:20 +00:00
AST_RWLIST_RDLOCK ( & psql_columns );
AST_RWLIST_TRAVERSE ( & psql_columns , cur , list ) {
/* For fields not set, simply skip them */
2013-06-17 03:00:38 +00:00
ast_cdr_format_var ( cdr , cur -> name , & value , buf , sizeof ( buf ), 0 );
2008-05-10 14:19:41 +00:00
if ( strcmp ( cur -> name , "calldate" ) == 0 && ! value ) {
2013-06-17 03:00:38 +00:00
ast_cdr_format_var ( cdr , "start" , & value , buf , sizeof ( buf ), 0 );
2008-05-10 14:19:41 +00:00
}
2008-04-11 23:26:56 +00:00
if ( ! value ) {
if ( cur -> notnull && ! cur -> hasdefault ) {
/* Field is NOT NULL (but no default), must include it anyway */
2008-06-29 12:06:46 +00:00
LENGTHEN_BUF1 ( strlen ( cur -> name ) + 2 );
2008-12-13 08:36:35 +00:00
ast_str_append ( & sql , 0 , "%s \" %s \" " , first ? "" : "," , cur -> name );
2008-04-11 23:26:56 +00:00
LENGTHEN_BUF2 ( 3 );
2008-12-13 08:36:35 +00:00
ast_str_append ( & sql2 , 0 , "%s''" , first ? "" : "," );
first = 0 ;
2008-04-11 23:26:56 +00:00
}
2008-02-25 23:04:20 +00:00
continue ;
2008-04-11 23:26:56 +00:00
}
2008-11-20 17:48:58 +00:00
2008-06-29 12:06:46 +00:00
LENGTHEN_BUF1 ( strlen ( cur -> name ) + 2 );
2008-12-13 08:36:35 +00:00
ast_str_append ( & sql , 0 , "%s \" %s \" " , first ? "" : "," , cur -> name );
2008-02-25 23:04:20 +00:00
if ( strcmp ( cur -> name , "start" ) == 0 || strcmp ( cur -> name , "calldate" ) == 0 ) {
if ( strncmp ( cur -> type , "int" , 3 ) == 0 ) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 13 );
2010-03-20 12:03:07 +00:00
ast_str_append ( & sql2 , 0 , "%s%ld" , first ? "" : "," , ( long ) cdr -> start . tv_sec );
2008-02-25 23:04:20 +00:00
} else if ( strncmp ( cur -> type , "float" , 5 ) == 0 ) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
ast_str_append ( & sql2 , 0 , "%s%f" , first ? "" : "," , ( double ) cdr -> start . tv_sec + ( double ) cdr -> start . tv_usec / 1000000.0 );
2008-02-25 23:04:20 +00:00
} else {
/* char, hopefully */
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
2010-10-11 03:20:17 +00:00
ast_localtime ( & cdr -> start , & tm , tz );
2008-08-24 16:23:15 +00:00
ast_strftime ( buf , sizeof ( buf ), DATE_FORMAT , & tm );
2008-12-13 08:36:35 +00:00
ast_str_append ( & sql2 , 0 , "%s%s" , first ? "" : "," , buf );
2008-02-25 23:04:20 +00:00
}
} else if ( strcmp ( cur -> name , "answer" ) == 0 ) {
if ( strncmp ( cur -> type , "int" , 3 ) == 0 ) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 13 );
2010-03-20 12:03:07 +00:00
ast_str_append ( & sql2 , 0 , "%s%ld" , first ? "" : "," , ( long ) cdr -> answer . tv_sec );
2008-02-25 23:04:20 +00:00
} else if ( strncmp ( cur -> type , "float" , 5 ) == 0 ) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
ast_str_append ( & sql2 , 0 , "%s%f" , first ? "" : "," , ( double ) cdr -> answer . tv_sec + ( double ) cdr -> answer . tv_usec / 1000000.0 );
2008-02-25 23:04:20 +00:00
} else {
/* char, hopefully */
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
2010-12-20 17:48:09 +00:00
ast_localtime ( & cdr -> answer , & tm , tz );
2008-08-24 16:23:15 +00:00
ast_strftime ( buf , sizeof ( buf ), DATE_FORMAT , & tm );
2008-12-13 08:36:35 +00:00
ast_str_append ( & sql2 , 0 , "%s%s" , first ? "" : "," , buf );
2008-02-25 23:04:20 +00:00
}
} else if ( strcmp ( cur -> name , "end" ) == 0 ) {
if ( strncmp ( cur -> type , "int" , 3 ) == 0 ) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 13 );
2010-03-20 12:03:07 +00:00
ast_str_append ( & sql2 , 0 , "%s%ld" , first ? "" : "," , ( long ) cdr -> end . tv_sec );
2008-02-25 23:04:20 +00:00
} else if ( strncmp ( cur -> type , "float" , 5 ) == 0 ) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
ast_str_append ( & sql2 , 0 , "%s%f" , first ? "" : "," , ( double ) cdr -> end . tv_sec + ( double ) cdr -> end . tv_usec / 1000000.0 );
2008-02-25 23:04:20 +00:00
} else {
/* char, hopefully */
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
2010-10-11 03:20:17 +00:00
ast_localtime ( & cdr -> end , & tm , tz );
2008-08-24 16:23:15 +00:00
ast_strftime ( buf , sizeof ( buf ), DATE_FORMAT , & tm );
2008-12-13 08:36:35 +00:00
ast_str_append ( & sql2 , 0 , "%s%s" , first ? "" : "," , buf );
2008-02-25 23:04:20 +00:00
}
} else if ( strcmp ( cur -> name , "duration" ) == 0 || strcmp ( cur -> name , "billsec" ) == 0 ) {
if ( cur -> type [ 0 ] == 'i' ) {
/* Get integer, no need to escape anything */
2013-06-17 03:00:38 +00:00
ast_cdr_format_var ( cdr , cur -> name , & value , buf , sizeof ( buf ), 0 );
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 13 );
ast_str_append ( & sql2 , 0 , "%s%s" , first ? "" : "," , value );
2008-02-25 23:04:20 +00:00
} else if ( strncmp ( cur -> type , "float" , 5 ) == 0 ) {
2010-12-20 17:48:09 +00:00
struct timeval * when = cur -> name [ 0 ] == 'd' ? & cdr -> start : ast_tvzero ( cdr -> answer ) ? & cdr -> end : & cdr -> answer ;
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
2010-06-08 23:48:17 +00:00
ast_str_append ( & sql2 , 0 , "%s%f" , first ? "" : "," , ( double ) ( ast_tvdiff_us ( cdr -> end , * when ) / 1000000.0 ));
2008-02-25 23:04:20 +00:00
} else {
/* Char field, probably */
2010-12-20 17:48:09 +00:00
struct timeval * when = cur -> name [ 0 ] == 'd' ? & cdr -> start : ast_tvzero ( cdr -> answer ) ? & cdr -> end : & cdr -> answer ;
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 31 );
2010-06-08 23:48:17 +00:00
ast_str_append ( & sql2 , 0 , "%s'%f'" , first ? "" : "," , ( double ) ( ast_tvdiff_us ( cdr -> end , * when ) / 1000000.0 ));
2008-02-25 23:04:20 +00:00
}
} else if ( strcmp ( cur -> name , "disposition" ) == 0 || strcmp ( cur -> name , "amaflags" ) == 0 ) {
if ( strncmp ( cur -> type , "int" , 3 ) == 0 ) {
/* Integer, no need to escape anything */
2013-06-17 03:00:38 +00:00
ast_cdr_format_var ( cdr , cur -> name , & value , buf , sizeof ( buf ), 1 );
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2 ( 13 );
ast_str_append ( & sql2 , 0 , "%s%s" , first ? "" : "," , value );
2008-02-25 23:04:20 +00:00
} else {
/* Although this is a char field, there are no special characters in the values for these fields */
2013-06-17 03:00:38 +00:00
ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2(31);
ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
2008-02-25 23:04:20 +00:00
}
} else {
/* Arbitrary field, could be anything */
2013-06-17 03:00:38 +00:00
ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
2008-02-25 23:04:20 +00:00
if (strncmp(cur->type, "int", 3) == 0) {
long long whatever;
2009-08-10 19:20:57 +00:00
if (value && sscanf(value, "%30lld", &whatever) == 1) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2(26);
ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", whatever);
2008-02-25 23:04:20 +00:00
} else {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2(2);
ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
2008-02-25 23:04:20 +00:00
}
} else if (strncmp(cur->type, "float", 5) == 0) {
long double whatever;
2009-08-10 19:20:57 +00:00
if (value && sscanf(value, "%30Lf", &whatever) == 1) {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2(51);
ast_str_append(&sql2, 0, "%s%30Lf", first ? "" : ",", whatever);
2008-02-25 23:04:20 +00:00
} else {
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2(2);
ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
2008-02-25 23:04:20 +00:00
}
/* XXX Might want to handle dates, times, and other misc fields here XXX */
} else {
if (value)
PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
else
escapebuf[0] = '\0';
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF2(strlen(escapebuf) + 3);
ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", escapebuf);
2008-02-25 23:04:20 +00:00
}
}
2008-12-13 08:36:35 +00:00
first = 0;
2012-02-07 15:29:14 +00:00
}
2008-12-13 08:36:35 +00:00
LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
2012-02-07 15:29:14 +00:00
AST_RWLIST_UNLOCK(&psql_columns);
2008-12-13 08:36:35 +00:00
ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
ast_verb(11, "[%s]\n", ast_str_buffer(sql));
2003-10-01 15:03:30 +00:00
2008-04-20 14:52:07 +00:00
ast_debug(2, "inserting a CDR record.\n");
2003-10-01 15:03:30 +00:00
2003-11-05 05:56:27 +00:00
/* Test to be sure we're still connected... */
/* If we're connected, and connection is working, good. */
/* Otherwise, attempt reconnect. If it fails... sorry... */
if (PQstatus(conn) == CONNECTION_OK) {
connected = 1;
} else {
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
2003-11-05 05:56:27 +00:00
PQreset(conn);
if (PQstatus(conn) == CONNECTION_OK) {
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Connection reestablished.\n");
2003-11-05 05:56:27 +00:00
connected = 1;
2011-09-29 12:03:23 +00:00
connect_time = time(NULL);
records = 0;
2003-11-05 05:56:27 +00:00
} else {
pgerror = PQerrorMessage(conn);
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
2007-07-27 17:05:18 +00:00
PQfinish(conn);
2007-07-26 18:31:28 +00:00
conn = NULL;
2003-11-05 05:56:27 +00:00
connected = 0;
2004-01-22 17:03:11 +00:00
ast_mutex_unlock(&pgsql_lock);
2008-08-24 16:26:48 +00:00
ast_free(sql);
ast_free(sql2);
2003-11-05 05:56:27 +00:00
return -1;
}
}
2008-12-13 08:36:35 +00:00
result = PQexec(conn, ast_str_buffer(sql));
2007-06-12 19:40:41 +00:00
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
pgerror = PQresultErrorMessage(result);
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
2005-02-19 20:10:52 +00:00
PQreset(conn);
if (PQstatus(conn) == CONNECTION_OK) {
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Connection reestablished.\n");
2005-02-19 20:10:52 +00:00
connected = 1;
2011-09-29 12:03:23 +00:00
connect_time = time(NULL);
records = 0;
2007-06-20 23:33:49 +00:00
PQclear(result);
2008-12-13 08:36:35 +00:00
result = PQexec(conn, ast_str_buffer(sql));
2007-06-12 19:40:41 +00:00
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
2005-02-19 20:10:52 +00:00
pgerror = PQresultErrorMessage(result);
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
2011-09-29 12:03:23 +00:00
} else {
/* Second try worked out ok */
totalrecords++;
records++;
ast_mutex_unlock(&pgsql_lock);
PQclear(result);
return 0;
2005-02-19 20:10:52 +00:00
}
}
2003-10-01 15:03:30 +00:00
ast_mutex_unlock(&pgsql_lock);
2007-06-12 19:40:41 +00:00
PQclear(result);
2008-08-24 16:23:15 +00:00
ast_free(sql);
ast_free(sql2);
2003-10-01 15:03:30 +00:00
return -1;
2011-09-29 12:03:23 +00:00
} else {
totalrecords++;
records++;
2003-10-01 15:03:30 +00:00
}
2007-06-12 19:40:41 +00:00
PQclear(result);
2008-08-24 16:23:15 +00:00
ast_free(sql);
ast_free(sql2);
2003-10-01 15:03:30 +00:00
}
ast_mutex_unlock(&pgsql_lock);
return 0;
}
2012-02-07 15:29:14 +00:00
/* This function should be called without holding the pgsql_columns lock */
static void empty_columns(void)
2008-02-25 23:04:20 +00:00
{
2008-08-07 00:52:23 +00:00
struct columns *current;
2012-02-07 15:29:14 +00:00
AST_RWLIST_WRLOCK(&psql_columns);
while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
ast_free(current);
}
AST_RWLIST_UNLOCK(&psql_columns);
2009-06-20 14:09:40 +00:00
2012-02-07 15:29:14 +00:00
}
static int unload_module(void)
{
2008-02-25 23:04:20 +00:00
ast_cdr_unregister(name);
2011-09-29 12:03:23 +00:00
ast_cli_unregister_multiple(cdr_pgsql_status_cli, ARRAY_LEN(cdr_pgsql_status_cli));
2008-02-25 23:04:20 +00:00
2007-07-26 18:31:28 +00:00
PQfinish(conn);
2008-02-25 23:04:20 +00:00
2012-02-07 15:29:14 +00:00
ast_free(pghostname);
ast_free(pgdbname);
ast_free(pgdbuser);
ast_free(pgpassword);
ast_free(pgdbport);
ast_free(table);
ast_free(encoding);
ast_free(tz);
2008-02-25 23:04:20 +00:00
2012-02-07 15:29:14 +00:00
empty_columns();
2008-02-25 23:04:20 +00:00
2003-10-01 15:03:30 +00:00
return 0;
}
2007-08-16 21:09:46 +00:00
static int config_module(int reload)
2003-10-01 15:03:30 +00:00
{
2007-08-16 21:09:46 +00:00
char *pgerror;
2008-02-25 23:04:20 +00:00
struct columns *cur;
PGresult *result;
2006-09-20 21:03:37 +00:00
const char *tmp;
2007-08-16 21:09:46 +00:00
struct ast_config *cfg;
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2008-11-19 19:25:14 +00:00
if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
2007-08-16 21:09:46 +00:00
ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
return -1;
2010-09-27 19:30:18 +00:00
} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2007-08-16 21:09:46 +00:00
return 0;
2010-09-27 19:30:18 +00:00
}
2003-10-01 15:03:30 +00:00
2012-02-07 15:29:14 +00:00
ast_mutex_lock(&pgsql_lock);
2012-04-17 18:57:40 +00:00
if (!ast_variable_browse(cfg, "global")) {
2007-09-20 16:10:57 +00:00
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
ast_log(LOG_NOTICE, "cdr_pgsql configuration contains no global section, skipping module %s.\n",
reload ? "reload" : "load");
return -1;
2007-09-20 16:10:57 +00:00
}
2003-10-01 15:03:30 +00:00
2007-08-16 21:09:46 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
ast_log(LOG_WARNING, "PostgreSQL server hostname not specified. Assuming unix socket connection\n");
2006-05-20 00:57:04 +00:00
tmp = ""; /* connect via UNIX-socket by default */
2005-07-11 21:06:27 +00:00
}
2007-08-16 21:09:46 +00:00
2012-02-07 15:29:14 +00:00
ast_free(pghostname);
2007-09-20 16:10:57 +00:00
if (!(pghostname = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2005-07-11 21:06:27 +00:00
return -1;
2007-09-20 16:10:57 +00:00
}
2003-10-01 15:03:30 +00:00
2006-05-20 01:29:08 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
2008-04-20 14:52:07 +00:00
ast_log(LOG_WARNING, "PostgreSQL database not specified. Assuming asterisk\n");
2005-07-11 21:06:27 +00:00
tmp = "asteriskcdrdb";
}
2006-05-20 01:29:08 +00:00
2012-02-07 15:29:14 +00:00
ast_free(pgdbname);
2007-09-20 16:10:57 +00:00
if (!(pgdbname = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2005-07-11 21:06:27 +00:00
return -1;
2007-09-20 16:10:57 +00:00
}
2003-10-01 15:03:30 +00:00
2006-05-20 01:29:08 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
2008-04-20 14:52:07 +00:00
ast_log(LOG_WARNING, "PostgreSQL database user not specified. Assuming asterisk\n");
2006-05-20 01:35:12 +00:00
tmp = "asterisk";
2005-07-11 21:06:27 +00:00
}
2006-05-20 01:29:08 +00:00
2012-02-07 15:29:14 +00:00
ast_free(pgdbuser);
2007-09-20 16:10:57 +00:00
if (!(pgdbuser = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2005-07-11 21:06:27 +00:00
return -1;
2007-09-20 16:10:57 +00:00
}
2003-10-01 15:03:30 +00:00
2006-05-20 01:29:08 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
2008-04-20 14:52:07 +00:00
ast_log(LOG_WARNING, "PostgreSQL database password not specified. Assuming blank\n");
2005-07-11 21:06:27 +00:00
tmp = "";
}
2006-05-20 01:29:08 +00:00
2012-02-07 15:29:14 +00:00
ast_free(pgpassword);
2007-09-20 16:10:57 +00:00
if (!(pgpassword = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2005-07-11 21:06:27 +00:00
return -1;
2007-09-20 16:10:57 +00:00
}
2003-10-01 15:03:30 +00:00
2008-04-20 14:52:07 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
ast_log(LOG_WARNING, "PostgreSQL database port not specified. Using default 5432.\n");
2005-07-11 21:06:27 +00:00
tmp = "5432";
}
2006-05-20 01:29:08 +00:00
2012-02-07 15:29:14 +00:00
ast_free(pgdbport);
2007-09-20 16:10:57 +00:00
if (!(pgdbport = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2005-07-11 21:06:27 +00:00
return -1;
2007-09-20 16:10:57 +00:00
}
2005-07-11 21:06:27 +00:00
2006-05-20 01:29:08 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
2008-04-20 14:52:07 +00:00
ast_log(LOG_WARNING, "CDR table not specified. Assuming cdr\n");
2005-07-11 21:06:27 +00:00
tmp = "cdr";
}
2006-05-20 01:29:08 +00:00
2012-02-07 15:29:14 +00:00
ast_free(table);
2007-09-20 16:10:57 +00:00
if (!(table = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2005-07-11 21:06:27 +00:00
return -1;
2007-09-20 16:10:57 +00:00
}
2003-10-01 15:03:30 +00:00
2010-09-22 15:18:49 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
ast_log(LOG_WARNING, "Encoding not specified. Assuming LATIN9\n");
tmp = "LATIN9";
}
2012-02-07 15:29:14 +00:00
ast_free(encoding);
2010-09-22 15:18:49 +00:00
if (!(encoding = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2010-09-22 15:18:49 +00:00
return -1;
}
2010-10-11 03:20:17 +00:00
if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
tmp = "";
}
2012-02-07 15:29:14 +00:00
ast_free(tz);
tz = NULL;
2010-10-11 03:20:17 +00:00
if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2010-10-11 03:20:17 +00:00
return -1;
}
2006-01-24 11:46:29 +00:00
if (option_debug) {
2008-03-11 16:28:03 +00:00
if (ast_strlen_zero(pghostname)) {
2008-04-20 14:52:07 +00:00
ast_debug(1, "using default unix socket\n");
2008-03-11 16:28:03 +00:00
} else {
2008-04-20 14:52:07 +00:00
ast_debug(1, "got hostname of %s\n", pghostname);
2008-03-11 16:28:03 +00:00
}
2008-04-20 14:52:07 +00:00
ast_debug(1, "got port of %s\n", pgdbport);
ast_debug(1, "got user of %s\n", pgdbuser);
ast_debug(1, "got dbname of %s\n", pgdbname);
ast_debug(1, "got password of %s\n", pgpassword);
ast_debug(1, "got sql table name of %s\n", table);
2010-10-11 03:20:17 +00:00
ast_debug(1, "got encoding of %s\n", encoding);
ast_debug(1, "got timezone of %s\n", tz);
2006-01-24 11:46:29 +00:00
}
2008-03-11 16:28:03 +00:00
2003-10-01 15:03:30 +00:00
conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
if (PQstatus(conn) != CONNECTION_BAD) {
2009-10-06 19:31:39 +00:00
char sqlcmd[768];
2008-04-11 23:26:56 +00:00
char *fname, *ftype, *flen, *fnotnull, *fdef;
2009-10-06 19:31:39 +00:00
int i, rows, version;
2007-06-14 19:39:12 +00:00
ast_debug(1, "Successfully connected to PostgreSQL database.\n");
2003-10-01 15:03:30 +00:00
connected = 1;
2011-09-29 12:03:23 +00:00
connect_time = time(NULL);
records = 0;
2010-09-22 15:18:49 +00:00
if (PQsetClientEncoding(conn, encoding)) {
2010-09-24 03:41:02 +00:00
#ifdef HAVE_PGSQL_pg_encoding_to_char
2010-09-22 15:18:49 +00:00
ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
2010-09-24 03:41:02 +00:00
#else
ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
#endif
2010-09-22 15:18:49 +00:00
}
2009-10-06 19:31:39 +00:00
version = PQserverVersion(conn);
if (version >= 70300) {
char *schemaname, *tablename;
if (strchr(table, '.')) {
schemaname = ast_strdupa(table);
tablename = strchr(schemaname, '.');
*tablename++ = '\0';
} else {
schemaname = "";
tablename = table;
}
/* Escape special characters in schemaname */
if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
char *tmp = schemaname, *ptr;
2012-07-31 20:21:43 +00:00
ptr = schemaname = ast_alloca(strlen(tmp) * 2 + 1);
2009-10-06 19:31:39 +00:00
for (; *tmp; tmp++) {
if (strchr("\\'", *tmp)) {
*ptr++ = *tmp;
}
*ptr++ = *tmp;
}
*ptr = '\0';
}
/* Escape special characters in tablename */
if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
char *tmp = tablename, *ptr;
2012-07-31 20:21:43 +00:00
ptr = tablename = ast_alloca(strlen(tmp) * 2 + 1);
2009-10-06 19:31:39 +00:00
for (; *tmp; tmp++) {
if (strchr("\\'", *tmp)) {
*ptr++ = *tmp;
}
*ptr++ = *tmp;
}
*ptr = '\0';
}
2008-02-25 23:04:20 +00:00
2009-10-06 19:31:39 +00:00
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",
tablename,
ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
2008-12-15 18:09:58 +00:00
} else {
2009-10-06 19:31:39 +00:00
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);
2008-12-15 18:09:58 +00:00
}
2008-02-25 23:04:20 +00:00
/* Query the columns */
result = PQexec(conn, sqlcmd);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
pgerror = PQresultErrorMessage(result);
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
2008-02-25 23:04:20 +00:00
PQclear(result);
unload_module();
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2008-02-25 23:04:20 +00:00
return AST_MODULE_LOAD_DECLINE;
}
rows = PQntuples(result);
2010-07-12 15:37:01 +00:00
if (rows == 0) {
ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
PQclear(result);
unload_module();
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
2010-07-12 15:37:01 +00:00
return AST_MODULE_LOAD_DECLINE;
}
2012-02-07 15:29:14 +00:00
/* Clear out the columns list. */
empty_columns();
2008-02-25 23:04:20 +00:00
for (i = 0; i < rows; i++) {
fname = PQgetvalue(result, i, 0);
ftype = PQgetvalue(result, i, 1);
flen = PQgetvalue(result, i, 2);
2008-04-11 23:26:56 +00:00
fnotnull = PQgetvalue(result, i, 3);
fdef = PQgetvalue(result, i, 4);
2009-10-06 19:31:39 +00:00
if (atoi(flen) == -1) {
/* For varchar columns, the maximum length is encoded in a different field */
flen = PQgetvalue(result, i, 5);
}
2008-02-25 23:04:20 +00:00
ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
if (cur) {
2009-08-10 19:20:57 +00:00
sscanf(flen, "%30d", &cur->len);
2008-02-25 23:04:20 +00:00
cur->name = (char *)cur + sizeof(*cur);
cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
strcpy(cur->name, fname);
strcpy(cur->type, ftype);
2008-04-11 23:26:56 +00:00
if (*fnotnull == 't') {
cur->notnull = 1;
} else {
cur->notnull = 0;
}
if (!ast_strlen_zero(fdef)) {
cur->hasdefault = 1;
} else {
cur->hasdefault = 0;
}
2012-02-07 15:29:14 +00:00
AST_RWLIST_WRLOCK(&psql_columns);
2008-02-25 23:04:20 +00:00
AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
2012-02-07 15:29:14 +00:00
AST_RWLIST_UNLOCK(&psql_columns);
2008-02-25 23:04:20 +00:00
}
}
PQclear(result);
2003-10-01 15:03:30 +00:00
} else {
2007-08-16 21:09:46 +00:00
pgerror = PQerrorMessage(conn);
2008-04-20 14:52:07 +00:00
ast_log(LOG_ERROR, "Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
2003-10-01 15:03:30 +00:00
connected = 0;
}
2007-09-20 16:10:57 +00:00
ast_config_destroy(cfg);
2012-02-07 15:29:14 +00:00
ast_mutex_unlock(&pgsql_lock);
return 0;
2003-10-01 15:03:30 +00:00
}
2006-08-21 02:11:39 +00:00
static int load_module(void)
2003-10-01 15:03:30 +00:00
{
2011-09-29 12:03:23 +00:00
ast_cli_register_multiple(cdr_pgsql_status_cli, sizeof(cdr_pgsql_status_cli) / sizeof(struct ast_cli_entry));
2012-02-07 15:29:14 +00:00
if (config_module(0)) {
return AST_MODULE_LOAD_DECLINE;
}
return ast_cdr_register(name, ast_module_info->description, pgsql_log)
? AST_MODULE_LOAD_DECLINE : 0;
2003-10-01 15:03:30 +00:00
}
2006-08-21 02:11:39 +00:00
static int reload(void)
2003-10-01 15:03:30 +00:00
{
2007-08-16 21:09:46 +00:00
return config_module(1);
2003-10-01 15:03:30 +00:00
}
2010-07-20 19:35:02 +00:00
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
2006-08-21 02:11:39 +00:00
.load = load_module,
.unload = unload_module,
.reload = reload,
2010-07-20 19:35:02 +00:00
.load_pri = AST_MODPRI_CDR_DRIVER,
2006-08-21 02:11:39 +00:00
);