dect
/
asterisk
Archived
13
0
Fork 0

Merge realtime_update2 branch, which adds a new realtime API call named

'update2', which permits updates which match across multiple columns, instead
of requiring all tables to have a single unique identifier.  All of the other
API calls with the exception of 'update' already had the ability to match on
multiple fields, so it was a missing and very desireable feature that an API
call implementing an update should have this, too.

This does not change any outward performance of Asterisk, but it should make
life easier for application developers who use the RealTime framework.


git-svn-id: http://svn.digium.com/svn/asterisk/trunk@148570 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
tilghman 2008-10-14 00:08:52 +00:00
parent 1078121ae0
commit 4d59ca2641
10 changed files with 942 additions and 173 deletions

View File

@ -943,7 +943,7 @@ static int change_password_realtime(struct ast_vm_user *vmu, const char *passwor
if (strlen(password) > 10) {
ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
}
res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, SENTINEL);
res = ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL);
if (res > 0) {
ast_copy_string(vmu->password, password, sizeof(vmu->password));
res = 0;
@ -4638,8 +4638,6 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
int ausemacro = 0;
int ousemacro = 0;
int ouseexten = 0;
int rtmsgid = 0;
char tmpid[16];
char tmpdur[16];
char priority[16];
char origtime[16];
@ -4932,7 +4930,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
snprintf(priority, sizeof(priority), "%d", chan->priority);
snprintf(origtime, sizeof(origtime), "%ld", (long)time(NULL));
get_date(date, sizeof(date));
rtmsgid = ast_store_realtime("voicemail_data", "origmailbox", ext, "context", chan->context, "macrocontext", chan->macrocontext, "exten", chan->exten, "priority", priority, "callerchan", chan->name, "callerid", ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"), "origdate", date, "origtime", origtime, "category", S_OR(category,""), SENTINEL);
ast_store_realtime("voicemail_data", "origmailbox", ext, "context", chan->context, "macrocontext", chan->macrocontext, "exten", chan->exten, "priority", priority, "callerchan", chan->name, "callerid", ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"), "origdate", date, "origtime", origtime, "category", S_OR(category,""), "filename", tmptxtfile, SENTINEL);
}
/* Store information */
@ -4976,8 +4974,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
ast_filedelete(tmptxtfile, NULL);
unlink(tmptxtfile);
if (ast_check_realtime("voicemail_data")) {
snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
ast_destroy_realtime("voicemail_data", "id", tmpid, SENTINEL);
ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
}
} else {
fprintf(txt, "duration=%d\n", duration);
@ -4992,8 +4989,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
unlink(tmptxtfile);
ast_unlock_path(dir);
if (ast_check_realtime("voicemail_data")) {
snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
ast_destroy_realtime("voicemail_data", "id", tmpid, SENTINEL);
ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
}
} else {
#ifndef IMAP_STORAGE
@ -5019,9 +5015,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
ast_unlock_path(dir);
if (ast_check_realtime("voicemail_data")) {
snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
snprintf(tmpdur, sizeof(tmpdur), "%d", duration);
ast_update_realtime("voicemail_data", "id", tmpid, "filename", fn, "duration", tmpdur, SENTINEL);
ast_update_realtime("voicemail_data", "filename", tmptxtfile, "filename", fn, "duration", tmpdur, SENTINEL);
}
/* We must store the file first, before copying the message, because
* ODBC storage does the entire copy with SQL.

View File

@ -91,6 +91,7 @@ typedef struct ast_config *config_load_func(const char *database, const char *ta
typedef struct ast_variable *realtime_var_get(const char *database, const char *table, va_list ap);
typedef struct ast_config *realtime_multi_get(const char *database, const char *table, va_list ap);
typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
typedef int realtime_update2(const char *database, const char *table, va_list ap);
typedef int realtime_store(const char *database, const char *table, va_list ap);
typedef int realtime_destroy(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
typedef int realtime_require(const char *database, const char *table, va_list ap);
@ -103,6 +104,7 @@ struct ast_config_engine {
realtime_var_get *realtime_func;
realtime_multi_get *realtime_multi_func;
realtime_update *update_func;
realtime_update2 *update2_func;
realtime_store *store_func;
realtime_destroy *destroy_func;
realtime_require *require_func;
@ -208,6 +210,9 @@ int ast_category_exist(const struct ast_config *config, const char *category_nam
* entity in realtime and return a variable list of its parameters. Note
* that unlike the variables in ast_config, the resulting list of variables
* MUST be freed with ast_variables_destroy() as there is no container.
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
struct ast_variable *ast_load_realtime(const char *family, ...) attribute_sentinel;
struct ast_variable *ast_load_realtime_all(const char *family, ...) attribute_sentinel;
@ -243,6 +248,9 @@ int ast_unload_realtime(const char *family);
* a timeout value may reasonably be specified as an INTEGER2, with size 5.
* Even though values above 32767 seconds are possible, they are unlikely
* to be useful, and we should not complain about that size).
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
int ast_realtime_require_field(const char *family, ...) attribute_sentinel;
@ -254,6 +262,9 @@ int ast_realtime_require_field(const char *family, ...) attribute_sentinel;
* the ast_load_realtime, this function can return more than one entry and
* is thus stored inside a taditional ast_config structure rather than
* just returning a linked list of variables.
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
struct ast_config *ast_load_realtime_multientry(const char *family, ...) attribute_sentinel;
@ -264,14 +275,31 @@ struct ast_config *ast_load_realtime_multientry(const char *family, ...) attribu
* \param lookup which value to look for in the key field to match the entry.
* This function is used to update a parameter in realtime configuration space.
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...) attribute_sentinel;
/*!
* \brief Update realtime configuration
* \param family which family/config to be updated
* This function is used to update a parameter in realtime configuration space.
* It includes the ability to lookup a row based upon multiple key criteria.
* As a result, this function includes two sentinel values, one to terminate
* lookup values and the other to terminate the listing of fields to update.
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
int ast_update2_realtime(const char *family, ...) attribute_sentinel;
/*!
* \brief Create realtime configuration
* \param family which family/config to be created
* This function is used to create a parameter in realtime configuration space.
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
int ast_store_realtime(const char *family, ...) attribute_sentinel;
@ -283,6 +311,8 @@ int ast_store_realtime(const char *family, ...) attribute_sentinel;
* This function is used to destroy an entry in realtime configuration space.
* Additional params are used as keys.
*
* Note that you should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*/
int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) attribute_sentinel;

View File

@ -421,8 +421,8 @@ int ast_str_make_space(struct ast_str **buf, size_t new_len),
_DB1(__ast_threadstorage_object_replace(old_buf, *buf, new_len + sizeof(struct ast_str));)
}
(*buf)->len = new_len;
return 0;
(*buf)->len = new_len;
return 0;
}
)

