[svn-commits] tilghman: branch tilghman/realtime_update2 r147589 - in /team/tilghman/realti...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Wed Oct 8 12:34:01 CDT 2008


Author: tilghman
Date: Wed Oct  8 12:34:01 2008
New Revision: 147589

URL: http://svn.digium.com/view/asterisk?view=rev&rev=147589
Log:
Initial batch of changes

Modified:
    team/tilghman/realtime_update2/include/asterisk/config.h
    team/tilghman/realtime_update2/include/asterisk/strings.h
    team/tilghman/realtime_update2/main/config.c
    team/tilghman/realtime_update2/res/res_config_curl.c
    team/tilghman/realtime_update2/res/res_config_ldap.c
    team/tilghman/realtime_update2/res/res_config_odbc.c
    team/tilghman/realtime_update2/res/res_config_pgsql.c
    team/tilghman/realtime_update2/res/res_config_sqlite.c

Modified: team/tilghman/realtime_update2/include/asterisk/config.h
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/include/asterisk/config.h?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/include/asterisk/config.h (original)
+++ team/tilghman/realtime_update2/include/asterisk/config.h Wed Oct  8 12:34:01 2008
@@ -91,6 +91,7 @@
 typedef struct ast_variable *realtime_var_get(const char *database, const char *table, va_list ap);
 typedef struct ast_config *realtime_multi_get(const char *database, const char *table, va_list ap);
 typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
+typedef int realtime_update2(const char *database, const char *table, va_list ap);
 typedef int realtime_store(const char *database, const char *table, va_list ap);
 typedef int realtime_destroy(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
 typedef int realtime_require(const char *database, const char *table, va_list ap);
@@ -103,6 +104,7 @@
 	realtime_var_get *realtime_func;
 	realtime_multi_get *realtime_multi_func;
 	realtime_update *update_func;
+	realtime_update2 *update2_func;
 	realtime_store *store_func;
 	realtime_destroy *destroy_func;
 	realtime_require *require_func;
@@ -208,6 +210,9 @@
  * entity in realtime and return a variable list of its parameters.  Note
  * that unlike the variables in ast_config, the resulting list of variables
  * MUST be freed with ast_variables_destroy() as there is no container.
+ *
+ * Note that you should use the constant SENTINEL to terminate arguments, in
+ * order to preserve cross-platform compatibility.
  */
 struct ast_variable *ast_load_realtime(const char *family, ...) attribute_sentinel;
 struct ast_variable *ast_load_realtime_all(const char *family, ...) attribute_sentinel;
@@ -243,6 +248,9 @@
  * a timeout value may reasonably be specified as an INTEGER2, with size 5.
  * Even though values above 32767 seconds are possible, they are unlikely
  * to be useful, and we should not complain about that size).
+ *
+ * Note that you should use the constant SENTINEL to terminate arguments, in
+ * order to preserve cross-platform compatibility.
  */
 int ast_realtime_require_field(const char *family, ...) attribute_sentinel;
 
@@ -254,6 +262,9 @@
  * the ast_load_realtime, this function can return more than one entry and
  * is thus stored inside a taditional ast_config structure rather than 
  * just returning a linked list of variables.
+ *
+ * Note that you should use the constant SENTINEL to terminate arguments, in
+ * order to preserve cross-platform compatibility.
  */
 struct ast_config *ast_load_realtime_multientry(const char *family, ...) attribute_sentinel;
 
@@ -264,14 +275,31 @@
  * \param lookup which value to look for in the key field to match the entry.
  * This function is used to update a parameter in realtime configuration space.
  *
+ * Note that you should use the constant SENTINEL to terminate arguments, in
+ * order to preserve cross-platform compatibility.
  */
 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...) attribute_sentinel;
