Merge realtime_update2 branch, which adds a new realtime API call named
authorTilghman Lesher <tilghman@meg.abyt.es>
Tue, 14 Oct 2008 00:08:52 +0000 (00:08 +0000)
committerTilghman Lesher <tilghman@meg.abyt.es>
Tue, 14 Oct 2008 00:08:52 +0000 (00:08 +0000)
'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: https://origsvn.digium.com/svn/asterisk/trunk@148570 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_voicemail.c
include/asterisk/config.h
include/asterisk/strings.h
main/config.c
res/res_config_curl.c
res/res_config_ldap.c
res/res_config_odbc.c
res/res_config_pgsql.c
res/res_config_sqlite.c
res/res_realtime.c

index 4134f19..0ddcf3b 100644 (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);
                }
                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;
                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 ausemacro = 0;
        int ousemacro = 0;
        int ouseexten = 0;
-       int rtmsgid = 0;
-       char tmpid[16];
        char tmpdur[16];
        char priority[16];
        char origtime[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));
                        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 */
                }
 
                /* 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")) {
                                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);
                                }
                        } 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")) {
                                        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
                                        }
                                } 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")) {
 
                                        ast_unlock_path(dir);
                                        if (ast_check_realtime("voicemail_data")) {
-                                               snprintf(tmpid, sizeof(tmpid), "%d", rtmsgid);
                                                snprintf(tmpdur, sizeof(tmpdur), "%d", duration);
                                                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.
                                        }
                                        /* We must store the file first, before copying the message, because
                                         * ODBC storage does the entire copy with SQL.
index 8c9a1d3..bd030a7 100644 (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 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);
 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_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;
        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.
  * 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;
  */
 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).
  * 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;
 
  */
 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.
  * 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;
 
  */
 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.
  *
  * \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;
 
 /*! 
  */
 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.
  *
  * \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;
 
  */
 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.
  *
  * 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;
 
  */
 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) attribute_sentinel;
 
index 514b3af..6825242 100644 (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));)
        }
 
                _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;
 }
 )
 
 }
 )
 
index 5ea68a7..986c815 100644 (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;
 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));
        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;
 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;
 
        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;
 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));
        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;
 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);
        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;
 {
        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);
        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;
 }
 
        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;
 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);
        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;
 {
        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);
        va_list ap;
 
        va_start(ap, lookup);
index 37079ad..b159c76 100644 (file)
@@ -275,6 +275,69 @@ static int update_curl(const char *url, const char *unused, const char *keyfield
        return -1;
 }
 
        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
 /*!
  * \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,
        .store_func = store_curl,
        .destroy_func = destroy_curl,
        .update_func = update_curl,
+       .update2_func = update2_curl,
        .require_func = require_curl,
 };
 
        .require_func = require_curl,
 };
 
index 966da9e..b5690b7 100644 (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;
        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 */
 };
 
 /*! \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;
 }
 
        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,
 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)
 };
 
 static int load_module(void)
index 82d6c98..ea83652 100644 (file)
@@ -49,6 +49,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/res_odbc.h"
 #include "asterisk/utils.h"
 
 #include "asterisk/res_odbc.h"
 #include "asterisk/utils.h"
 
+AST_THREADSTORAGE(sql_buf);
+
 struct custom_prepare_struct {
        const char *sql;
        const char *extra;
 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;
 }
 
        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
 /*!
  * \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,
        .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,
 };
        .require_func = require_odbc,
        .unload_func = ast_odbc_clear_cache,
 };
index f7c1f3a..fe672bf 100644 (file)
@@ -42,6 +42,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 
 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
 #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"
 
 
 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
 
@@ -59,7 +63,7 @@ struct columns {
 };
 
 struct tables {
 };
 
 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];
        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"),
 };
 
        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;
 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);
        }
        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);
 }
 
        ast_free(table);
 }
 
@@ -103,7 +116,7 @@ static struct tables *find_table(const char *tablename)
 {
        struct columns *column;
        struct tables *table;
 {
        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;
        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_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;
                        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 */
                return NULL;
        }
        strcpy(table->name, tablename); /* SAFE */
-       ast_mutex_init(&table->lock);
+       ast_rwlock_init(&table->lock);
        AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
        AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
-       
+
        rows = PQntuples(result);
        for (i = 0; i < rows; i++) {
                fname = PQgetvalue(result, i, 0);
        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);
        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;
 }
 
        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;
 {
        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;
 
        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;
        }
                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;
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
+               }
                return 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, ' ') ? "" : " =";
 
           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;
        }
 
                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, ' '))
        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 = "";
 
                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;
                }
 
                        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);
 
        }
        va_end(ap);
 
@@ -259,10 +286,10 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                return NULL;
        }
 
                return NULL;
        }
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, sql->str))) {
                ast_log(LOG_WARNING,
                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;
                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,
                        && 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);
                        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;
 
        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_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);
        }
 
        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;
 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;
        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;
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
+               }
                return NULL;
        }
 
                return NULL;
        }
 
