Index: funcs/func_odbc.c =================================================================== --- funcs/func_odbc.c (revision 131073) +++ funcs/func_odbc.c (working copy) @@ -57,6 +57,7 @@ char writehandle[5][30]; char sql_read[2048]; char sql_write[2048]; + char sql_insert[2048]; unsigned int flags; int rowlimit; struct ast_custom_function *acf; @@ -106,15 +107,32 @@ res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + ast_log(LOG_WARNING, "SQL Alloc Handle failed (%d)!\n", res); return NULL; } res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - ast_log(LOG_WARNING, "SQL Exec Direct failed![%s]\n", sql); + if (res == SQL_ERROR) { + int i; + SQLINTEGER nativeerror=0, numfields=0; + SQLSMALLINT diagbytes=0; + unsigned char state[10], diagnostic[256]; + + SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes); + for (i = 0; i < numfields; i++) { + SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes); + ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes); + if (i > 10) { + ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields); + break; + } + } + } + + ast_log(LOG_WARNING, "SQL Exec Direct failed (%d)![%s]\n", res, sql); SQLCloseCursor(stmt); - SQLFreeHandle (SQL_HANDLE_STMT, stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); return NULL; } @@ -128,7 +146,8 @@ { struct odbc_obj *obj = NULL; struct acf_odbc_query *query; - char *t, buf[2048], varname[15]; + char *t, buf[2048], insertbuf[2048], varname[15]; + const char *status = "FAILURE"; int i, dsn, bogus_chan = 0; AST_DECLARE_APP_ARGS(values, AST_APP_ARG(field)[100]; @@ -149,6 +168,7 @@ if (!query) { ast_log(LOG_ERROR, "No such function '%s'\n", cmd); AST_LIST_UNLOCK(&queries); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "FAILURE"); return -1; } @@ -170,6 +190,7 @@ ast_autoservice_stop(chan); if (bogus_chan) ast_channel_free(chan); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "FAILURE"); return -1; } @@ -190,6 +211,11 @@ pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : ""); pbx_substitute_variables_helper(chan, query->sql_write, buf, sizeof(buf) - 1); + if (!ast_strlen_zero(query->sql_insert)) { + pbx_substitute_variables_helper(chan, query->sql_insert, insertbuf, sizeof(insertbuf) - 1); + } else { + insertbuf[0] = '\0'; + } /* Restore prior values */ for (i = 0; i < args.argc; i++) { @@ -211,13 +237,30 @@ if (obj) stmt = ast_odbc_direct_execute(obj, generic_execute, buf); } - if (stmt) + if (stmt) { + /* Rows affected */ + status = "SUCCESS"; + SQLRowCount(stmt, &rows); break; + } } - if (stmt) { - /* Rows affected */ - SQLRowCount(stmt, &rows); + if (stmt && rows == 0 && !ast_strlen_zero(insertbuf)) { + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + for (dsn = 0; dsn < 5; dsn++) { + if (!ast_strlen_zero(query->writehandle[dsn])) { + obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + if (obj) { + stmt = ast_odbc_direct_execute(obj, generic_execute, insertbuf); + } + } + if (stmt) { + status = "FAILOVER"; + SQLRowCount(stmt, &rows); + break; + } + } } /* Output the affected rows, for all cases. In the event of failure, we @@ -226,6 +269,7 @@ * not change. */ snprintf(varname, sizeof(varname), "%d", (int)rows); pbx_builtin_setvar_helper(chan, "ODBCROWS", varname); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status); if (stmt) { SQLCloseCursor(stmt); @@ -247,6 +291,7 @@ struct odbc_obj *obj = NULL; struct acf_odbc_query *query; char sql[2048], varname[15], colnames[2048] = "", rowcount[12] = "-1"; + const char *status = "FAILURE"; int res, x, y, buflen = 0, escapecommas, rowlimit = 1, dsn, bogus_chan = 0; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(field)[100]; @@ -321,6 +366,7 @@ if (obj) ast_odbc_release_obj(obj); pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "FAILURE"); if (chan) ast_autoservice_stop(chan); if (bogus_chan) @@ -335,6 +381,7 @@ SQLFreeHandle (SQL_HANDLE_STMT, stmt); ast_odbc_release_obj(obj); pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "SUCCESS"); if (chan) ast_autoservice_stop(chan); if (bogus_chan) @@ -356,6 +403,7 @@ SQLFreeHandle(SQL_HANDLE_STMT, stmt); ast_odbc_release_obj(obj); pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "NOROWS"); if (chan) ast_autoservice_stop(chan); if (bogus_chan) @@ -363,6 +411,8 @@ return res1; } + status = "SUCCESS"; + for (y = 0; y < rowlimit; y++) { *buf = '\0'; for (x = 0; x < colcount; x++) { @@ -407,6 +457,7 @@ SQLFreeHandle(SQL_HANDLE_STMT, stmt); ast_odbc_release_obj(obj); pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); if (chan) ast_autoservice_stop(chan); if (bogus_chan) @@ -455,6 +506,7 @@ row = ast_calloc(1, sizeof(*row) + buflen); if (!row) { ast_log(LOG_ERROR, "Unable to allocate space for more rows in this resultset.\n"); + status = "MEMERROR"; goto end_acf_read; } strcpy((char *)row + sizeof(*row), buf); @@ -463,8 +515,10 @@ /* Get next row */ res = SQLFetch(stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - if (res != SQL_NO_DATA) + if (res != SQL_NO_DATA) { ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql); + status = "FETCHERROR"; + } y++; break; } @@ -474,6 +528,7 @@ end_acf_read: snprintf(rowcount, sizeof(rowcount), "%d", y); pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); + pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status); pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames); if (resultset) { int uid; @@ -483,6 +538,7 @@ odbc_store = ast_channel_datastore_alloc(&odbc_info, buf); if (!odbc_store) { ast_log(LOG_ERROR, "Rows retrieved, but unable to store it in the channel. Results fail.\n"); + pbx_builtin_setvar_helper(chan, "MEMERROR", status); odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); @@ -659,6 +715,10 @@ return EINVAL; } + if ((tmp = ast_variable_retrieve(cfg, catg, "insertsql"))) { + ast_copy_string((*query)->sql_insert, tmp, sizeof((*query)->sql_insert)); + } + /* Allow escaping of embedded commas in fields to be turned off */ ast_set_flag((*query), OPT_ESCAPECOMMAS); if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) { @@ -710,9 +770,16 @@ "substitution of the arguments into the query as specified by ${ARG1},\n" "${ARG2}, ... ${ARGn}. When setting the function, the values are provided\n" "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n" - "\nRead:\n%s\n\nWrite:\n%s\n", + "%s" + "\nRead:\n%s\n\nWrite:\n%s\n%s%s%s", + ast_strlen_zero((*query)->sql_insert) ? "" : + "If the write query affects no rows, the insert query will be\n" + "performed.\n", (*query)->sql_read, - (*query)->sql_write); + (*query)->sql_write, + ast_strlen_zero((*query)->sql_insert) ? "" : "Insert:\n", + ast_strlen_zero((*query)->sql_insert) ? "" : (*query)->sql_insert, + ast_strlen_zero((*query)->sql_insert) ? "" : "\n"); } else if (!ast_strlen_zero((*query)->sql_read)) { asprintf((char **)&((*query)->acf->desc), "Runs the following query, as defined in func_odbc.conf, performing\n" @@ -725,14 +792,20 @@ "substitution of the arguments into the query as specified by ${ARG1},\n" "${ARG2}, ... ${ARGn}. The values are provided either in whole as\n" "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n" - "This function may only be set.\nSQL:\n%s\n", - (*query)->sql_write); + "This function may only be set.\n%s\nSQL:\n%s\n%s%s%s", + ast_strlen_zero((*query)->sql_insert) ? "" : + "If the write query affects no rows, the insert query will be\n" + "performed.\n", + (*query)->sql_write, + ast_strlen_zero((*query)->sql_insert) ? "" : "Insert:\n", + ast_strlen_zero((*query)->sql_insert) ? "" : (*query)->sql_insert, + ast_strlen_zero((*query)->sql_insert) ? "" : "\n"); } else { ast_free((char *)(*query)->acf->syntax); ast_free((char *)(*query)->acf->name); ast_free((*query)->acf); ast_free(*query); - ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute. Ignoring.\n", catg); + ast_log(LOG_WARNING, "Section '%s' was found, but there was no SQL to execute. Ignoring.\n", catg); return EINVAL; } Index: configs/func_odbc.conf.sample =================================================================== --- configs/func_odbc.conf.sample (revision 131073) +++ configs/func_odbc.conf.sample (working copy) @@ -31,6 +31,8 @@ ; readhandle. "dsn" is a synonym for "writehandle". ; readsql The statement to execute when reading from the function class. ; writesql The statement to execute when writing to the function class. +; insertsql The statement to execute when writing to the function class +; succeeds, but initially indicates that 0 rows were affected. ; prefix Normally, all function classes are prefixed with "ODBC" to keep ; them uniquely named. You may choose to change this prefix, which ; may be useful to segregate a collection of certain function