+
+/*! 
+ * \brief Update realtime configuration 
+ * \param family which family/config to be updated
+ * This function is used to update a parameter in realtime configuration space.
+ * It includes the ability to lookup a row based upon multiple key criteria.
+ * As a result, this function includes two sentinel values, one to terminate
+ * lookup values and the other to terminate the listing of fields to update.
+ *
+ * Note that you should use the constant SENTINEL to terminate arguments, in
+ * order to preserve cross-platform compatibility.
+ */
+int ast_update2_realtime(const char *family, ...) attribute_sentinel;
 
 /*! 
  * \brief Create realtime configuration 
  * \param family which family/config to be created
  * This function is used to create a parameter in realtime configuration space.
  *
+ * Note that you should use the constant SENTINEL to terminate arguments, in
+ * order to preserve cross-platform compatibility.
  */
 int ast_store_realtime(const char *family, ...) attribute_sentinel;
 
@@ -283,6 +311,8 @@
  * 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;
 

Modified: team/tilghman/realtime_update2/include/asterisk/strings.h
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/include/asterisk/strings.h?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/include/asterisk/strings.h (original)
+++ team/tilghman/realtime_update2/include/asterisk/strings.h Wed Oct  8 12:34:01 2008
@@ -421,8 +421,8 @@
 		_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;
 }
 )
 

Modified: team/tilghman/realtime_update2/main/config.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/main/config.c?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/main/config.c (original)
+++ team/tilghman/realtime_update2/main/config.c Wed Oct  8 12:34:01 2008
@@ -2069,8 +2069,8 @@
 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
 {
 	struct ast_config_engine *eng;
-	char db[256]="";
-	char table[256]="";
+	char db[256];
+	char table[256];
 	struct ast_variable *res=NULL;
 
 	eng = find_engine(family, db, sizeof(db), table, sizeof(table));
@@ -2141,8 +2141,8 @@
 int ast_realtime_require_field(const char *family, ...)
 {
 	struct ast_config_engine *eng;
-	char db[256] = "";
-	char table[256] = "";
+	char db[256];
+	char table[256];
 	va_list ap;
 	int res = -1;
 
@@ -2159,8 +2159,8 @@
 int ast_unload_realtime(const char *family)
 {
 	struct ast_config_engine *eng;
-	char db[256] = "";
-	char table[256] = "";
+	char db[256];
+	char table[256];
 	int res = -1;
 
 	eng = find_engine(family, db, sizeof(db), table, sizeof(table));
@@ -2173,9 +2173,9 @@
 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
 {
 	struct ast_config_engine *eng;
-	char db[256]="";
-	char table[256]="";
-	struct ast_config *res=NULL;
+	char db[256];
+	char table[256];
+	struct ast_config *res = NULL;
 	va_list ap;
 
 	va_start(ap, family);
@@ -2191,8 +2191,8 @@
 {
 	struct ast_config_engine *eng;
 	int res = -1;
-	char db[256]="";
-	char table[256]="";
+	char db[256];
+	char table[256];
 	va_list ap;
 
 	va_start(ap, lookup);
@@ -2204,12 +2204,29 @@
 	return res;
 }
 
-int ast_store_realtime(const char *family, ...)
+int ast_update2_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);
+	eng = find_engine(family, db, sizeof(db), table, sizeof(table));
+	if (eng && eng->update2_func) 
+		res = eng->update2_func(db, table, ap);
+	va_end(ap);
+
+	return res;
+}
+
+int ast_store_realtime(const char *family, ...)
+{
+	struct ast_config_engine *eng;
+	int res = -1;
+	char db[256];
+	char table[256];
 	va_list ap;
 
 	va_start(ap, family);
@@ -2225,8 +2242,8 @@
 {
 	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);

Modified: team/tilghman/realtime_update2/res/res_config_curl.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/res/res_config_curl.c?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/res/res_config_curl.c (original)
+++ team/tilghman/realtime_update2/res/res_config_curl.c Wed Oct  8 12:34:01 2008
@@ -258,6 +258,69 @@
 	va_end(ap);
 
 	ast_str_append(&query, 0, ")}");
+	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;
+}
+
+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 i, 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 */
@@ -535,6 +598,7 @@
 	.store_func = store_curl,
 	.destroy_func = destroy_curl,
 	.update_func = update_curl,
+	.update2_func = update2_curl,
 	.require_func = require_curl,
 };
 

Modified: team/tilghman/realtime_update2/res/res_config_ldap.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/res/res_config_ldap.c?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/res/res_config_ldap.c (original)
+++ team/tilghman/realtime_update2/res/res_config_ldap.c Wed Oct  8 12:34:01 2008
@@ -89,6 +89,7 @@
 	struct ast_variable *attributes;  /*!< attribute names conversion */
 	struct ast_variable *delimiters;  /*!< the current delimiter is semicolon, so we are not using this variable */
 	AST_LIST_ENTRY(ldap_table_config) entry;
+	/* TODO: Make proxies work */
 };
 
 /*! \brief Should be locked before using it */
