Skip to content
Snippets Groups Projects
res_config_odbc.c 36.3 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * Asterisk -- An open source telephony toolkit.
    
     * Copyright (C) 1999 - 2010, Digium, Inc.
    
     * Mark Spencer <markster@digium.com>
    
     * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
    
     *
     * 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.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
     * \brief odbc+odbc plugin for portable configuration engine
    
     * \author Mark Spencer <markster@digium.com>
     * \author Anthony Minessale II <anthmct@yahoo.com>
     *
     * \arg http://www.unixodbc.org
    
    	<depend>res_odbc</depend>
    
    	<support_level>core</support_level>
    
    #include "asterisk/file.h"
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    #include "asterisk/config.h"
    #include "asterisk/module.h"
    #include "asterisk/lock.h"
    #include "asterisk/res_odbc.h"
    #include "asterisk/utils.h"
    
    #include "asterisk/stringfields.h"
    
    /*! Initial SQL query buffer size to allocate. */
    #define SQL_BUF_SIZE	1024
    
    
    struct custom_prepare_struct {
    	const char *sql;
    	const char *extra;
    
    	AST_DECLARE_STRING_FIELDS(
    		AST_STRING_FIELD(encoding)[256];
    	);
    
    	const struct ast_variable *fields;
    
    #define ENCODE_CHUNK(buffer, s) \
    	do { \
    		char *eptr = buffer; \
    		const char *vptr = s; \
    		for (; *vptr && eptr < buffer + sizeof(buffer); vptr++) { \
    			if (strchr("^;", *vptr)) { \
    				/* We use ^XX, instead of %XX because '%' is a special character in SQL */ \
    				snprintf(eptr, buffer + sizeof(buffer) - eptr, "^%02hhX", *vptr); \
    				eptr += 3; \
    			} else { \
    				*eptr++ = *vptr; \
    			} \
    		} \
    		if (eptr < buffer + sizeof(buffer)) { \
    			*eptr = '\0'; \
    		} else { \
    			buffer[sizeof(buffer) - 1] = '\0'; \
    		} \
    	} while(0)
    
    
    static void decode_chunk(char *chunk)
    {
    	for (; *chunk; chunk++) {
    
    		if (*chunk == '^' && strchr("0123456789ABCDEF", chunk[1]) && strchr("0123456789ABCDEF", chunk[2])) {
    
    			sscanf(chunk + 1, "%02hhX", (unsigned char *)chunk);
    
    			memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
    		}
    	}
    }
    
    
    static inline int is_text(const struct odbc_cache_columns *column)
    {
    	return column->type == SQL_CHAR || column->type == SQL_VARCHAR || column->type == SQL_LONGVARCHAR
    		|| column->type == SQL_WCHAR || column->type == SQL_WVARCHAR || column->type == SQL_WLONGVARCHAR;
    }
    
    
    static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
    {
    
    	struct custom_prepare_struct *cps = data;
    
    	const struct ast_variable *field;
    
    	char encodebuf[1024];
    
    	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");
    		return NULL;
    	}
    
    
    	ast_debug(1, "Skip: %llu; SQL: %s\n", cps->skip, cps->sql);
    
    	res = ast_odbc_prepare(obj, stmt, cps->sql);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		if (res == SQL_ERROR) {
    			ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Prepare");
    		}
    
    		ast_log(LOG_WARNING, "SQL Prepare failed! [%s]\n", cps->sql);
    
    		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    		return NULL;
    	}
    
    
    	for (field = cps->fields; field; field = field->next) {
    		const char *newval = field->value;
    
    
    			ast_debug(1, "Skipping field '%s'='%s' (%llo/%llo)\n", field->name, newval, 1ULL << (count - 1), cps->skip);
    
    		ast_debug(1, "Parameter %d ('%s') = '%s'\n", x, field->name, newval);
    
    		if (strchr(newval, ';') || strchr(newval, '^')) {
    
    			ENCODE_CHUNK(encodebuf, newval);
    
    			ast_string_field_set(cps, encoding[x], encodebuf);
    			newval = cps->encoding[x];
    		}
    
    		SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
    	}
    
    
    	if (!ast_strlen_zero(cps->extra)) {
    
    		const char *newval = cps->extra;
    
    		ast_debug(1, "Parameter %d = '%s'\n", x, newval);
    
    		if (strchr(newval, ';') || strchr(newval, '^')) {
    			ENCODE_CHUNK(encodebuf, newval);
    			ast_string_field_set(cps, encoding[x], encodebuf);
    			newval = cps->encoding[x];
    
    		SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*!
     * \brief Excute an SQL query and return ast_variable list
     * \param database
     * \param table
    
    Jason Parker's avatar
    Jason Parker committed
     * \param ap list containing one or more field/operator/value set.
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * Select database and preform query on table, prepare the sql statement
     * Sub-in the values to the prepared statement and execute it. Return results
     * as a ast_variable list.
     *
     * \retval var on success
     * \retval NULL on failure
    
    static struct ast_variable *realtime_odbc(const char *database, const char *table, const struct ast_variable *fields)
    
    	struct odbc_obj *obj;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	struct ast_str *rowdata = ast_str_thread_get(&rowdata_buf, 128);
    
    	char *op;
    
    	const struct ast_variable *field = fields;
    
    	char *stringp;
    	char *chunk;
    	SQLSMALLINT collen;
    	int res;
    	int x;
    	struct ast_variable *var=NULL, *prev=NULL;
    	SQLULEN colsize;
    	SQLSMALLINT colcount=0;
    	SQLSMALLINT datatype;
    	SQLSMALLINT decimaldigits;
    	SQLSMALLINT nullable;
    
    	SQLLEN indicator;
    
    	struct custom_prepare_struct cps = { .fields = fields, };
    
    	struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
    
    	if (!table || !field || !sql || !rowdata) {
    
    	obj = ast_odbc_request_obj2(database, connected_flag);
    
    	if (!obj) {
    		ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", database);
    
    	op = !strchr(field->name, ' ') ? " =" : "";
    
    	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s ?%s", table, field->name, op,
    
    		strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\\\'" : "");
    
    	while ((field = field->next)) {
    		op = !strchr(field->name, ' ') ? " =" : "";
    
    		ast_str_append(&sql, 0, " AND %s%s ?%s", field->name, op,
    
    			strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\\\'" : "");
    
    	if (ast_string_field_init(&cps, 256)) {
    		ast_odbc_release_obj(obj);
    		return NULL;
    	}
    
    	stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
    
    	ast_string_field_free_memory(&cps);
    
    		ast_odbc_release_obj(obj);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL Column Count error! [%s]\n", ast_str_buffer(sql));
    
    		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    
    		ast_odbc_release_obj(obj);
    
    	res = SQLFetch(stmt);
    	if (res == SQL_NO_DATA) {
    		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    
    		ast_odbc_release_obj(obj);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL Fetch error! [%s]\n", ast_str_buffer(sql));
    
    		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    
    		ast_odbc_release_obj(obj);
    
    	for (x = 0; x < colcount; x++) {
    
    		res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
    
    					&datatype, &colsize, &decimaldigits, &nullable);
    
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    			ast_log(LOG_WARNING, "SQL Describe Column error! [%s]\n", ast_str_buffer(sql));
    
    			ast_odbc_release_obj(obj);
    
    
    		res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata), ast_str_size(rowdata), &indicator);
    		ast_str_update(rowdata);
    		if (indicator == SQL_NULL_DATA) {
    			ast_str_reset(rowdata);
    		} else if (!ast_str_strlen(rowdata)) {
    
    			/* Because we encode the empty string for a NULL, we will encode
    			 * actual empty strings as a string containing a single whitespace. */
    
    			ast_str_set(&rowdata, -1, "%s", " ");
    		} else if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) {
    			if (indicator != ast_str_strlen(rowdata)) {
    				/* If the available space was not enough to contain the row data enlarge and read in the rest */
    				ast_str_make_space(&rowdata, indicator + 1);
    				res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata) + ast_str_strlen(rowdata),
    					ast_str_size(rowdata) - ast_str_strlen(rowdata), &indicator);
    				ast_str_update(rowdata);
    			}
    
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    			ast_log(LOG_WARNING, "SQL Get Data error! [%s]\n", ast_str_buffer(sql));
    
    			ast_odbc_release_obj(obj);
    
    		while (stringp) {
    
    			if (!ast_strlen_zero(ast_strip(chunk))) {
    
    				if (strchr(chunk, '^')) {
    					decode_chunk(chunk);
    				}
    
    					prev->next = ast_variable_new(coltitle, chunk, "");
    
    					if (prev->next) {
    
    						prev = prev->next;
    
    					prev = var = ast_variable_new(coltitle, chunk, "");
    
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    	ast_odbc_release_obj(obj);
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*!
     * \brief Excute an Select query and return ast_config list
     * \param database
     * \param table
    
    Jason Parker's avatar
    Jason Parker committed
     * \param ap list containing one or more field/operator/value set.
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * Select database and preform query on table, prepare the sql statement
    
     * Sub-in the values to the prepared statement and execute it.
    
    Russell Bryant's avatar
    Russell Bryant committed
     * Execute this prepared query against several ODBC connected databases.
     * Return results as an ast_config variable.
     *
     * \retval var on success
     * \retval NULL on failure
    
    static struct ast_config *realtime_multi_odbc(const char *database, const char *table, const struct ast_variable *fields)
    
    	struct odbc_obj *obj;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	struct ast_str *rowdata = ast_str_thread_get(&rowdata_buf, 128);
    
    	const struct ast_variable *field = fields;
    
    	struct ast_variable *var=NULL;
    
    	struct ast_config *cfg=NULL;
    	struct ast_category *cat=NULL;
    
    	struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
    
    	SQLULEN colsize;
    	SQLSMALLINT colcount=0;
    	SQLSMALLINT datatype;
    	SQLSMALLINT decimaldigits;
    	SQLSMALLINT nullable;
    
    	SQLLEN indicator;
    
    	struct custom_prepare_struct cps = { .fields = fields, };
    
    	if (!table || !field || !sql || !rowdata) {
    
    	obj = ast_odbc_request_obj2(database, connected_flag);
    
    	initfield = ast_strdupa(field->name);
    
    		*op = '\0';
    
    	op = !strchr(field->name, ' ') ? " =" : "";
    
    	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s ?%s", table, field->name, op,
    
    		strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\\\'" : "");
    
    	while ((field = field->next)) {
    		op = !strchr(field->name, ' ') ? " =" : "";
    
    		ast_str_append(&sql, 0, " AND %s%s ?%s", field->name, op,
    
    			strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\\\'" : "");
    
    	ast_str_append(&sql, 0, " ORDER BY %s", initfield);
    
    	if (ast_string_field_init(&cps, 256)) {
    		ast_odbc_release_obj(obj);
    		return NULL;
    	}
    
    	stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
    
    	ast_string_field_free_memory(&cps);
    
    		ast_odbc_release_obj(obj);
    
    		return NULL;
    	}
    
    	res = SQLNumResultCols(stmt, &colcount);
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL Column Count error! [%s]\n", ast_str_buffer(sql));
    
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    		ast_odbc_release_obj(obj);
    
    	cfg = ast_config_new();
    	if (!cfg) {
    		ast_log(LOG_WARNING, "Out of memory!\n");
    
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    		ast_odbc_release_obj(obj);
    
    	while ((res=SQLFetch(stmt)) != SQL_NO_DATA) {
    
    		var = NULL;
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    			ast_log(LOG_WARNING, "SQL Fetch error! [%s]\n", ast_str_buffer(sql));
    
    		cat = ast_category_new_anonymous();
    
    		if (!cat) {
    			continue;
    		}
    
    			res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
    
    						&datatype, &colsize, &decimaldigits, &nullable);
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    				ast_log(LOG_WARNING, "SQL Describe Column error! [%s]\n", ast_str_buffer(sql));
    
    				ast_category_destroy(cat);
    
    				goto next_sql_fetch;
    
    
    			res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata), ast_str_size(rowdata), &indicator);
    			ast_str_update(rowdata);
    			if (indicator == SQL_NULL_DATA) {
    
    			}
    
    			if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) {
    				if (indicator != ast_str_strlen(rowdata)) {
    					/* If the available space was not enough to contain the row data enlarge and read in the rest */
    					ast_str_make_space(&rowdata, indicator + 1);
    					res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata) + ast_str_strlen(rowdata),
    						ast_str_size(rowdata) - ast_str_strlen(rowdata), &indicator);
    					ast_str_update(rowdata);
    				}
    			}
    
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    				ast_log(LOG_WARNING, "SQL Get Data error! [%s]\n", ast_str_buffer(sql));
    
    				ast_category_destroy(cat);
    
    				goto next_sql_fetch;
    
    			while (stringp) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    				if (!ast_strlen_zero(ast_strip(chunk))) {
    
    					if (strchr(chunk, '^')) {
    						decode_chunk(chunk);
    					}
    
    					if (!strcmp(initfield, coltitle)) {
    
    						ast_category_rename(cat, chunk);
    
    					var = ast_variable_new(coltitle, chunk, "");
    
    					ast_variable_append(cat, var);
    
    		ast_category_append(cfg, cat);
    
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    	ast_odbc_release_obj(obj);
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*!
     * \brief Excute an UPDATE query
     * \param database
     * \param table
     * \param keyfield where clause field
     * \param lookup value of field for where clause
    
    Jason Parker's avatar
    Jason Parker committed
     * \param ap list containing one or more field/value set(s).
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * Update a database table, prepare the sql statement using keyfield and lookup
     * control the number of records to change. All values to be changed are stored in ap list.
     * Sub-in the values to the prepared statement and execute it.
     *
     * \retval number of rows affected
     * \retval -1 on failure
    
    static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields)
    
    	struct odbc_obj *obj;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	const struct ast_variable *field = fields;
    
    	struct custom_prepare_struct cps = { .extra = lookup, .fields = fields, };
    
    	struct odbc_cache_tables *tableptr;
    
    	struct odbc_cache_columns *column = NULL;
    
    	struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
    
    	if (!table || !field || !keyfield || !sql) {
    
    		return -1;
    	}
    
    	tableptr = ast_odbc_find_table(database, table);
    
    	if (!(obj = ast_odbc_request_obj2(database, connected_flag))) {
    
    	if (tableptr && !ast_odbc_find_column(tableptr, keyfield)) {
    		ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'.  Update will fail\n", keyfield, table, database);
    
    	ast_str_set(&sql, 0, "UPDATE %s SET ", table);
    
    	while (field) {
    		if ((tableptr && (column = ast_odbc_find_column(tableptr, field->name))) || count >= 64) {
    			if (paramcount++) {
    
    			}
    			/* NULL test for non-text columns */
    			if (count < 64 && ast_strlen_zero(field->value) && column->nullable && !is_text(column)) {
    
    				ast_str_append(&sql, 0, "%s=NULL", field->name);
    
    				cps.skip |= (1LL << count);
    			} else {
    
    				/* Value is not an empty string, or column is of text type, or we couldn't fit any more into cps.skip (count >= 64 ?!). */
    
    				ast_str_append(&sql, 0, "%s=?", field->name);
    
    		} else { /* the column does not exist in the table */
    			cps.skip |= (1LL << count);
    
    	ast_str_append(&sql, 0, " WHERE %s=?", keyfield);
    
    	if (ast_string_field_init(&cps, 256)) {
    		ast_odbc_release_obj(obj);
    		return -1;
    	}
    
    	stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
    
    	ast_string_field_free_memory(&cps);
    
    		ast_odbc_release_obj(obj);
    
    		return -1;
    	}
    
    	res = SQLRowCount(stmt, &rowcount);
    	SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    
    	ast_odbc_release_obj(obj);
    
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL Row Count error! [%s]\n", ast_str_buffer(sql));
    
    	if (rowcount >= 0) {
    		return (int) rowcount;
    	}
    
    struct update2_prepare_struct {
    	const char *database;
    	const char *table;
    
    	const struct ast_variable *lookup_fields;
    	const struct ast_variable *update_fields;
    
    	struct odbc_cache_tables *tableptr;
    
    };
    
    static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data)
    {
    	int res, x = 1, first = 1;
    	struct update2_prepare_struct *ups = data;
    
    	const struct ast_variable *field;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	SQLHSTMT stmt;
    
    	if (!sql) {
    		return NULL;
    	}
    
    	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");
    		return NULL;
    	}
    
    	ast_str_set(&sql, 0, "UPDATE %s SET ", ups->table);
    
    
    	for (field = ups->update_fields; field; field = field->next) {
    
    		if (ast_odbc_find_column(ups->tableptr, field->name)) {
    
    			ast_str_append(&sql, 0, "%s%s=? ", first ? "" : ", ", field->name);
    			SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(field->name), 0, (void *)field->value, 0, NULL);
    
    			ast_log(LOG_NOTICE, "Not updating column '%s' in '%s@%s' because that column does not exist!\n", field->name, ups->table, ups->database);
    
    	for (field = ups->lookup_fields; field; field = field->next) {
    
    		if (!ast_odbc_find_column(ups->tableptr, field->name)) {
    
    			ast_log(LOG_ERROR, "One or more of the criteria columns '%s' on '%s@%s' for this update does not exist!\n", field->name, ups->table, ups->database);
    
    			SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    			return NULL;
    		}
    
    		ast_str_append(&sql, 0, "%s %s=?", first ? "" : " AND", field->name);
    		SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(field->value), 0, (void *)field->value, 0, NULL);
    
    	res = ast_odbc_prepare(obj, stmt, ast_str_buffer(sql));
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		if (res == SQL_ERROR) {
    			ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Prepare");
    		}
    
    		ast_log(LOG_WARNING, "SQL Prepare failed! [%s]\n", ast_str_buffer(sql));
    
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    		return NULL;
    	}
    
    	return stmt;
    }
    
    /*!
     * \brief Execute an UPDATE query
     * \param database
     * \param table
     * \param ap list containing one or more field/value set(s).
     *
     * Update a database table, preparing the sql statement from a list of
     * key/value pairs specified in ap.  The lookup pairs are specified first
     * and are separated from the update pairs by a sentinel value.
     * Sub-in the values to the prepared statement and execute it.
     *
     * \retval number of rows affected
     * \retval -1 on failure
    */
    
    static int update2_odbc(const char *database, const char *table, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
    
    	struct update2_prepare_struct ups = {
    		.database = database,
    		.table = table,
    		.lookup_fields = lookup_fields,
    		.update_fields = update_fields,
    	};
    
    	struct ast_str *sql;
    	int res;
    	SQLLEN rowcount = 0;
    
    
    	ups.tableptr = ast_odbc_find_table(database, table);
    	if (!ups.tableptr) {
    		ast_log(LOG_ERROR, "Could not retrieve metadata for table '%s@%s'. Update will fail!\n", table, database);
    		return -1;
    	}
    
    
    	if (!(obj = ast_odbc_request_obj(database, 0))) {
    
    		ast_odbc_release_table(ups.tableptr);
    
    		return -1;
    	}
    
    	if (!(stmt = ast_odbc_prepare_and_execute(obj, update2_prepare, &ups))) {
    		ast_odbc_release_obj(obj);
    
    		ast_odbc_release_table(ups.tableptr);
    
    	/* We don't need the table anymore */
    	ast_odbc_release_table(ups.tableptr);
    
    
    	res = SQLRowCount(stmt, &rowcount);
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    	ast_odbc_release_obj(obj);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    		/* Since only a single thread can access this memory, we can retrieve what would otherwise be lost. */
    
    		sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    		ast_assert(sql != NULL);
    		ast_log(LOG_WARNING, "SQL Row Count error! [%s]\n", ast_str_buffer(sql));
    
    /*!
     * \brief Excute an INSERT query
     * \param database
     * \param table
     * \param ap list containing one or more field/value set(s)
    
    Jason Parker's avatar
    Jason Parker committed
     *
    
     * Insert a new record into database table, prepare the sql statement.
     * All values to be changed are stored in ap list.
     * Sub-in the values to the prepared statement and execute it.
     *
     * \retval number of rows affected
     * \retval -1 on failure
    
    static int store_odbc(const char *database, const char *table, const struct ast_variable *fields)
    
    	const struct ast_variable *field = fields;
    
    	struct ast_str *keys;
    	struct ast_str *vals;
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	struct custom_prepare_struct cps = { .fields = fields, };
    
    	struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
    
    	keys = ast_str_create(SQL_BUF_SIZE / 2);
    	vals = ast_str_create(SQL_BUF_SIZE / 4);
    	if (!table || !field || !keys || !vals || !sql) {
    		ast_free(vals);
    		ast_free(keys);
    
    	obj = ast_odbc_request_obj2(database, connected_flag);
    
    	ast_str_set(&keys, 0, "%s", field->name);
    	ast_str_set(&vals, 0, "?");
    
    	while ((field = field->next)) {
    
    		ast_str_append(&keys, 0, ", %s", field->name);
    		ast_str_append(&vals, 0, ", ?");
    
    	ast_str_set(&sql, 0, "INSERT INTO %s (%s) VALUES (%s)",
    		table, ast_str_buffer(keys), ast_str_buffer(vals));
    
    	ast_free(vals);
    	ast_free(keys);
    	cps.sql = ast_str_buffer(sql);
    
    	if (ast_string_field_init(&cps, 256)) {
    		ast_odbc_release_obj(obj);
    		return -1;
    	}
    
    	stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
    
    	ast_string_field_free_memory(&cps);
    
    
    	if (!stmt) {
    		ast_odbc_release_obj(obj);
    		return -1;
    	}
    
    	res = SQLRowCount(stmt, &rowcount);
    	SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    	ast_odbc_release_obj(obj);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL Row Count error! [%s]\n", ast_str_buffer(sql));
    
    		return -1;
    	}
    
    	if (rowcount >= 0)
    		return (int)rowcount;
    
    	return -1;
    }
    
    /*!
     * \brief Excute an DELETE query
     * \param database
     * \param table
     * \param keyfield where clause field
     * \param lookup value of field for where clause
     * \param ap list containing one or more field/value set(s)
    
    Jason Parker's avatar
    Jason Parker committed
     *
     * Delete a row from a database table, prepare the sql statement using keyfield and lookup
    
     * control the number of records to change. Additional params to match rows are stored in ap list.
     * Sub-in the values to the prepared statement and execute it.
     *
     * \retval number of rows affected
     * \retval -1 on failure
    
    static int destroy_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields)
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	const struct ast_variable *field;
    
    	struct custom_prepare_struct cps = { .extra = lookup, .fields = fields, };
    
    	struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
    
    	obj = ast_odbc_request_obj2(database, connected_flag);
    
    	ast_str_set(&sql, 0, "DELETE FROM %s WHERE ", table);
    
    	for (field = fields; field; field = field->next) {
    
    		ast_str_append(&sql, 0, "%s=? AND ", field->name);
    
    	ast_str_append(&sql, 0, "%s=?", keyfield);
    
    	cps.sql = ast_str_buffer(sql);
    
    	if (ast_string_field_init(&cps, 256)) {
    		ast_odbc_release_obj(obj);
    		return -1;
    	}
    
    	stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
    
    	ast_string_field_free_memory(&cps);
    
    
    	if (!stmt) {
    		ast_odbc_release_obj(obj);
    		return -1;
    	}
    
    	res = SQLRowCount(stmt, &rowcount);
    	SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    	ast_odbc_release_obj(obj);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL Row Count error! [%s]\n", ast_str_buffer(sql));
    
    struct config_odbc_obj {
    	char *sql;
    	unsigned long cat_metric;
    	char category[128];
    	char var_name[128];
    
    
    static SQLHSTMT length_determination_odbc_prepare(struct odbc_obj *obj, void *data)
    {
    	struct config_odbc_obj *q = data;
    	SQLHSTMT sth;
    	int res;
    
    	res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    		ast_verb(4, "Failure in AllocStatement %d\n", res);
    		return NULL;
    	}
    
    
    	res = ast_odbc_prepare(obj, sth, q->sql);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    		ast_verb(4, "Error in PREPARE %d\n", res);
    		SQLFreeHandle(SQL_HANDLE_STMT, sth);
    		return NULL;
    	}
    
    	SQLBindCol(sth, 1, SQL_C_ULONG, &q->var_val_size, sizeof(q->var_val_size), &q->err);
    
    	return sth;
    }
    
    
    static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
    {
    	struct config_odbc_obj *q = data;
    	SQLHSTMT sth;
    	int res;
    
    	res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_verb(4, "Failure in AllocStatement %d\n", res);
    
    	res = ast_odbc_prepare(obj, sth, q->sql);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_verb(4, "Error in PREPARE %d\n", res);
    
    		SQLFreeHandle(SQL_HANDLE_STMT, sth);
    		return NULL;
    	}
    
    
    	SQLBindCol(sth, 1, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err);
    	SQLBindCol(sth, 2, SQL_C_CHAR, q->category, sizeof(q->category), &q->err);
    	SQLBindCol(sth, 3, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err);
    
    	SQLBindCol(sth, 4, SQL_C_CHAR, q->var_val, q->var_val_size, &q->err);
    
    static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl, const char *who_asked)
    
    	struct ast_variable *new_v;
    	struct ast_category *cur_cat;
    
    	struct odbc_obj *obj;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, SQL_BUF_SIZE);
    
    	unsigned int last_cat_metric = 0;
    
    	SQLSMALLINT rowcount = 0;
    
    	char last[128] = "";
    
    	struct config_odbc_obj q;
    
    	struct ast_flags loader_flags = { 0 };
    
    	struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
    
    
    	memset(&q, 0, sizeof(q));
    
    	if (!file || !strcmp (file, "res_config_odbc.conf") || !sql) {
    
    		return NULL;		/* cant configure myself with myself ! */
    
    	obj = ast_odbc_request_obj2(database, connected_flag);
    
    	ast_str_set(&sql, 0, "SELECT MAX(LENGTH(var_val)) FROM %s WHERE filename='%s'",
    		table, file);
    	q.sql = ast_str_buffer(sql);
    
    
    	stmt = ast_odbc_prepare_and_execute(obj, length_determination_odbc_prepare, &q);
    	if (!stmt) {
    
    		ast_log(LOG_WARNING, "SQL select error! [%s]\n", ast_str_buffer(sql));
    
    		ast_odbc_release_obj(obj);
    		return NULL;
    	}
    
    	res = SQLNumResultCols(stmt, &rowcount);
    
    	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    		ast_log(LOG_WARNING, "SQL NumResultCols error! [%s]\n", ast_str_buffer(sql));
    
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    		ast_odbc_release_obj(obj);
    		return NULL;
    	}
    
    	if (!rowcount) {
    		ast_log(LOG_NOTICE, "found nothing\n");
    		ast_odbc_release_obj(obj);
    		return cfg;
    	}
    
    	/* There will be only one result for this, the maximum length of a variable value */
    	if (SQLFetch(stmt) == SQL_NO_DATA) {
    		ast_log(LOG_NOTICE, "Failed to determine maximum length of a configuration value\n");
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    		ast_odbc_release_obj(obj);
    		return NULL;
    	}
    
    	/* Reset stuff to a fresh state for the actual query which will retrieve all configuration */
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    
    	ast_str_set(&sql, 0, "SELECT cat_metric, category, var_name, var_val FROM %s ", table);
    	ast_str_append(&sql, 0, "WHERE filename='%s' AND commented=0 ", file);
    	ast_str_append(&sql, 0, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
    	q.sql = ast_str_buffer(sql);
    
    
    	q.var_val_size += 1;
    	q.var_val = ast_malloc(q.var_val_size);
    	if (!q.var_val) {
    		ast_log(LOG_WARNING, "Could not create buffer for reading in configuration values for '%s'\n", file);
    		ast_odbc_release_obj(obj);
    		return NULL;
    	}
    
    	stmt = ast_odbc_prepare_and_execute(obj, config_odbc_prepare, &q);
    
    		ast_log(LOG_WARNING, "SQL select error! [%s]\n", ast_str_buffer(sql));
    
    		ast_odbc_release_obj(obj);
    
    	res = SQLNumResultCols(stmt, &rowcount);