@@ -375,15 +403,14 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
        else
                op = "";
 
        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;
        }
 
                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, ' '))
        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 = "";
 
                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;
                }
 
                        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) {
        }
 
        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);
        }
 
        va_end(ap);
@@ -415,10 +441,10 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                return NULL;
        }
 
                return NULL;
        }
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, sql->str))) {
                ast_log(LOG_WARNING,
                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;
                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,
                        && 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);
                        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);
 
        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;
                                                const char *lookup, va_list ap)
 {
        PGresult *result = NULL;
-       int numrows = 0, pgerror;
-       char escapebuf[513];
+       int numrows = 0, pgresult;
        const char *newparam, *newval;
        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");
        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);
                return -1;
        }
 
        if (!(table = find_table(tablename))) {
                ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
-               ast_free(sql);
                return -1;
        }
 
                return -1;
        }
 
@@ -518,9 +542,8 @@ static int update_pgsql(const char *database, const char *tablename, const char
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
-               ast_mutex_unlock(&table->lock);
-               ast_free(sql);
+               }
+               release_table(table);
                return -1;
        }
 
                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);
 
        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 */
 
                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_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;
        }
                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 *);
 
 
        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;
                }
 
                        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_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;
                }
 
                        return -1;
                }
 
-               ast_str_append(&sql, 0, ", %s = '%s'", newparam, escapebuf);
+               ast_str_append(&sql, 0, ", %s = '%s'", newparam, escapebuf->str);
        }
        va_end(ap);
        }
        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_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
                va_end(ap);
-               ast_free(sql);
                return -1;
        }
 
                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);
 
 
        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_mutex_lock(&pgsql_lock);
        if (!pgsql_reconnect(database)) {
                ast_mutex_unlock(&pgsql_lock);
-               ast_free(sql);
                return -1;
        }
 
                return -1;
        }
 
@@ -642,22 +653,145 @@ static int update_pgsql(const char *database, const char *tablename, const char
        return -1;
 }
 
        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;
 
 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;
 
        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_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);
                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_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);
                        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);
 
 
        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;
        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) {
        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_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);
                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_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);
                        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);
 
 
        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;
        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;
 
        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;
        }
 
                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);
 
        /* 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;
        }
 
                return NULL;
        }
 
-       if (!(result = PQexec(pgsqlConn, sqlbuf))) {
+       if (!(result = PQexec(pgsqlConn, sql->str))) {
                ast_log(LOG_WARNING,
                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;
                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");
                        && 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);
                        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;
 }
 
        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,
        .store_func = store_pgsql,
        .destroy_func = destroy_pgsql,
        .update_func = update_pgsql,
+       .update2_func = update2_pgsql,
        .require_func = require_pgsql,
        .unload_func = unload_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_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]);
                }
                } else {
                        ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
                }
index 7a18d00..c3ef1dd 100644 (file)
@@ -123,6 +123,9 @@ MACRO_BEGIN                                         \
        }                                               \
 MACRO_END
 
        }                                               \
 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
 /*!
  * 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,
  * \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.
 
 /*!
  * \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_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
 
 /*!
  * \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,
        .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,
 };
        .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;
 }
 
        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;
 {
        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;
        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");
                ast_log(LOG_WARNING, "1 parameter and 1 value at least required\n");
+       }
 
        return params_count;
 }
 
        return params_count;
 }
@@ -1029,7 +1036,7 @@ static struct ast_variable * realtime_handler(const char *database, const char *
                return NULL;
        }
 
                return NULL;
        }
 
-       params_count = get_params(ap, &params, &vals);
+       params_count = get_params(ap, &params, &vals, 1);
 
        if (params_count == 0)
                return NULL;
 
        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
 
 /* \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 */
 
 /* \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");
 
        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;
        }
 
                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;
        }
                ast_config_destroy(cfg);
                return NULL;
        }
@@ -1286,7 +1293,7 @@ static int realtime_update_handler(const char *database, const char *table,
                return -1;
        }
 
                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 */
                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;
 }
 
        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;
 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;
        }
 
                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 */
                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 ) {
                }
 
                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 {
                        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");
                }
                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 *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;
        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;
        }
 
                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
 
 /* \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)
 
                error = sqlite_exec(db, query, NULL, NULL, &errormsg);
        RES_CONFIG_SQLITE_END(error)
 
-       if (!error)
+       if (!error) {
                rows_num = sqlite_changes(db);
                rows_num = sqlite_changes(db);
-       else
+       } else {
                rows_num = -1;
                rows_num = -1;
+       }
 
        ast_mutex_unlock(&mutex);
 
 
        ast_mutex_unlock(&mutex);
 
index 5ecff8b..15e25be 100644 (file)
@@ -76,7 +76,8 @@ static char *cli_realtime_load(struct ast_cli_entry *e, int cmd, struct ast_cli_
        return CLI_SUCCESS;
 }
 
        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) {
        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;
        }
 
                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 (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));
+
+       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, "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_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;
 }
 
        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."),
 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)
 };
 
 static int unload_module(void)