@@ -1305,12 +1306,200 @@
 	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) {
+		ast_debug(3, "LINE(%d) Modifying %s=%s hits: %d\n", __LINE__, attribute, lookup, num_entries);
+		for (i = 0; option_debug > 2 && i < mods_size - 1; i++)
+			ast_debug(3, "LINE(%d) %s=%s \n", __LINE__, ldap_mods[i]->mod_type, ldap_mods[i]->mod_values[0]);
+
+		ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
+
+		for (i = 0; ldap_entry; i++) { 
+			dn = ldap_get_dn(ldapConn, ldap_entry);
+			if ((error = ldap_modify_ext_s(ldapConn, dn, ldap_mods, NULL, NULL)) != LDAP_SUCCESS) 
+				ast_log(LOG_ERROR, "Couldn't modify dn:%s because %s", dn, ldap_err2string(error));
+
+			ldap_entry = ldap_next_entry(ldapConn, ldap_entry);
+		}
+	}
+
+	ast_mutex_unlock(&ldap_lock);
+	if (filter)
+		free(filter);
+	if (clean_basedn)
+		free(clean_basedn);
+	ldap_msgfree(ldap_result_msg);
+	ldap_mods_free(ldap_mods, 0);
+	return num_entries;
+}
+
 static struct ast_config_engine ldap_engine = {
 	.name = "ldap",
 	.load_func = config_ldap,
 	.realtime_func = realtime_ldap,
 	.realtime_multi_func = realtime_multi_ldap,
-	.update_func = update_ldap
+	.update_func = update_ldap,
+	.update2_func = update2_ldap,
 };
 
 static int load_module(void)

Modified: team/tilghman/realtime_update2/res/res_config_odbc.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/res/res_config_odbc.c?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/res/res_config_odbc.c (original)
+++ team/tilghman/realtime_update2/res/res_config_odbc.c Wed Oct  8 12:34:01 2008
@@ -48,6 +48,8 @@
 #include "asterisk/lock.h"
 #include "asterisk/res_odbc.h"
 #include "asterisk/utils.h"
+
+AST_THREADSTORAGE(sql_buf);
 
 struct custom_prepare_struct {
 	const char *sql;
@@ -470,6 +472,145 @@
 
 	if (rowcount >= 0)
 		return (int)rowcount;
+
+	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, count = 0, 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->description);
+		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, "%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, table, 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, "%s%s ? ", first ? "" : ", ", 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;
+
+	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;
 }
@@ -899,6 +1040,7 @@
 	.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,
 };

Modified: team/tilghman/realtime_update2/res/res_config_pgsql.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/realtime_update2/res/res_config_pgsql.c?view=diff&rev=147589&r1=147588&r2=147589
==============================================================================
--- team/tilghman/realtime_update2/res/res_config_pgsql.c (original)
+++ team/tilghman/realtime_update2/res/res_config_pgsql.c Wed Oct  8 12:34:01 2008
@@ -42,6 +42,10 @@
 #include "asterisk/cli.h"
 
 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
+AST_THREADSTORAGE(sql_buf);
+AST_THREADSTORAGE(findtable_buf);
+AST_THREADSTORAGE(where_buf);
+AST_THREADSTORAGE(escapebuf_buf);
 
 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
 
@@ -59,7 +63,7 @@
 };
 
 struct tables {
-	ast_mutex_t lock;
+	ast_rwlock_t lock;
 	AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
 	AST_LIST_ENTRY(tables) list;
 	char name[0];
@@ -87,15 +91,24 @@
 	AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
 };
 