View File

@ -2069,8 +2069,8 @@ struct ast_config *ast_config_load2(const char *filename, const char *who_asked,
static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
{
struct ast_config_engine *eng;
char db[256]="";
char table[256]="";
char db[256];
char table[256];
struct ast_variable *res=NULL;
eng = find_engine(family, db, sizeof(db), table, sizeof(table));
@ -2141,8 +2141,8 @@ int ast_realtime_enabled()
int ast_realtime_require_field(const char *family, ...)
{
struct ast_config_engine *eng;
char db[256] = "";
char table[256] = "";
char db[256];
char table[256];
va_list ap;
int res = -1;
@ -2159,8 +2159,8 @@ int ast_realtime_require_field(const char *family, ...)
int ast_unload_realtime(const char *family)
{
struct ast_config_engine *eng;
char db[256] = "";
char table[256] = "";
char db[256];
char table[256];
int res = -1;
eng = find_engine(family, db, sizeof(db), table, sizeof(table));
@ -2173,9 +2173,9 @@ int ast_unload_realtime(const char *family)
struct ast_config *ast_load_realtime_multientry(const char *family, ...)
{
struct ast_config_engine *eng;
char db[256]="";
char table[256]="";
struct ast_config *res=NULL;
char db[256];
char table[256];
struct ast_config *res = NULL;
va_list ap;
va_start(ap, family);
@ -2191,8 +2191,8 @@ int ast_update_realtime(const char *family, const char *keyfield, const char *lo
{
struct ast_config_engine *eng;
int res = -1;
char db[256]="";
char table[256]="";
char db[256];
char table[256];
va_list ap;
va_start(ap, lookup);
@ -2204,12 +2204,29 @@ int ast_update_realtime(const char *family, const char *keyfield, const char *lo
return res;
}
int ast_update2_realtime(const char *family, ...)
{
struct ast_config_engine *eng;
int res = -1;
char db[256];
char table[256];
va_list ap;
va_start(ap, family);
eng = find_engine(family, db, sizeof(db), table, sizeof(table));
if (eng && eng->update2_func)
res = eng->update2_func(db, table, ap);
va_end(ap);
return res;
}
int ast_store_realtime(const char *family, ...)
{
struct ast_config_engine *eng;
int res = -1;
char db[256]="";
char table[256]="";
char db[256];
char table[256];
va_list ap;
va_start(ap, family);
@ -2225,8 +2242,8 @@ int ast_destroy_realtime(const char *family, const char *keyfield, const char *l
{
struct ast_config_engine *eng;
int res = -1;
char db[256]="";
char table[256]="";
char db[256];
char table[256];
va_list ap;
va_start(ap, lookup);

View File

@ -275,6 +275,69 @@ static int update_curl(const char *url, const char *unused, const char *keyfield
return -1;
}
static int update2_curl(const char *url, const char *unused, va_list ap)
{
struct ast_str *query;
char buf1[200], buf2[200];
const char *newparam, *newval;
char *stringp;
int rowcount = -1, lookup = 1, first = 1;
const int EncodeSpecialChars = 1, bufsize = 100;
char *buffer;
if (!ast_custom_function_find("CURL")) {
ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
return -1;
}
if (!(query = ast_str_create(1000)))
return -1;
if (!(buffer = ast_malloc(bufsize))) {
ast_free(query);
return -1;
}
ast_str_set(&query, 0, "${CURL(%s/update?", url);
for (;;) {
if ((newparam = va_arg(ap, const char *)) == SENTINEL) {
if (lookup) {
lookup = 0;
ast_str_append(&query, 0, ",");
/* Back to the first parameter; we don't need a starting '&' */
first = 1;
continue;
} else {
break;
}
}
newval = va_arg(ap, const char *);
ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
ast_str_append(&query, 0, "%s%s=%s", first ? "" : "&", buf1, buf2);
}
va_end(ap);
ast_str_append(&query, 0, ")}");
/* TODO: Make proxies work */
pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
/* Line oriented output */
stringp = buffer;
while (*stringp <= ' ')
stringp++;
sscanf(stringp, "%d", &rowcount);
ast_free(buffer);
ast_free(query);
if (rowcount >= 0)
return (int)rowcount;
return -1;
}
/*!
* \brief Execute an INSERT query
* \param url
@ -535,6 +598,7 @@ static struct ast_config_engine curl_engine = {
.store_func = store_curl,
.destroy_func = destroy_curl,
.update_func = update_curl,
.update2_func = update2_curl,
.require_func = require_curl,
};

View File

@ -89,6 +89,7 @@ struct ldap_table_config {
struct ast_variable *attributes; /*!< attribute names conversion */
struct ast_variable *delimiters; /*!< the current delimiter is semicolon, so we are not using this variable */
AST_LIST_ENTRY(ldap_table_config) entry;
/* TODO: Make proxies work */
};
/*! \brief Should be locked before using it */
@ -1305,12 +1306,199 @@ static int update_ldap(const char *basedn, const char *table_name, const char *a
return num_entries;
}
static int update2_ldap(const char *basedn, const char *table_name, va_list ap)
{
int error = 0;
LDAPMessage *ldap_entry = NULL;
LDAPMod **ldap_mods;
const char *newparam = NULL;
const char *newval = NULL;
char *dn;
int num_entries = 0;
int i = 0;
int mods_size = 0;
int mod_exists = 0;
struct ldap_table_config *table_config = NULL;
char *clean_basedn = NULL;
struct ast_str *filter = NULL;
int tries = 0;
int result = 0;
LDAPMessage *ldap_result_msg = NULL;
if (!table_name) {
ast_log(LOG_WARNING, "No table_name specified.\n");
return -1;
}
if (!(filter = ast_str_create(80)))
return -1;
ast_mutex_lock(&ldap_lock);
/* We now have our complete statement; Lets connect to the server and execute it. */
if (!ldap_reconnect()) {
ast_mutex_unlock(&ldap_lock);
ast_free(filter);
return -1;
}
table_config = table_config_for_table_name(table_name);
if (!table_config) {
ast_log(LOG_WARNING, "No table named '%s'.\n", table_name);
ast_mutex_unlock(&ldap_lock);
ast_free(filter);
return -1;
}
clean_basedn = cleaned_basedn(NULL, basedn);
/* Create the filter with the table additional filter and the parameter/value pairs we were given */
ast_str_append(&filter, 0, "(&");
if (table_config && table_config->additional_filter) {
ast_str_append(&filter, 0, "%s", table_config->additional_filter);
}
if (table_config != base_table_config && base_table_config
&& base_table_config->additional_filter) {
ast_str_append(&filter, 0, "%s", base_table_config->additional_filter);
}
/* Get multiple lookup keyfields and values */
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
append_var_and_value_to_filter(&filter, table_config, newparam, newval);
}
ast_str_append(&filter, 0, ")");
/* Create the modification array with the parameter/value pairs we were given,
* if there are several parameters with the same name, we collect them into
* one parameter/value pair and delimit them with a semicolon */
newparam = va_arg(ap, const char *);
newparam = convert_attribute_name_to_ldap(table_config, newparam);
newval = va_arg(ap, const char *);
if (!newparam || !newval) {
ast_log(LOG_WARNING,
"LINE(%d): need at least one parameter to modify.\n", __LINE__);
ast_free(filter);
ast_free(clean_basedn);
return -1;
}
mods_size = 2; /* one for the first param/value pair and one for the the terminating NULL */
ldap_mods = ast_calloc(sizeof(LDAPMod *), mods_size);
ldap_mods[0] = ast_calloc(1, sizeof(LDAPMod));
ldap_mods[0]->mod_op = LDAP_MOD_REPLACE;
ldap_mods[0]->mod_type = ast_calloc(sizeof(char), strlen(newparam) + 1);
strcpy(ldap_mods[0]->mod_type, newparam);
ldap_mods[0]->mod_values = ast_calloc(sizeof(char), 2);
ldap_mods[0]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1);
strcpy(ldap_mods[0]->mod_values[0], newval);
while ((newparam = va_arg(ap, const char *))) {
newparam = convert_attribute_name_to_ldap(table_config, newparam);
newval = va_arg(ap, const char *);
mod_exists = 0;
for (i = 0; i < mods_size - 1; i++) {
if (ldap_mods[i]&& !strcmp(ldap_mods[i]->mod_type, newparam)) {
/* We have the parameter allready, adding the value as a semicolon delimited value */
ldap_mods[i]->mod_values[0] = ast_realloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(newval) + 2));
strcat(ldap_mods[i]->mod_values[0], ";");
strcat(ldap_mods[i]->mod_values[0], newval);
mod_exists = 1;
break;
}
}
/* create new mod */
if (!mod_exists) {
mods_size++;
ldap_mods = ast_realloc(ldap_mods, sizeof(LDAPMod *) * mods_size);
ldap_mods[mods_size - 1] = NULL;
ldap_mods[mods_size - 2] = ast_calloc(1, sizeof(LDAPMod));
ldap_mods[mods_size - 2]->mod_op = LDAP_MOD_REPLACE;
ldap_mods[mods_size - 2]->mod_type = ast_calloc(sizeof(char), strlen(newparam) + 1);
strcpy(ldap_mods[mods_size - 2]->mod_type, newparam);
ldap_mods[mods_size - 2]->mod_values = ast_calloc(sizeof(char *), 2);
ldap_mods[mods_size - 2]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1);
strcpy(ldap_mods[mods_size - 2]->mod_values[0], newval);
}
}
/* freeing ldap_mods further down */
do {
/* freeing ldap_result further down */
result = ldap_search_ext_s(ldapConn, clean_basedn,
LDAP_SCOPE_SUBTREE, filter->str, NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT,
&ldap_result_msg);
if (result != LDAP_SUCCESS && is_ldap_connect_error(result)) {
ast_log(LOG_WARNING, "Failed to query database. Try %d/3\n",
tries + 1);
tries++;
if (tries < 3) {
usleep(500000L * tries);
if (ldapConn) {
ldap_unbind_ext_s(ldapConn, NULL, NULL);
ldapConn = NULL;
}
if (!ldap_reconnect())
break;
}
}
} while (result != LDAP_SUCCESS && tries < 3 && is_ldap_connect_error(result));
if (result != LDAP_SUCCESS) {
ast_log(LOG_WARNING, "Failed to query directory. Check debug for more info.\n");
ast_log(LOG_WARNING, "Query: %s\n", filter->str);
ast_log(LOG_WARNING, "Query Failed because: %s\n",
ldap_err2string(result));
ast_mutex_unlock(&ldap_lock);
if (filter)
free(filter);
if (clean_basedn)
free(clean_basedn);
ldap_msgfree(ldap_result_msg);
ldap_mods_free(ldap_mods, 0);
return -1;
}
/* Ready to update */
if ((num_entries = ldap_count_entries(ldapConn, ldap_result_msg)) > 0) {
for (i = 0; option_debug > 2 && i < mods_size - 1; i++)
ast_debug(3, "LINE(%d) %s=%s \n", __LINE__, ldap_mods[i]->mod_type, ldap_mods[i]->mod_values[0]);
ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
for (i = 0; ldap_entry; i++) {
dn = ldap_get_dn(ldapConn, ldap_entry);
if ((error = ldap_modify_ext_s(ldapConn, dn, ldap_mods, NULL, NULL)) != LDAP_SUCCESS)
ast_log(LOG_ERROR, "Couldn't modify dn:%s because %s", dn, ldap_err2string(error));
ldap_entry = ldap_next_entry(ldapConn, ldap_entry);
}
}
ast_mutex_unlock(&ldap_lock);
if (filter)
free(filter);
if (clean_basedn)
free(clean_basedn);
ldap_msgfree(ldap_result_msg);
ldap_mods_free(ldap_mods, 0);
return num_entries;
}
static struct ast_config_engine ldap_engine = {
.name = "ldap",
.load_func = config_ldap,
.realtime_func = realtime_ldap,
.realtime_multi_func = realtime_multi_ldap,
.update_func = update_ldap
.update_func = update_ldap,
.update2_func = update2_ldap,
};
static int load_module(void)

View File

@ -49,6 +49,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/res_odbc.h"
#include "asterisk/utils.h"
AST_THREADSTORAGE(sql_buf);
struct custom_prepare_struct {
const char *sql;
const char *extra;
@ -474,6 +476,147 @@ static int update_odbc(const char *database, const char *table, const char *keyf
return -1;
}
struct update2_prepare_struct {
const char *database;
const char *table;
va_list ap;
};
static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data)
{
int res, x = 1, first = 1;
struct update2_prepare_struct *ups = data;
const char *newparam, *newval;
struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
SQLHSTMT stmt;
va_list ap;
struct odbc_cache_tables *tableptr = ast_odbc_find_table(ups->database, ups->table);
struct odbc_cache_columns *column;
if (!sql) {
if (tableptr) {
ast_odbc_release_table(tableptr);
}
return NULL;
}
if (!tableptr) {
ast_log(LOG_ERROR, "Could not retrieve metadata for table '%s@%s'. Update will fail!\n", ups->table, ups->database);
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");
ast_odbc_release_table(tableptr);
return NULL;
}
ast_str_set(&sql, 0, "UPDATE %s SET ", ups->table);
/* Start by finding the second set of parameters */
va_copy(ap, ups->ap);
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
}
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
if ((column = ast_odbc_find_column(tableptr, newparam))) {
ast_str_append(&sql, 0, "%s%s=? ", first ? "" : ", ", newparam);
SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
first = 0;
} else {
ast_log(LOG_NOTICE, "Not updating column '%s' in '%s@%s' because that column does not exist!\n", newparam, ups->table, ups->database);
}
}
va_end(ap);
/* Restart search, because we need to add the search parameters */
va_copy(ap, ups->ap);
ast_str_append(&sql, 0, "WHERE");
first = 1;
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
if (!(column = ast_odbc_find_column(tableptr, newparam))) {
ast_log(LOG_ERROR, "One or more of the criteria columns '%s' on '%s@%s' for this update does not exist!\n", newparam, ups->table, ups->database);
ast_odbc_release_table(tableptr);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
}
ast_str_append(&sql, 0, "%s %s=?", first ? "" : " AND", newparam);
SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
first = 0;
}
va_end(ap);
/* Done with the table metadata */
ast_odbc_release_table(tableptr);
res = SQLPrepare(stmt, (unsigned char *)sql->str, SQL_NTS);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql->str);
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, va_list ap)
{
struct odbc_obj *obj;
SQLHSTMT stmt;
struct update2_prepare_struct ups = { .database = database, .table = table, };
struct ast_str *sql;
int res;
SQLLEN rowcount = 0;
va_copy(ups.ap, ap);
if (!(obj = ast_odbc_request_obj(database, 0))) {
return -1;
}
if (!(stmt = ast_odbc_prepare_and_execute(obj, update2_prepare, &ups))) {
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)) {
/* Since only a single thread can access this memory, we can retrieve what would otherwise be lost. */
sql = ast_str_thread_get(&sql_buf, 16);
ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n", sql->str);
return -1;
}
if (rowcount >= 0) {
return (int)rowcount;
}
return -1;
}
/*!
* \brief Excute an INSERT query
* \param database
@ -899,6 +1042,7 @@ static struct ast_config_engine odbc_engine = {
.store_func = store_odbc,
.destroy_func = destroy_odbc,
.update_func = update_odbc,
.update2_func = update2_odbc,
.require_func = require_odbc,
.unload_func = ast_odbc_clear_cache,
};

View File

@ -42,6 +42,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
AST_MUTEX_DEFINE_STATIC(pgsql_lock);
AST_THREADSTORAGE(sql_buf);
AST_THREADSTORAGE(findtable_buf);
AST_THREADSTORAGE(where_buf);
AST_THREADSTORAGE(escapebuf_buf);
#define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
@ -59,7 +63,7 @@ struct columns {
};
struct tables {
ast_mutex_t lock;
ast_rwlock_t lock;
AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
AST_LIST_ENTRY(tables) list;
char name[0];
@ -87,15 +91,24 @@ static struct ast_cli_entry cli_realtime[] = {
AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
};
#define ESCAPE_STRING(buffer, stringname) \
do { \
int len; \
if ((len = strlen(stringname)) > (buffer->len - 1) / 2) { \
ast_str_make_space(&buffer, len * 2 + 1); \
} \
PQescapeStringConn(pgsqlConn, buffer->str, stringname, len, &pgresult); \
} while (0)
static void destroy_table(struct tables *table)
{
struct columns *column;
ast_mutex_lock(&table->lock);
ast_rwlock_wrlock(&table->lock);
while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
ast_free(column);
}
ast_mutex_unlock(&table->lock);
ast_mutex_destroy(&table->lock);
ast_rwlock_unlock(&table->lock);
ast_rwlock_destroy(&table->lock);
ast_free(table);
}
@ -103,7 +116,7 @@ static struct tables *find_table(const char *tablename)
{
struct columns *column;
struct tables *table;
struct ast_str *sql = ast_str_create(330);
struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
char *pgerror;
PGresult *result;
char *fname, *ftype, *flen, *fnotnull, *fdef;
@ -113,7 +126,7 @@ static struct tables *find_table(const char *tablename)
AST_LIST_TRAVERSE(&psql_tables, table, list) {
if (!strcasecmp(table->name, tablename)) {
ast_debug(1, "Found table in cache; now locking\n");
ast_mutex_lock(&table->lock);
ast_rwlock_rdlock(&table->lock);
ast_debug(1, "Lock cached table; now returning\n");
AST_LIST_UNLOCK(&psql_tables);
return table;
@ -140,9 +153,9 @@ static struct tables *find_table(const char *tablename)
return NULL;
}
strcpy(table->name, tablename); /* SAFE */
ast_mutex_init(&table->lock);
ast_rwlock_init(&table->lock);
AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
rows = PQntuples(result);
for (i = 0; i < rows; i++) {
fname = PQgetvalue(result, i, 0);
@ -186,23 +199,39 @@ static struct tables *find_table(const char *tablename)
PQclear(result);
AST_LIST_INSERT_TAIL(&psql_tables, table, list);
ast_mutex_lock(&table->lock);
ast_rwlock_rdlock(&table->lock);
AST_LIST_UNLOCK(&psql_tables);
return table;
}
static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
#define release_table(table) ast_rwlock_unlock(&(table)->lock);
static struct columns *find_column(struct tables *t, const char *colname)
{
struct columns *column;
/* Check that the column exists in the table */
AST_LIST_TRAVERSE(&t->columns, column, list) {
if (strcmp(column->name, colname) == 0) {
return column;
}
}
return NULL;
}
static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, va_list ap)
{
PGresult *result = NULL;
int num_rows = 0, pgerror;
char sql[256], escapebuf[513];
int num_rows = 0, pgresult;
struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
char *stringp;
char *chunk;
char *op;
const char *newparam, *newval;
struct ast_variable *var = NULL, *prev = NULL;
if (!table) {
if (!tablename) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
return NULL;
}
@ -216,7 +245,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
if (pgsqlConn) {
PQfinish(pgsqlConn);
pgsqlConn = NULL;
};
}
return NULL;
}
@ -224,15 +253,14 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
op = strchr(newparam, ' ') ? "" : " =";
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
return NULL;
}
snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
escapebuf);
ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, newparam, op, escapebuf->str);
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
if (!strchr(newparam, ' '))
@ -240,15 +268,14 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
else
op = "";
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
return NULL;
}
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
op, escapebuf);
ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, escapebuf->str);
}
va_end(ap);
@ -259,10 +286,10 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
return NULL;
}
if (!(result = PQexec(pgsqlConn, sql))) {
if (!(result = PQexec(pgsqlConn, sql->str))) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
"PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
return NULL;
@ -272,8 +299,8 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
&& result_status != PGRES_TUPLES_OK
&& result_status != PGRES_NONFATAL_ERROR) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
"PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
@ -281,7 +308,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
}
}
ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql);
ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql->str);
if ((num_rows = PQntuples(result)) > 0) {
int i = 0;
@ -318,7 +345,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
}
ast_free(fieldnames);
} else {
ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s.\n", table);
ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
}
ast_mutex_unlock(&pgsql_lock);
@ -330,8 +357,9 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
{
PGresult *result = NULL;
int num_rows = 0, pgerror;
char sql[256], escapebuf[513];
int num_rows = 0, pgresult;
struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
const char *initfield = NULL;
char *stringp;
char *chunk;
@ -358,7 +386,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
if (pgsqlConn) {
PQfinish(pgsqlConn);
pgsqlConn = NULL;
};
}
return NULL;
}
@ -375,15 +403,14 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
else
op = "";
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
return NULL;
}
snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
escapebuf);
ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, escapebuf->str);
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
if (!strchr(newparam, ' '))
@ -391,19 +418,18 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
else
op = "";
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
return NULL;
}
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
op, escapebuf);
ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, escapebuf->str);
}
if (initfield) {
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
ast_str_append(&sql, 0, " ORDER BY %s", initfield);
}
va_end(ap);
@ -415,10 +441,10 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
return NULL;
}
if (!(result = PQexec(pgsqlConn, sql))) {
if (!(result = PQexec(pgsqlConn, sql->str))) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
"PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
return NULL;
@ -428,8 +454,8 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
&& result_status != PGRES_TUPLES_OK
&& result_status != PGRES_NONFATAL_ERROR) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
"PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
@ -437,7 +463,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
}
}
ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql);
ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql->str);
if ((num_rows = PQntuples(result)) > 0) {
int numFields = PQnfields(result);
@ -490,22 +516,20 @@ static int update_pgsql(const char *database, const char *tablename, const char
const char *lookup, va_list ap)
{
PGresult *result = NULL;
int numrows = 0, pgerror;
char escapebuf[513];
int numrows = 0, pgresult;
const char *newparam, *newval;
struct ast_str *sql = ast_str_create(100);
struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
struct tables *table;
struct columns *column = NULL;
if (!tablename) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
ast_free(sql);
return -1;
}
if (!(table = find_table(tablename))) {
ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
ast_free(sql);
return -1;
}
@ -518,9 +542,8 @@ static int update_pgsql(const char *database, const char *tablename, const char
if (pgsqlConn) {
PQfinish(pgsqlConn);
pgsqlConn = NULL;
};
ast_mutex_unlock(&table->lock);
ast_free(sql);
}
release_table(table);
return -1;
}
@ -533,62 +556,51 @@ static int update_pgsql(const char *database, const char *tablename, const char
if (!column) {
ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
ast_mutex_unlock(&table->lock);
ast_free(sql);
release_table(table);
return -1;
}
/* Create the first part of the query using the first parameter/value pairs we just extracted
If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
ast_mutex_unlock(&table->lock);
ast_free(sql);
release_table(table);
return -1;
}
ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, escapebuf);
ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, escapebuf->str);
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
/* If the column is not within the table, then skip it */
AST_LIST_TRAVERSE(&table->columns, column, list) {
if (strcmp(column->name, newparam) == 0) {
break;
}
}
if (!column) {
if (!find_column(table, newparam)) {
ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
continue;
}
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
ast_mutex_unlock(&table->lock);
ast_free(sql);
release_table(table);
return -1;
}
ast_str_append(&sql, 0, ", %s = '%s'", newparam, escapebuf);
ast_str_append(&sql, 0, ", %s = '%s'", newparam, escapebuf->str);
}
va_end(ap);
ast_mutex_unlock(&table->lock);
release_table(table);
PQescapeStringConn(pgsqlConn, escapebuf, lookup, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ESCAPE_STRING(escapebuf, lookup);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
va_end(ap);
ast_free(sql);
return -1;
}
ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, escapebuf);
ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, escapebuf->str);
ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", sql->str);
@ -596,7 +608,6 @@ static int update_pgsql(const char *database, const char *tablename, const char
ast_mutex_lock(&pgsql_lock);
if (!pgsql_reconnect(database)) {
ast_mutex_unlock(&pgsql_lock);
ast_free(sql);
return -1;
}
@ -642,22 +653,145 @@ static int update_pgsql(const char *database, const char *tablename, const char
return -1;
}
#define ESCAPE_STRING(buffer, stringname) \
do { \
int len; \
if ((len = strlen(stringname)) > (buffer->len - 1) / 2) { \
ast_str_make_space(&buffer, len * 2 + 1); \
} \
PQescapeStringConn(pgsqlConn, buffer->str, stringname, len, &pgresult); \
} while (0)
static int update2_pgsql(const char *database, const char *tablename, va_list ap)
{
PGresult *result = NULL;
int numrows = 0, pgresult, first = 1;
struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
const char *newparam, *newval;
struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
struct ast_str *where = ast_str_thread_get(&where_buf, 100);
struct tables *table;
if (!tablename) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
return -1;
}
if (!escapebuf || !sql || !where) {
/* Memory error, already handled */
return -1;
}
if (!(table = find_table(tablename))) {
ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
return -1;
}
ast_str_set(&sql, 0, "UPDATE %s SET ", tablename);
ast_str_set(&where, 0, "WHERE");
while ((newparam = va_arg(ap, const char *))) {
if (!find_column(table, newparam)) {
ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", newparam, tablename, database);
release_table(table);
return -1;
}
newval = va_arg(ap, const char *);
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
release_table(table);
ast_free(sql);
return -1;
}
ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, escapebuf->str);
first = 0;
}
if (first) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Realtime update requires at least 1 parameter and 1 value to search on.\n");
if (pgsqlConn) {
PQfinish(pgsqlConn);
pgsqlConn = NULL;
}
release_table(table);
return -1;
}
/* Now retrieve the columns to update */
first = 1;
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
/* If the column is not within the table, then skip it */
if (!find_column(table, newparam)) {
ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", newparam, tablename, database);
continue;
}
ESCAPE_STRING(escapebuf, newval);
if (pgresult) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
release_table(table);
ast_free(sql);
return -1;
}
ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", newparam, escapebuf->str);
}
release_table(table);
ast_str_append(&sql, 0, " %s", where->str);
ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", sql->str);
/* We now have our complete statement; connect to the server and execute it. */
ast_mutex_lock(&pgsql_lock);
if (!pgsql_reconnect(database)) {
ast_mutex_unlock(&pgsql_lock);
return -1;
}
if (!(result = PQexec(pgsqlConn, sql->str))) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
return -1;
} else {
ExecStatusType result_status = PQresultStatus(result);
if (result_status != PGRES_COMMAND_OK
&& result_status != PGRES_TUPLES_OK
&& result_status != PGRES_NONFATAL_ERROR) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
return -1;
}
}
numrows = atoi(PQcmdTuples(result));
ast_mutex_unlock(&pgsql_lock);
ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
/* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
* An integer greater than zero indicates the number of rows affected
* Zero indicates that no records were updated
* -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
*/
if (numrows >= 0) {
return (int) numrows;
}
return -1;
}
static int store_pgsql(const char *database, const char *table, va_list ap)
{
PGresult *result = NULL;
Oid insertid;
struct ast_str *buf = ast_str_create(256);
struct ast_str *sql1 = ast_str_create(256);
struct ast_str *sql2 = ast_str_create(256);
struct ast_str *buf = ast_str_thread_get(&escapebuf_buf, 256);
struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256);
struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256);
int pgresult;
const char *newparam, *newval;
@ -710,9 +844,6 @@ static int store_pgsql(const char *database, const char *table, va_list ap)
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql1->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
ast_free(sql1);
ast_free(sql2);
ast_free(buf);
return -1;
} else {
ExecStatusType result_status = PQresultStatus(result);
@ -725,18 +856,12 @@ static int store_pgsql(const char *database, const char *table, va_list ap)
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
ast_free(sql1);
ast_free(sql2);
ast_free(buf);
return -1;
}
}
insertid = PQoidValue(result);
ast_mutex_unlock(&pgsql_lock);
ast_free(sql1);
ast_free(sql2);
ast_free(buf);
ast_debug(1, "PostgreSQL RealTime: row inserted on table: %s, id: %u\n", table, insertid);
@ -757,8 +882,8 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
PGresult *result = NULL;
int numrows = 0;
int pgresult;
struct ast_str *sql = ast_str_create(256);
struct ast_str *buf1 = ast_str_create(60), *buf2 = ast_str_create(60);
struct ast_str *sql = ast_str_thread_get(&sql_buf, 256);
struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60);
const char *newparam, *newval;
if (!table) {
@ -810,9 +935,6 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
ast_free(buf1);
ast_free(buf2);
ast_free(sql);
return -1;
} else {
ExecStatusType result_status = PQresultStatus(result);
@ -825,18 +947,12 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
ast_free(buf1);
ast_free(buf2);
ast_free(sql);
return -1;
}
}
numrows = atoi(PQcmdTuples(result));
ast_mutex_unlock(&pgsql_lock);
ast_free(buf1);
ast_free(buf2);
ast_free(sql);
ast_debug(1, "PostgreSQL RealTime: Deleted %d rows on table: %s\n", numrows, table);
@ -861,9 +977,7 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
long num_rows;
struct ast_variable *new_v;
struct ast_category *cur_cat = NULL;
char sqlbuf[1024] = "";
char *sql = sqlbuf;
size_t sqlleft = sizeof(sqlbuf);
struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
char last[80] = "";
int last_cat_metric = 0;
@ -874,11 +988,11 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
return NULL;
}
ast_build_string(&sql, &sqlleft, "SELECT category, var_name, var_val, cat_metric FROM %s ", table);
ast_build_string(&sql, &sqlleft, "WHERE filename='%s' and commented=0", file);
ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s "
"WHERE filename='%s' and commented=0"
"ORDER BY cat_metric DESC, var_metric ASC, category, var_name ", table, file);
ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", sqlbuf);
ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", sql->str);
/* We now have our complete statement; Lets connect to the server and execute it. */
ast_mutex_lock(&pgsql_lock);
@ -887,10 +1001,10 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
return NULL;
}
if (!(result = PQexec(pgsqlConn, sqlbuf))) {
if (!(result = PQexec(pgsqlConn, sql->str))) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
"PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", table, database);
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
return NULL;
@ -901,7 +1015,7 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
&& result_status != PGRES_NONFATAL_ERROR) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
@ -1067,7 +1181,7 @@ static int require_pgsql(const char *database, const char *tablename, va_list ap
}
}
}
ast_mutex_unlock(&table->lock);
release_table(table);
return res;
}
@ -1101,6 +1215,7 @@ static struct ast_config_engine pgsql_engine = {
.store_func = store_pgsql,
.destroy_func = destroy_pgsql,
.update_func = update_pgsql,
.update2_func = update2_pgsql,
.require_func = require_pgsql,
.unload_func = unload_pgsql,
};
@ -1353,7 +1468,7 @@ static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, s
AST_LIST_TRAVERSE(&cur->columns, col, list) {
ast_cli(a->fd, "%-20.20s %-20.20s %3d %-8.8s\n", col->name, col->type, col->len, col->notnull ? "NOT NULL" : "");
}
ast_mutex_unlock(&cur->lock);
release_table(cur);
} else {
ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
}

