Index: cdr/cdr_odbc.c =================================================================== --- cdr/cdr_odbc.c (revision 34550) +++ cdr/cdr_odbc.c (working copy) @@ -26,6 +26,9 @@ * \arg http://www.unixodbc.org * \arg \ref Config_cdr * \ingroup cdr_drivers + * + * + * XXX MUST BE UPDATED TO USE res_odbc */ /*** MODULEINFO @@ -61,16 +64,28 @@ #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" - static char *desc = "ODBC CDR Backend"; 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 int usegmtime = 0; -static int dispositionstring = 0; +static char sqlcmd[4096]; + +struct field { + char name[128]; /*!< The field name */ + char value_expr[128]; /*!< The expression to get value */ + SQLSMALLINT type; + int setnull; /*!< The field can be set to null */ + + char buff[2048]; /*!< The parameter buffer for the prepared SQL */ + SQLINTEGER len; /*!< The length of buffer for the prepared SQL */ + AST_LIST_ENTRY(field) list; +}; + +static AST_LIST_HEAD_STATIC(field_list, field); /*!< The Field list */ + static int connected = 0; AST_MUTEX_DEFINE_STATIC(odbc_lock); @@ -79,44 +94,108 @@ static int odbc_init(void); static SQLHENV ODBC_env = SQL_NULL_HANDLE; /* global ODBC Environment */ -static SQLHDBC ODBC_con; /* global ODBC Connection Handle */ -static SQLHSTMT ODBC_stmt; /* global ODBC Statement Handle */ +static SQLHDBC ODBC_con = SQL_NULL_HANDLE; /* global ODBC Connection Handle */ +static SQLHSTMT ODBC_stmt = SQL_NULL_HANDLE; /* global ODBC Statement Handle */ + static void odbc_disconnect(void) { - SQLDisconnect(ODBC_con); - SQLFreeHandle(SQL_HANDLE_DBC, ODBC_con); - SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env); + if (option_verbose > 10) + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Disconnecting from %s\n", dsn); + + if (ODBC_stmt != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); + ODBC_stmt = SQL_NULL_HANDLE; + } + if (ODBC_con != SQL_NULL_HANDLE) { + SQLDisconnect(ODBC_con); + SQLFreeHandle(SQL_HANDLE_DBC, ODBC_con); + ODBC_con = SQL_NULL_HANDLE; + } + if (ODBC_env != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env); + ODBC_env = SQL_NULL_HANDLE; + } + ODBC_stmt = SQL_NULL_HANDLE; connected = 0; } + +static int odbc_add_field(char *name, char *expr, SQLSMALLINT type, int setnull) +{ + 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; + f->setnull = setnull; + AST_LIST_LOCK(&field_list); + AST_LIST_INSERT_TAIL(&field_list, f, list); + AST_LIST_UNLOCK(&field_list); + } + + } + return 0; +} + +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", ")"); + + if (option_verbose > 10) + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: insert_sql : len=%d\n%s\n", strlen(sqlcmd), sqlcmd); +} + + static int odbc_log(struct ast_cdr *cdr) { int ODBC_res; - char sqlcmd[2048] = "", timestr[128]; + int idx; int res = 0; - struct tm tm; + struct field *f; + struct ast_channel dummy; - if (usegmtime) - gmtime_r(&cdr->start.tv_sec,&tm); - else - localtime_r(&cdr->start.tv_sec,&tm); + short int ODBC_mlen; + SQLINTEGER ODBC_err; + char ODBC_msg[200], ODBC_stat[10]; 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); - } - + if (!connected) { res = odbc_init(); if (res < 0) { @@ -129,9 +208,10 @@ ODBC_res = SQLAllocHandle(SQL_HANDLE_STMT, ODBC_con, &ODBC_stmt); if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, (unsigned char *)ODBC_stat, &ODBC_err, (unsigned char *)ODBC_msg, 100, &ODBC_mlen); if (option_verbose > 10) - ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Failure in AllocStatement %d\n", ODBC_res); - SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Failure in AllocStatement %d : %s %s \n", + ODBC_res, ODBC_stat, ODBC_msg); odbc_disconnect(); ast_mutex_unlock(&odbc_lock); return 0; @@ -144,35 +224,41 @@ ODBC_res = SQLPrepare(ODBC_stmt, (unsigned char *)sqlcmd, SQL_NTS); if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_STMT, ODBC_stmt, 1, (unsigned char *)ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen); if (option_verbose > 10) - ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in PREPARE %d\n", ODBC_res); - SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in PREPARE %d: %s %s\n", ODBC_res, ODBC_stat, ODBC_msg); odbc_disconnect(); 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; + AST_LIST_TRAVERSE(&field_list, f, list) { + // SQLBindParameter just associate a parameter to a memory position + // in this case we must have a different buffer for each parameter + memset(f->buff, 0, sizeof(f->buff)); + pbx_substitute_variables_helper(&dummy, f->value_expr, f->buff, sizeof(f->buff) - 1); + if (f->setnull && ast_strlen_zero(f->buff)) { + f->len = SQL_NULL_DATA; + ODBC_res = SQLBindParameter(ODBC_stmt, idx, SQL_PARAM_INPUT, SQL_C_CHAR, f->type, + 0, 0, f->buff, 0, &f->len); - 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); + } else { + f->len = SQL_NTS; + ODBC_res = SQLBindParameter(ODBC_stmt, idx, SQL_PARAM_INPUT, SQL_C_CHAR, f->type, + 0, 0, f->buff, strlen(f->buff), &f->len); + + } + if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_STMT, ODBC_stmt, 1, ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen); + ast_log(LOG_ERROR, "cdr_odbc: parameter bind error %s = %s, err=%d %s %s\n", + f->name, f->value_expr, ODBC_res, ODBC_stat, ODBC_msg); + connected = 0; + break; + } + idx++; } if (connected) { @@ -201,8 +287,12 @@ } else { if (option_verbose > 10) ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query FAILED Call not logged!\n"); + odbc_disconnect(); } - SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); + if (ODBC_stmt != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); + ODBC_stmt = SQL_NULL_HANDLE; + } ast_mutex_unlock(&odbc_lock); return 0; } @@ -214,11 +304,12 @@ static int odbc_unload_module(void) { + struct field *f; + ast_mutex_lock(&odbc_lock); if (connected) { if (option_verbose > 10) ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Disconnecting from %s\n", dsn); - SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); odbc_disconnect(); } if (dsn) { @@ -242,6 +333,13 @@ free(table); } + if (option_verbose > 10) + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: free fields\n"); + 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; @@ -252,7 +350,12 @@ int res = 0; struct ast_config *cfg; struct ast_variable *var; - char *tmp; + char *tmp, *tp; + int loguniqueid; + int dispositionstring = 0; + int usegmtime = 0; + SQLSMALLINT type; + int setnull; ast_mutex_lock(&odbc_lock); @@ -345,6 +448,62 @@ goto out; } + var = ast_variable_browse(cfg,"fields"); + while (var) { + if (!ast_strlen_zero(var->value)) { + type = SQL_CHAR; + setnull = 0; + tmp = var->value; + tp = strsep(&tmp, ","); + if (strncasecmp(tp, "INTEGER", 7) == 0) { + type = SQL_INTEGER; + } else if (strncasecmp(tp, "TIMESTAMP", 9) == 0) { + type = SQL_TIMESTAMP; + } else if (strncasecmp(tp, "STRING", 6) == 0) { + type = SQL_CHAR; + } else { + ast_log(LOG_ERROR,"cdr_odbc: invalid field type %s\n", tp); + } + + if (strncmp(tmp, "NULL=", 5) == 0) { + tmp += 5; + setnull = 1; + } + odbc_add_field(var->name, tmp, type, setnull); + } + var = var->next; + } + + if (AST_LIST_EMPTY(&field_list)) { + if (usegmtime) { + odbc_add_field("calldate","${CDR(start_gmt)}", SQL_CHAR, 0); + } else { + odbc_add_field("calldate","${CDR(start)}", SQL_CHAR, 0); + } + odbc_add_field("clid", "${CDR(clid)}", SQL_CHAR, 0); + odbc_add_field("src", "${CDR(src)}", SQL_CHAR, 0); + odbc_add_field("dst", "${CDR(dst)}", SQL_CHAR, 0); + odbc_add_field("dcontext", "${CDR(dcontext)}", SQL_CHAR, 0); + odbc_add_field("channel", "${CDR(channel)}", SQL_CHAR, 0); + odbc_add_field("dstchannel", "${CDR(dstchannel)}", SQL_CHAR, 0); + odbc_add_field("lastapp", "${CDR(lastapp)}", SQL_CHAR, 0); + odbc_add_field("lastdata","${CDR(lastdata)}", SQL_CHAR, 0); + odbc_add_field("duration", "${CDR(duration)}", SQL_INTEGER, 0); + odbc_add_field("billsec", "${CDR(billsec)}", SQL_INTEGER, 0); + if (dispositionstring) { + odbc_add_field("disposition", "${CDR(disposition)}", SQL_CHAR, 0); + } else { + odbc_add_field("disposition", "${CDR(disposition_code)}", SQL_CHAR, 0); + } + odbc_add_field("amaflags","${CDR(amaflags_code)}", SQL_INTEGER, 0); + odbc_add_field("accountcode","${CDR(accountcode)}", SQL_CHAR, 0); + + if (loguniqueid) { + odbc_add_field("uniqueid","${CDR(uniqueid)}", SQL_CHAR, 0); + odbc_add_field("userfield", "${CDR(userfield)}", SQL_CHAR, 0); + } + } + ast_config_destroy(cfg); if (option_verbose > 2) { ast_verbose( VERBOSE_PREFIX_3 "cdr_odbc: dsn is %s\n",dsn); @@ -358,6 +517,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); @@ -376,24 +536,24 @@ static int odbc_do_query(void) { - SQLINTEGER ODBC_err; int ODBC_res; + short int ODBC_mlen; + SQLINTEGER ODBC_err; char ODBC_msg[200], ODBC_stat[10]; ODBC_res = SQLExecute(ODBC_stmt); if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_STMT, ODBC_stmt, 1, (unsigned char *)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, (unsigned char *)ODBC_stat, &ODBC_err, (unsigned char *)ODBC_msg, 100, &ODBC_mlen); - SQLFreeHandle(SQL_HANDLE_STMT, ODBC_stmt); + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error in Query %d : %s %s\n", + ODBC_res, ODBC_stat, ODBC_msg); odbc_disconnect(); return -1; } else { if (option_verbose > 10) ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Query Successful!\n"); - connected = 1; } return 0; } @@ -402,6 +562,10 @@ { int ODBC_res; + short int ODBC_mlen; + SQLINTEGER ODBC_err; + char ODBC_msg[200], ODBC_stat[10]; + if (ODBC_env == SQL_NULL_HANDLE || connected == 0) { ODBC_res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ODBC_env); if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { @@ -416,8 +580,7 @@ if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { if (option_verbose > 10) ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SetEnv\n"); - SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env); - connected = 0; + odbc_disconnect(); return -1; } @@ -426,8 +589,7 @@ if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { if (option_verbose > 10) ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error AllocHDB %d\n", ODBC_res); - SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env); - connected = 0; + odbc_disconnect(); return -1; } SQLSetConnectAttr(ODBC_con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)10, 0); @@ -438,8 +600,10 @@ ODBC_res = SQLConnect(ODBC_con, (SQLCHAR*)dsn, SQL_NTS, (SQLCHAR*)username, SQL_NTS, (SQLCHAR*)password, SQL_NTS); if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_DBC, ODBC_con, 1, (unsigned char *)ODBC_stat, &ODBC_err, ODBC_msg, 100, &ODBC_mlen); if (option_verbose > 10) - ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SQLConnect %d\n", ODBC_res); + ast_verbose( VERBOSE_PREFIX_4 "cdr_odbc: Error SQLConnect %d : %s %s \n", + ODBC_res, ODBC_stat, ODBC_msg); SQLFreeHandle(SQL_HANDLE_DBC, ODBC_con); SQLFreeHandle(SQL_HANDLE_ENV, ODBC_env); connected = 0;