+#define ESCAPE_STRING(buffer, stringname) \
+	do { \
+		int len; \
+		if ((len = strlen(stringname)) > (buffer->len - 1) / 2) { \
+			ast_str_make_space(&buffer, len * 2 + 1); \
+		} \
+		PQescapeStringConn(pgsqlConn, buffer->str, stringname, len, &pgresult); \
+	} while (0)
+
 static void destroy_table(struct tables *table)
 {
 	struct columns *column;
-	ast_mutex_lock(&table->lock);
+	ast_rwlock_wrlock(&table->lock);
 	while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
 		ast_free(column);
 	}
-	ast_mutex_unlock(&table->lock);
-	ast_mutex_destroy(&table->lock);
+	ast_rwlock_unlock(&table->lock);
+	ast_rwlock_destroy(&table->lock);
 	ast_free(table);
 }
 
@@ -103,7 +116,7 @@
 {
 	struct columns *column;
 	struct tables *table;
-	struct ast_str *sql = ast_str_create(330);
+	struct ast_str *sql = ast_str_thread_get(findtable_buf, 330);
 	char *pgerror;
 	PGresult *result;
 	char *fname, *ftype, *flen, *fnotnull, *fdef;
@@ -113,7 +126,7 @@
 	AST_LIST_TRAVERSE(&psql_tables, table, list) {
 		if (!strcasecmp(table->name, tablename)) {
 			ast_debug(1, "Found table in cache; now locking\n");
-			ast_mutex_lock(&table->lock);
+			ast_rwlock_rdlock(&table->lock);
 			ast_debug(1, "Lock cached table; now returning\n");
 			AST_LIST_UNLOCK(&psql_tables);
 			return table;
@@ -140,9 +153,9 @@
 		return NULL;
 	}
 	strcpy(table->name, tablename); /* SAFE */
-	ast_mutex_init(&table->lock);
+	ast_rwlock_init(&table->lock);
 	AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
-	
+
 	rows = PQntuples(result);
 	for (i = 0; i < rows; i++) {
 		fname = PQgetvalue(result, i, 0);
@@ -186,23 +199,39 @@
 	PQclear(result);
 
 	AST_LIST_INSERT_TAIL(&psql_tables, table, list);
-	ast_mutex_lock(&table->lock);
+	ast_rwlock_rdlock(&table->lock);
 	AST_LIST_UNLOCK(&psql_tables);
 	return table;
 }
 
-static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
+#define release_table(table) ast_rwlock_unlock(&(table)->lock);
+
+static struct columns *find_column(struct tables *t, const char *colname)
+{
+	struct columns *column;
+
+	/* Check that the column exists in the table */
+	AST_LIST_TRAVERSE(&t->columns, column, list) {
+		if (strcmp(column->name, colname) == 0) {
+			return column;
+		}
+	}
+	return NULL;
+}
+
+static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, va_list ap)
 {
 	PGresult *result = NULL;
-	int num_rows = 0, pgerror;
-	char sql[256], escapebuf[513];
+	int num_rows = 0, pgresult;
+	struct ast_str *sql = ast_str_thread_get(sql_buf, 100);
+	struct ast_str *escapebuf = ast_str_thread_get(escapebuf_buf, 100);
 	char *stringp;
 	char *chunk;
 	char *op;
 	const char *newparam, *newval;
 	struct ast_variable *var = NULL, *prev = NULL;
 
-	if (!table) {
+	if (!tablename) {
 		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
 		return NULL;
 	}
@@ -216,7 +245,7 @@
 		if (pgsqlConn) {
 			PQfinish(pgsqlConn);
 			pgsqlConn = NULL;
-		};
+		}
 		return NULL;
 	}
 
@@ -224,15 +253,14 @@
 	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
 	op = strchr(newparam, ' ') ? "" : " =";
 
-	PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-	if (pgerror) {
+	ESCAPE_STRING(escapebuf, newval);
+	if (pgresult) {
 		ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
 		va_end(ap);
 		return NULL;
 	}
 
-	snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
-			 escapebuf);
+	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, newparam, op, escapebuf->str);
 	while ((newparam = va_arg(ap, const char *))) {
 		newval = va_arg(ap, const char *);
 		if (!strchr(newparam, ' '))
@@ -240,15 +268,14 @@
 		else
 			op = "";
 
-		PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-		if (pgerror) {
+		ESCAPE_STRING(escapebuf, newval);
+		if (pgresult) {
 			ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
 			va_end(ap);
 			return NULL;
 		}
 
-		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
-				 op, escapebuf);
+		ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, escapebuf->str);
 	}
 	va_end(ap);
 
