Index: cdr/cdr_odbc.c =================================================================== --- cdr/cdr_odbc.c (revision 9118) +++ cdr/cdr_odbc.c (working copy) @@ -57,6 +57,8 @@ #include "asterisk/cdr.h" #include "asterisk/module.h" #include "asterisk/logger.h" +#include "asterisk/pbx.h" +#include "asterisk/linkedlists.h" #define DATE_FORMAT "%Y-%m-%d %T" @@ -64,9 +66,18 @@ static char *name = "ODBC"; static char *config = "cdr_odbc.conf"; static char *dsn = NULL, *username = NULL, *password = NULL, *table = NULL; -static int loguniqueid = 0; +static char sqlcmd[4096]; + +struct field { + char name[128]; /*!< The field name */ + char value_expr[128]; /*!< The expression to get value */ + SQLSMALLINT type; + AST_LIST_ENTRY(field) list; +}; + +static AST_LIST_HEAD_STATIC(field_list, field); /*!< The Field list */ + static int usegmtime = 0; -static int dispositionstring = 0; static int connected = 0; AST_MUTEX_DEFINE_STATIC(odbc_lock); @@ -78,15 +89,57 @@ static SQLHDBC ODBC_con; /* global ODBC Connection Handle */ static SQLHSTMT ODBC_stmt; /* global ODBC Statement Handle */ +static void odbc_preparesql(void) +{ + char *sqlptr; + struct field *f; + int idx; + int sz = sizeof(sqlcmd) - 1; + + memset(sqlcmd, 0, sizeof(sqlcmd)); + sqlptr = sqlcmd; + snprintf(sqlptr, sz, "INSERT INTO %s (", table); + sz = sz - strlen(sqlptr); + sqlptr = sqlptr + strlen(sqlptr); + + idx = 0; + AST_LIST_TRAVERSE(&field_list, f, list) { + snprintf(sqlptr, sz, (idx == 0) ? "%s" : ",%s", f->name); + sz = sz - strlen(sqlptr); + sqlptr = sqlptr + strlen(sqlptr); + idx++; + } + snprintf(sqlptr, sz, "%s", ") values ("); + sz = sz - strlen(sqlptr); + sqlptr = sqlptr + strlen(sqlptr); + + idx = 0; + AST_LIST_TRAVERSE(&field_list, f, list) { + snprintf(sqlptr, sz, "%s", (idx == 0) ? "?" : ",?"); + sz = sz - strlen(sqlptr); + sqlptr = sqlptr + strlen(sqlptr); + idx++; + } + snprintf(sqlptr, sz, "%s", ")"); + + ast_log(LOG_NOTICE, "cdr_odbc: sql : len=%d\n%s\n", strlen(sqlcmd), sqlcmd); +} + + static int odbc_log(struct ast_cdr *cdr) { SQLINTEGER ODBC_err; + SQLINTEGER len; short int ODBC_mlen; int ODBC_res; char ODBC_msg[200], ODBC_stat[10]; - char sqlcmd[2048] = "", timestr[128]; + char timestr[128]; + char *buff; + int idx; int res = 0; struct tm tm; + struct field *f; + struct ast_channel dummy; if (usegmtime) gmtime_r(&cdr->start.tv_sec,&tm); @@ -95,19 +148,8 @@ ast_mutex_lock(&odbc_lock); strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm); - memset(sqlcmd,0,2048); - if (loguniqueid) { - snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s " - "(calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp," - "lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) " - "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", table); - } else { - snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s " - "(calldate,clid,src,dst,dcontext,channel,dstchannel,lastapp,lastdata," - "duration,billsec,disposition,amaflags,accountcode) " - "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", table); - } - + // TODO: check the limits correctly + if (!connected) { res = odbc_init(); if (res < 0) { @@ -138,34 +180,34 @@ if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { if (option_verbose > 10) ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in PREPARE %d\n", ODBC_res); - SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen); + SQLGetDiagRec(SQL_HANDLE_STMT, ODBC_stmt, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen); + ast_log(LOG_ERROR, "cdr_odbc: Error in prepare: %d: %s\n", ODBC_res, ODBC_msg); SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); connected = 0; ast_mutex_unlock(&odbc_lock); return 0; } - SQLBindParameter(ODBC_stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(timestr), 0, ×tr, 0, NULL); - SQLBindParameter(ODBC_stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->clid), 0, cdr->clid, 0, NULL); - SQLBindParameter(ODBC_stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->src), 0, cdr->src, 0, NULL); - SQLBindParameter(ODBC_stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dst), 0, cdr->dst, 0, NULL); - SQLBindParameter(ODBC_stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dcontext), 0, cdr->dcontext, 0, NULL); - SQLBindParameter(ODBC_stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->channel), 0, cdr->channel, 0, NULL); - SQLBindParameter(ODBC_stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dstchannel), 0, cdr->dstchannel, 0, NULL); - SQLBindParameter(ODBC_stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastapp), 0, cdr->lastapp, 0, NULL); - SQLBindParameter(ODBC_stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastdata), 0, cdr->lastdata, 0, NULL); - SQLBindParameter(ODBC_stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->duration, 0, NULL); - SQLBindParameter(ODBC_stmt, 11, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL); - if (dispositionstring) - SQLBindParameter(ODBC_stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL); - else - SQLBindParameter(ODBC_stmt, 12, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->disposition, 0, NULL); - SQLBindParameter(ODBC_stmt, 13, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->amaflags, 0, NULL); - SQLBindParameter(ODBC_stmt, 14, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->accountcode), 0, cdr->accountcode, 0, NULL); + memset(&dummy, 0, sizeof(dummy)); + dummy.cdr = cdr; + idx = 1; + len = SQL_NTS; +#define BUFF_SZ 2048 + AST_LIST_TRAVERSE(&field_list, f, list) { + buff = alloca(BUFF_SZ); + memset(buff, 0, BUFF_SZ); + pbx_substitute_variables_helper(&dummy, f->value_expr, buff, BUFF_SZ - 1); + ODBC_res = SQLBindParameter(ODBC_stmt, idx, SQL_PARAM_INPUT, SQL_C_CHAR, f->type, + 0, 0, buff, strlen(buff), NULL); - if (loguniqueid) { - SQLBindParameter(ODBC_stmt, 15, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->uniqueid), 0, cdr->uniqueid, 0, NULL); - SQLBindParameter(ODBC_stmt, 16, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->userfield), 0, cdr->userfield, 0, NULL); + ast_verbose(VERBOSE_PREFIX_1 "cdr_odbc: %d res=%d %s = %s len=%d type=%d\n", + idx, ODBC_res, f->name, buff, strlen(buff), f->type); + + if (ODBC_res != SQL_SUCCESS) { + ast_log(LOG_ERROR, "cdr_odbc: parameter format error %s = %s, err=%d\n", + f->name, f->value_expr, ODBC_res); + } + idx++; } if (connected) { @@ -206,6 +248,8 @@ static int odbc_unload_module(void) { + struct field *f; + ast_mutex_lock(&odbc_lock); if (connected) { if (option_verbose > 10) @@ -237,17 +281,46 @@ free(table); } + AST_LIST_LOCK(&field_list); + while ((f = AST_LIST_REMOVE_HEAD(&field_list, list))) + free(f); + AST_LIST_UNLOCK(&field_list); + ast_cdr_unregister(name); ast_mutex_unlock(&odbc_lock); return 0; } +static int odbc_add_field(char *name, char *expr, SQLSMALLINT type) +{ + struct field *f; + + if (!ast_strlen_zero(name) && !ast_strlen_zero(expr)) { + f = calloc(1, sizeof(*f)); + if (!f) { + ast_log(LOG_ERROR, "Allocation of domain structure failed, Out of memory\n"); + return -1; + } else { + ast_copy_string(f->name, name, sizeof(f->name)); + ast_copy_string(f->value_expr, expr, sizeof(f->value_expr)); + f->type = type; + AST_LIST_LOCK(&field_list); + AST_LIST_INSERT_TAIL(&field_list, f, list); + AST_LIST_UNLOCK(&field_list); + } + + } + return 0; +} + static int odbc_load_module(void) { int res = 0; struct ast_config *cfg; struct ast_variable *var; char *tmp; + int loguniqueid; + int dispositionstring = 0; ast_mutex_lock(&odbc_lock); @@ -340,6 +413,43 @@ goto out; } + var = ast_variable_browse(cfg,"fields"); + while (var) { + if (!ast_strlen_zero(var->value)) { + if (strncmp(var->value, "INTEGER=", 8) == 0) { + odbc_add_field(var->name, var->value + 8, SQL_INTEGER); + } else { + odbc_add_field(var->name, var->value, SQL_CHAR); + } + } + var = var->next; + } + + if (AST_LIST_EMPTY(&field_list)) { + odbc_add_field("clid", "${CDR(clid)}", SQL_CHAR); + odbc_add_field("src", "${CDR(src)}", SQL_CHAR); + odbc_add_field("dst", "${CDR(dst)}", SQL_CHAR); + odbc_add_field("dcontext", "${CDR(dcontext)}", SQL_CHAR); + odbc_add_field("channel", "${CDR(channel)}", SQL_CHAR); + odbc_add_field("dstchannel", "${CDR(dstchannel)}", SQL_CHAR); + odbc_add_field("lastapp", "${CDR(lastapp)}", SQL_CHAR); + odbc_add_field("lastdata","${CDR(lastdata)}", SQL_CHAR); + odbc_add_field("duration", "${CDR(duration)}", SQL_INTEGER); + odbc_add_field("billsec", "${CDR(billsec)}", SQL_INTEGER); + if (dispositionstring) { + odbc_add_field("disposition", "${CDR(disposition)}", SQL_CHAR); + } else { + odbc_add_field("disposition", "${CDR(disposition_code)}", SQL_CHAR); + } + odbc_add_field("amaflags","${CDR(amaflags_code)}", SQL_INTEGER); + odbc_add_field("accountcode","${CDR(accountcode)}", SQL_CHAR); + + if (loguniqueid) { + odbc_add_field("uniqueid","${CDR(uniqueid)}", SQL_CHAR); + odbc_add_field("userfield", "${CDR(userfield)}", SQL_CHAR); + } + } + ast_config_destroy(cfg); if (option_verbose > 2) { ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: dsn is %s\n",dsn); @@ -353,6 +463,7 @@ ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: table is %s\n",table); } + odbc_preparesql(); res = odbc_init(); if (res < 0) { ast_log(LOG_ERROR, "cdr_odbc: Unable to connect to datasource: %s\n", dsn); @@ -379,9 +490,10 @@ ODBC_res = SQLExecute(ODBC_stmt); if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_STMT, ODBC_stmt, 1, ODBC_stat, &ODBC_err, ODBC_msg, sizeof(ODBC_msg) - 1, &ODBC_mlen); if (option_verbose > 10) - ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in Query %d\n", ODBC_res); - SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen); + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in Query %d : %s %s\n", + ODBC_res, ODBC_stat, ODBC_msg); SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); connected = 0; return -1;