View File

@ -123,6 +123,9 @@ MACRO_BEGIN \
} \
MACRO_END
AST_THREADSTORAGE(sql_buf);
AST_THREADSTORAGE(where_buf);
/*!
* Maximum number of loops before giving up executing a query. Calls to
* sqlite_xxx() functions which can return SQLITE_BUSY
@ -299,7 +302,7 @@ static struct ast_config * config_handler(const char *database, const char *tabl
* \retval 0 if an error occurred.
*/
static size_t get_params(va_list ap, const char ***params_ptr,
const char ***vals_ptr);
const char ***vals_ptr, int warn);
/*!
* \brief SQLite callback function for RealTime configuration.
@ -396,6 +399,8 @@ static struct ast_config * realtime_multi_handler(const char *database,
*/
static int realtime_update_handler(const char *database, const char *table,
const char *keyfield, const char *entity, va_list ap);
static int realtime_update2_handler(const char *database, const char *table,
va_list ap);
/*!
* \brief Asterisk callback function for RealTime configuration (variable
@ -484,6 +489,7 @@ static struct ast_config_engine sqlite_engine =
.store_func = realtime_store_handler,
.destroy_func = realtime_destroy_handler,
.update_func = realtime_update_handler,
.update2_func = realtime_update2_handler,
.require_func = realtime_require_handler,
.unload_func = realtime_unload_handler,
};
@ -949,7 +955,7 @@ static struct ast_config *config_handler(const char *database, const char *table
return cfg;
}
static size_t get_params(va_list ap, const char ***params_ptr, const char ***vals_ptr)
static size_t get_params(va_list ap, const char ***params_ptr, const char ***vals_ptr, int warn)
{
const char **tmp, *param, *val, **params, **vals;
size_t params_count;
@ -981,8 +987,9 @@ static size_t get_params(va_list ap, const char ***params_ptr, const char ***val
if (params_count > 0) {
*params_ptr = params;
*vals_ptr = vals;
} else
} else if (warn) {
ast_log(LOG_WARNING, "1 parameter and 1 value at least required\n");
}
return params_count;
}
@ -1029,7 +1036,7 @@ static struct ast_variable * realtime_handler(const char *database, const char *
return NULL;
}
params_count = get_params(ap, &params, &vals);
params_count = get_params(ap, &params, &vals, 1);
if (params_count == 0)
return NULL;
@ -1038,10 +1045,10 @@ static struct ast_variable * realtime_handler(const char *database, const char *
/* \cond DOXYGEN_CAN_PARSE_THIS */
#undef QUERY
#define QUERY "SELECT * FROM '%q' WHERE commented = 0 AND %q%s '%q'"
#define QUERY "SELECT * FROM '%q' WHERE%s %q%s '%q'"
/* \endcond */
query = sqlite_mprintf(QUERY, table, params[0], op, vals[0]);
query = sqlite_mprintf(QUERY, table, !strcmp(config_table, table) ? " commented = 0 AND" : "", params[0], op, vals[0]);
if (!query) {
ast_log(LOG_WARNING, "Unable to allocate SQL query\n");
@ -1174,7 +1181,7 @@ static struct ast_config *realtime_multi_handler(const char *database,
return NULL;
}
if (!(params_count = get_params(ap, &params, &vals))) {
if (!(params_count = get_params(ap, &params, &vals, 1))) {
ast_config_destroy(cfg);
return NULL;
}
@ -1286,7 +1293,7 @@ static int realtime_update_handler(const char *database, const char *table,
return -1;
}
if (!(params_count = get_params(ap, &params, &vals)))
if (!(params_count = get_params(ap, &params, &vals, 1)))
return -1;
/* \cond DOXYGEN_CAN_PARSE_THIS */
@ -1355,6 +1362,80 @@ static int realtime_update_handler(const char *database, const char *table,
return rows_num;
}
static int realtime_update2_handler(const char *database, const char *table,
va_list ap)
{
char *errormsg = NULL, *tmp1, *tmp2;
int error, rows_num, first = 1;
struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
struct ast_str *where = ast_str_thread_get(&where_buf, 100);
const char *param, *value;
if (!table) {
ast_log(LOG_WARNING, "Table name unspecified\n");
return -1;
}
if (!sql) {
return -1;
}
ast_str_set(&sql, 0, "UPDATE %s SET", table);
ast_str_set(&where, 0, " WHERE");
while ((param = va_arg(ap, const char *))) {
value = va_arg(ap, const char *);
ast_str_append(&where, 0, "%s %s = %s",
first ? "" : " AND",
tmp1 = sqlite_mprintf("%q", param),
tmp2 = sqlite_mprintf("%Q", value));
sqlite_freemem(tmp1);
sqlite_freemem(tmp2);
first = 0;
}
if (first) {
ast_log(LOG_ERROR, "No criteria specified on update to '%s@%s'!\n", table, database);
return -1;
}
first = 1;
while ((param = va_arg(ap, const char *))) {
value = va_arg(ap, const char *);
ast_str_append(&sql, 0, "%s %s = %s",
first ? "" : ",",
tmp1 = sqlite_mprintf("%q", param),
tmp2 = sqlite_mprintf("%Q", value));
sqlite_freemem(tmp1);
sqlite_freemem(tmp2);
first = 0;
}
ast_str_append(&sql, 0, " %s", where->str);
ast_debug(1, "SQL query: %s\n", sql->str);
ast_mutex_lock(&mutex);
RES_CONFIG_SQLITE_BEGIN
error = sqlite_exec(db, sql->str, NULL, NULL, &errormsg);
RES_CONFIG_SQLITE_END(error)
if (!error) {
rows_num = sqlite_changes(db);
} else {
rows_num = -1;
}
ast_mutex_unlock(&mutex);
if (error) {
ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
}
sqlite_freemem(errormsg);
return rows_num;
}
static int realtime_store_handler(const char *database, const char *table, va_list ap)
{
char *errormsg = NULL, *tmp_str, *tmp_keys = NULL, *tmp_keys2 = NULL, *tmp_vals = NULL, *tmp_vals2 = NULL;
@ -1368,7 +1449,7 @@ static int realtime_store_handler(const char *database, const char *table, va_li
return -1;
}
if (!(params_count = get_params(ap, &params, &vals)))
if (!(params_count = get_params(ap, &params, &vals, 1)))
return -1;
/* \cond DOXYGEN_CAN_PARSE_THIS */
@ -1392,10 +1473,10 @@ static int realtime_store_handler(const char *database, const char *table, va_li
}
if ( tmp_vals2 ) {
tmp_vals = sqlite_mprintf("%s, '%q'", tmp_vals2, params[i]);
tmp_vals = sqlite_mprintf("%s, '%q'", tmp_vals2, vals[i]);
sqlite_freemem(tmp_vals2);
} else {
tmp_vals = sqlite_mprintf("'%q'", params[i]);
tmp_vals = sqlite_mprintf("'%q'", vals[i]);
}
if (!tmp_vals) {
ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
@ -1453,7 +1534,7 @@ static int realtime_destroy_handler(const char *database, const char *table,
const char *keyfield, const char *entity, va_list ap)
{
char *query, *errormsg = NULL, *tmp_str;
const char **params, **vals;
const char **params = NULL, **vals = NULL;
size_t params_count;
int error, rows_num;
size_t i;
@ -1463,8 +1544,7 @@ static int realtime_destroy_handler(const char *database, const char *table,
return -1;
}
if (!(params_count = get_params(ap, &params, &vals)))
return -1;
params_count = get_params(ap, &params, &vals, 0);
/* \cond DOXYGEN_CAN_PARSE_THIS */
#undef QUERY
@ -1509,10 +1589,11 @@ static int realtime_destroy_handler(const char *database, const char *table,
error = sqlite_exec(db, query, NULL, NULL, &errormsg);
RES_CONFIG_SQLITE_END(error)
if (!error)
if (!error) {
rows_num = sqlite_changes(db);
else
} else {
rows_num = -1;
}
ast_mutex_unlock(&mutex);

View File

@ -76,7 +76,8 @@ static char *cli_realtime_load(struct ast_cli_entry *e, int cmd, struct ast_cli_
return CLI_SUCCESS;
}
static char *cli_realtime_update(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) {
static char *cli_realtime_update(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
int res = 0;
switch (cmd) {
@ -93,18 +94,149 @@ static char *cli_realtime_update(struct ast_cli_entry *e, int cmd, struct ast_cl
return NULL;
}
if (a->argc < 7)
return CLI_SHOWUSAGE;
res = ast_update_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], SENTINEL);
if(res < 0) {
if (res < 0) {
ast_cli(a->fd, "Failed to update. Check the debug log for possible SQL related entries.\n");
return CLI_FAILURE;
}
ast_cli(a->fd, "Updated %d RealTime record%s.\n", res, ESS(res));
ast_cli(a->fd, "Updated %d RealTime record%s.\n", res, ESS(res));
return CLI_SUCCESS;
}
static char *cli_realtime_update2(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
int res = -1;
switch (cmd) {
case CLI_INIT:
e->command = "realtime update2";
e->usage =
"Usage: realtime update2 <family> <colupdate> <newvalue> <colmatch> <valuematch> [... <colmatch5> <valuematch5>]\n"
" Update a single variable using the RealTime driver.\n"
" You must supply a family name, a column to update on, a new value, column to match, and value to match.\n"
" Ex: realtime update sipfriends name bobsphone port 4343\n"
" will execute SQL as UPDATE sipfriends SET port = 4343 WHERE name = bobsphone\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc < 7)
return CLI_SHOWUSAGE;
if (a->argc == 7) {
res = ast_update2_realtime(a->argv[2], a->argv[5], a->argv[6], SENTINEL, a->argv[3], a->argv[4], SENTINEL);
} else if (a->argc == 9) {
res = ast_update2_realtime(a->argv[2], a->argv[5], a->argv[6], a->argv[7], a->argv[8], SENTINEL, a->argv[3], a->argv[4], SENTINEL);
} else if (a->argc == 11) {
res = ast_update2_realtime(a->argv[2], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], SENTINEL, a->argv[3], a->argv[4], SENTINEL);
} else if (a->argc == 13) {
res = ast_update2_realtime(a->argv[2], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], a->argv[11], a->argv[12], SENTINEL, a->argv[3], a->argv[4], SENTINEL);
} else if (a->argc == 15) {
res = ast_update2_realtime(a->argv[2], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], a->argv[11], a->argv[12], a->argv[13], a->argv[14], SENTINEL, a->argv[3], a->argv[4], SENTINEL);
} else {
return CLI_SHOWUSAGE;
}
if (res < 0) {
ast_cli(a->fd, "Failed to update. Check the debug log for possible SQL related entries.\n");
return CLI_FAILURE;
}
ast_cli(a->fd, "Updated %d RealTime record%s.\n", res, ESS(res));
return CLI_SUCCESS;
}
static char *cli_realtime_store(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
int res = -1;
switch (cmd) {
case CLI_INIT:
e->command = "realtime store";
e->usage =
"Usage: realtime store <family> <colname1> <value1> [<colname2> <value2> [... <colmatch5> <valuematch5>]]\n"
" Create a stored row using the RealTime driver.\n"
" You must supply a family name and name/value pairs (up to 5). If\n"
" you need to store more than 5 key/value pairs, start with the first\n"
" five, then use 'realtime update' or 'realtime update2' to add\n"
" additional columns.\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc < 5) {
return CLI_SHOWUSAGE;
} else if (a->argc == 5) {
res = ast_store_realtime(a->argv[2], a->argv[3], a->argv[4], SENTINEL);
} else if (a->argc == 7) {
res = ast_store_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], SENTINEL);
} else if (a->argc == 9) {
res = ast_store_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], a->argv[7], a->argv[8], SENTINEL);
} else if (a->argc == 11) {
res = ast_store_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], SENTINEL);
} else if (a->argc == 13) {
res = ast_store_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], a->argv[11], a->argv[12], SENTINEL);
} else {
return CLI_SHOWUSAGE;
}
if (res < 0) {
ast_cli(a->fd, "Failed to store record. Check the debug log for possible SQL related entries.\n");
return CLI_FAILURE;
}
ast_cli(a->fd, "Stored RealTime record.\n");
return CLI_SUCCESS;
}
static char *cli_realtime_destroy(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
int res = -1;
switch (cmd) {
case CLI_INIT:
e->command = "realtime destroy";
e->usage =
"Usage: realtime destroy <family> <colname1> <value1> [<colname2> <value2> [... <colmatch5> <valuematch5>]]\n"
" Remove a stored row using the RealTime driver.\n"
" You must supply a family name and name/value pairs (up to 5).\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc < 5) {
return CLI_SHOWUSAGE;
} else if (a->argc == 5) {
res = ast_destroy_realtime(a->argv[2], a->argv[3], a->argv[4], SENTINEL);
} else if (a->argc == 7) {
res = ast_destroy_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], SENTINEL);
} else if (a->argc == 9) {
res = ast_destroy_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], a->argv[7], a->argv[8], SENTINEL);
} else if (a->argc == 11) {
res = ast_destroy_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], SENTINEL);
} else if (a->argc == 13) {
res = ast_destroy_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], a->argv[7], a->argv[8], a->argv[9], a->argv[10], a->argv[11], a->argv[12], SENTINEL);
} else {
return CLI_SHOWUSAGE;
}
if (res < 0) {
ast_cli(a->fd, "Failed to remove record. Check the debug log for possible SQL related entries.\n");
return CLI_FAILURE;
}
ast_cli(a->fd, "Removed %d RealTime record%s.\n", res, ESS(res));
return CLI_SUCCESS;
}
@ -112,6 +244,9 @@ static char *cli_realtime_update(struct ast_cli_entry *e, int cmd, struct ast_cl
static struct ast_cli_entry cli_realtime[] = {
AST_CLI_DEFINE(cli_realtime_load, "Used to print out RealTime variables."),
AST_CLI_DEFINE(cli_realtime_update, "Used to update RealTime variables."),
AST_CLI_DEFINE(cli_realtime_update2, "Used to test the RealTime update2 method"),
AST_CLI_DEFINE(cli_realtime_store, "Store a new row into a RealTime database"),
AST_CLI_DEFINE(cli_realtime_destroy, "Delete a row from a RealTime database"),
};
static int unload_module(void)