@@ -259,10 +286,10 @@
 		return NULL;
 	}
 
-	if (!(result = PQexec(pgsqlConn, sql))) {
-		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);
+	if (!(result = PQexec(pgsqlConn, sql->str))) {
+		ast_log(LOG_WARNING,
+				"PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
+		ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
 		ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
 		ast_mutex_unlock(&pgsql_lock);
 		return NULL;
@@ -272,8 +299,8 @@
 			&& result_status != PGRES_TUPLES_OK
 			&& result_status != PGRES_NONFATAL_ERROR) {
 			ast_log(LOG_WARNING,
-					"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
-			ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
+					"PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
+			ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
 			ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
 						PQresultErrorMessage(result), PQresStatus(result_status));
 			ast_mutex_unlock(&pgsql_lock);
@@ -281,7 +308,7 @@
 		}
 	}
 
-	ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql);
+	ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql->str);
 
 	if ((num_rows = PQntuples(result)) > 0) {
 		int i = 0;
@@ -318,7 +345,7 @@
 		}
 		ast_free(fieldnames);
 	} else {
-		ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s.\n", table);
+		ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
 	}
 
 	ast_mutex_unlock(&pgsql_lock);
@@ -330,8 +357,9 @@
 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
 {
 	PGresult *result = NULL;
-	int num_rows = 0, pgerror;
-	char sql[256], escapebuf[513];
+	int num_rows = 0, pgresult;
+	struct ast_str *sql = ast_str_thread_get(sql_buf, 100);
+	struct ast_str *escapebuf, ast_str_thread_get(escapebuf_buf, 100);
 	const char *initfield = NULL;
 	char *stringp;
 	char *chunk;
@@ -358,7 +386,7 @@
 		if (pgsqlConn) {
 			PQfinish(pgsqlConn);
 			pgsqlConn = NULL;
-		};
+		}
 		return NULL;
 	}
 
@@ -375,15 +403,14 @@
 	else
 		op = "";
 
-	PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-	if (pgerror) {
+	ESCAPE_STRING(escapebuf, newval);
+	if (pgresult) {
 		ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
 		va_end(ap);
 		return NULL;
 	}
 
-	snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
-			 escapebuf);
+	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, escapebuf->str);
 	while ((newparam = va_arg(ap, const char *))) {
 		newval = va_arg(ap, const char *);
 		if (!strchr(newparam, ' '))
@@ -391,19 +418,18 @@
 		else
 			op = "";
 
-		PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-		if (pgerror) {
+		ESCAPE_STRING(escapebuf, newval);
+		if (pgresult) {
 			ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
 			va_end(ap);
 			return NULL;
 		}
 
-		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
-				 op, escapebuf);
+		ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, escapebuf->str);
 	}
 
 	if (initfield) {
-		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
+		ast_str_append(&sql, 0, " ORDER BY %s", initfield);
 	}
 
 	va_end(ap);
@@ -415,10 +441,10 @@
 		return NULL;
 	}
 
-	if (!(result = PQexec(pgsqlConn, sql))) {
-		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);
+	if (!(result = PQexec(pgsqlConn, sql->str))) {
+		ast_log(LOG_WARNING,
+				"PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
+		ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
 		ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
 		ast_mutex_unlock(&pgsql_lock);
 		return NULL;
@@ -428,8 +454,8 @@
 			&& result_status != PGRES_TUPLES_OK
 			&& result_status != PGRES_NONFATAL_ERROR) {
 			ast_log(LOG_WARNING,
-					"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
-			ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
+					"PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
+			ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
 			ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
 						PQresultErrorMessage(result), PQresStatus(result_status));
 			ast_mutex_unlock(&pgsql_lock);
@@ -437,7 +463,7 @@
 		}
 	}
 
-	ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql);
+	ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql->str);
 
 	if ((num_rows = PQntuples(result)) > 0) {
 		int numFields = PQnfields(result);
@@ -490,22 +516,20 @@
 						const char *lookup, va_list ap)
 {
 	PGresult *result = NULL;
-	int numrows = 0, pgerror;
-	char escapebuf[513];
+	int numrows = 0, pgresult;
 	const char *newparam, *newval;
-	struct ast_str *sql = ast_str_create(100);
+	struct ast_str *sql = ast_str_thread_get(sql_buf, 100);
+	struct ast_str *escapebuf = ast_str_thread_get(escapebuf_buf, 100);
 	struct tables *table;
 	struct columns *column = NULL;
 
 	if (!tablename) {
 		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
-		ast_free(sql);
 		return -1;
 	}
 
 	if (!(table = find_table(tablename))) {
 		ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
-		ast_free(sql);
 		return -1;
 	}
 
@@ -518,9 +542,8 @@
 		if (pgsqlConn) {
 			PQfinish(pgsqlConn);
 			pgsqlConn = NULL;
-		};
-		ast_mutex_unlock(&table->lock);
-		ast_free(sql);
+		}
+		release_table(table);
 		return -1;
 	}
 
@@ -533,20 +556,18 @@
 
 	if (!column) {
 		ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
-		ast_mutex_unlock(&table->lock);
-		ast_free(sql);
+		release_table(table);
 		return -1;
 	}
 
 	/* Create the first part of the query using the first parameter/value pairs we just extracted
 	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
 
-	PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-	if (pgerror) {
+	ESCAPE_STRING(escapebuf, newval);
+	if (pgresult) {
 		ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
 		va_end(ap);
-		ast_mutex_unlock(&table->lock);
-		ast_free(sql);
+		release_table(table);
 		return -1;
 	}
 	ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, escapebuf);
@@ -554,37 +575,28 @@
 	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, column)) {
 			ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
 			continue;
 		}
 
-		PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-		if (pgerror) {
+		ESCAPE_STRING(escapebuf, newval);
+		if (pgresult) {
 			ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
 			va_end(ap);
-			ast_mutex_unlock(&table->lock);
-			ast_free(sql);
+			release_table(table);
 			return -1;
 		}
 
 		ast_str_append(&sql, 0, ", %s = '%s'", newparam, escapebuf);
 	}
 	va_end(ap);
-	ast_mutex_unlock(&table->lock);
-
-	PQescapeStringConn(pgsqlConn, escapebuf, lookup, (sizeof(escapebuf) - 1) / 2, &pgerror);
-	if (pgerror) {
+	release_table(table);
+
+	ESCAPE_STRING(escapebuf, lookup);
+	if (pgresult) {
 		ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
 		va_end(ap);
-		ast_free(sql);
 		return -1;
 	}
 
@@ -596,7 +608,6 @@
 	ast_mutex_lock(&pgsql_lock);
 	if (!pgsql_reconnect(database)) {
 		ast_mutex_unlock(&pgsql_lock);
-		ast_free(sql);
 		return -1;
 	}
 
@@ -642,22 +653,152 @@
 	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;
+	struct columns *column = NULL;
+
+	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 ");
+	ast_set_set(&where, 0, "WHERE");
+
+	va_start(ap);
+	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);
+			va_end(ap);
+			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);
+			va_end(ap);
+			release_table(table);
+			ast_free(sql);
+			return -1;
+		}
+		ast_str_append(&where, 0, "%s %s='%s'", first ? "" : ",", 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);
+		va_end(ap);
+		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(column)) {
+			ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", newparam, tablename, database);
+			continue;
+		}
+

[... 321 lines stripped ...]



More information about the svn-commits mailing list