[asterisk-commits] tilghman: trunk r49030 - in /trunk: configs/ funcs/

asterisk-commits at lists.digium.com asterisk-commits at lists.digium.com
Thu Dec 28 13:13:01 MST 2006


Author: tilghman
Date: Thu Dec 28 14:13:00 2006
New Revision: 49030

URL: http://svn.digium.com/view/asterisk?view=rev&rev=49030
Log:
Integrate functionality tested on svncommunity users back into trunk

Modified:
    trunk/configs/func_odbc.conf.sample
    trunk/funcs/func_odbc.c
    trunk/funcs/func_strings.c

Modified: trunk/configs/func_odbc.conf.sample
URL: http://svn.digium.com/view/asterisk/trunk/configs/func_odbc.conf.sample?view=diff&rev=49030&r1=49029&r2=49030
==============================================================================
--- trunk/configs/func_odbc.conf.sample (original)
+++ trunk/configs/func_odbc.conf.sample Thu Dec 28 14:13:00 2006
@@ -17,25 +17,54 @@
 ; If you have data which may potentially contain single ticks, you may wish
 ; to use the dialplan function SQL_ESC() to escape the data prior to its
 ; inclusion in the SQL statement.
+;
+;
+; The following variables are available in this configuration file:
+;
+; readhandle   A comma-separated list of DSNs (from res_odbc.conf) to use when
+;              executing the readsql statement.  Each DSN is tried, in
+;              succession, until the statement succeeds.  You may specify up to
+;              5 DSNs per function class.  If not specified, it will default to
+;              the value of writehandle or dsn, if specified.
+; writehandle  A comma-separated list of DSNs (from res_odbc.conf) to use when
+;              executing the writesql statement.  The same rules apply as to
+;              readhandle.  "dsn" is a synonym for "writehandle".
+; readsql      The statement to execute when reading from the function class.
+; writesql     The statement to execute when writing to the function class.
+; prefix       Normally, all function classes are prefixed with "ODBC" to keep
+;              them uniquely named.  You may choose to change this prefix, which
+;              may be useful to segregate a collection of certain function
+;              classes from others.
+; escapecommas This option may be used to turn off the default behavior of
+;              escaping commas which occur within a field.  If commas are
+;              escaped (the default behavior), then fields containing commas
+;              will be treated as a single value when assigning to ARRAY() or
+;              HASH().  If commas are not escaped, then values will be separated
+;              at the comma within fields.  Please note that turning this option
+;              off is incompatible with the functionality of HASH().
 
 
 ; ODBC_SQL - Allow an SQL statement to be built entirely in the dialplan
 [SQL]
 dsn=mysql1
-read=${ARG1}
+readsql=${ARG1}
 
 ; ODBC_ANTIGF - A blacklist.
 [ANTIGF]
-dsn=mysql1
-read=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}'
+dsn=mysql1,mysql2   ; Use mysql1 as the primary handle, but fall back to mysql2
+                    ; if mysql1 is down.  Supports up to 5 comma-separated
+                    ; DSNs.  "dsn" may also be specified as "readhandle" and
+                    ; "writehandle", if it is important to separate reads and
+                    ; writes to different databases.
+readsql=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}'
 
 ; ODBC_PRESENCE - Retrieve and update presence
 [PRESENCE]
 dsn=mysql1
-read=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}'
-write=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}'
-;prefix=OFFICE		; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE
-;escapecommas=no	; Normally, commas within a field are escaped such that each
-			; field may be separated into individual variables with ARRAY.
-			; This option turns that behavior off [default=yes].
+readsql=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}'
+writesql=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}'
+;prefix=OFFICE      ; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE
+;escapecommas=no    ; Normally, commas within a field are escaped such that each
+                    ; field may be separated into individual variables with ARRAY.
+                    ; This option turns that behavior off [default=yes].
 

Modified: trunk/funcs/func_odbc.c
URL: http://svn.digium.com/view/asterisk/trunk/funcs/func_odbc.c?view=diff&rev=49030&r1=49029&r2=49030
==============================================================================
--- trunk/funcs/func_odbc.c (original)
+++ trunk/funcs/func_odbc.c Thu Dec 28 14:13:00 2006
@@ -37,6 +37,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
 
 #include "asterisk/module.h"
 #include "asterisk/file.h"
@@ -57,7 +58,8 @@
 
 struct acf_odbc_query {
 	AST_LIST_ENTRY(acf_odbc_query) list;
-	char dsn[30];
+	char readhandle[5][30];
+	char writehandle[5][30];
 	char sql_read[2048];
 	char sql_write[2048];
 	unsigned int flags;
@@ -96,7 +98,7 @@
 	struct odbc_obj *obj;
 	struct acf_odbc_query *query;
 	char *t, buf[2048]="", varname[15];
-	int i;
+	int i, dsn;
 	AST_DECLARE_APP_ARGS(values,
 		AST_APP_ARG(field)[100];
 	);
@@ -119,14 +121,6 @@
 		return -1;
 	}
 
-	obj = ast_odbc_request_obj(query->dsn, 0);
-
-	if (!obj) {
-		ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", query->dsn);
-		AST_LIST_UNLOCK(&queries);
-		return -1;
-	}
-
 	/* Parse our arguments */
 	t = value ? ast_strdupa(value) : "";
 
@@ -169,7 +163,15 @@
 
 	AST_LIST_UNLOCK(&queries);
 
-	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf);
+	for (dsn = 0; dsn < 5; dsn++) {
+		if (!ast_strlen_zero(query->writehandle[dsn])) {
+			obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
+			if (obj)
+				stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, buf);
+		}
+		if (stmt)
+			break;
+	}
 
 	if (stmt) {
 		/* Rows affected */
@@ -195,14 +197,15 @@
 {
 	struct odbc_obj *obj;
 	struct acf_odbc_query *query;
-	char sql[2048] = "", varname[15];
-	int res, x, buflen = 0, escapecommas;
+	char sql[2048] = "", varname[15], colnames[2048] = "";
+	int res, x, buflen = 0, escapecommas, dsn;
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(field)[100];
 	);
 	SQLHSTMT stmt;
 	SQLSMALLINT colcount=0;
 	SQLLEN indicator;
+	SQLSMALLINT collength;
 
 	AST_LIST_LOCK(&queries);
 	AST_LIST_TRAVERSE(&queries, query, list) {
@@ -217,14 +220,6 @@
 		return -1;
 	}
 
-	obj = ast_odbc_request_obj(query->dsn, 0);
-
-	if (!obj) {
-		ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn);
-		AST_LIST_UNLOCK(&queries);
-		return -1;
-	}
-
 	AST_STANDARD_APP_ARGS(args, s);
 	for (x = 0; x < args.argc; x++) {
 		snprintf(varname, sizeof(varname), "ARG%d", x + 1);
@@ -244,10 +239,20 @@
 
 	AST_LIST_UNLOCK(&queries);
 
-	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
+	for (dsn = 0; dsn < 5; dsn++) {
+		if (!ast_strlen_zero(query->writehandle[dsn])) {
+			obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
+			if (obj)
+				stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
+		}
+		if (stmt)
+			break;
+	}
 
 	if (!stmt) {
-		ast_odbc_release_obj(obj);
+		ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql);
+		if (obj)
+			ast_odbc_release_obj(obj);
 		return -1;
 	}
 
@@ -278,8 +283,33 @@
 	}
 
 	for (x = 0; x < colcount; x++) {
-		int i;
-		char coldata[256];
+		int i, namelen;
+		char coldata[256], colname[256];
+
+		res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, NULL, NULL, NULL);
+		if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
+			snprintf(colname, sizeof(colname), "field%d", x);
+		}
+
+		if (!ast_strlen_zero(colnames))
+			strncat(colnames, ",", sizeof(colnames) - 1);
+		namelen = strlen(colnames);
+
+		/* Copy data, encoding '\' and ',' for the argument parser */
+		for (i = 0; i < sizeof(colname); i++) {
+			if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) {
+				colnames[namelen++] = '\\';
+			}
+			colnames[namelen++] = colname[i];
+
+			if (namelen >= sizeof(colnames) - 2) {
+				colnames[namelen >= sizeof(colnames) ? sizeof(colnames) - 1 : namelen] = '\0';
+				break;
+			}
+
+			if (colname[i] == '\0')
+				break;
+		}
 
 		buflen = strlen(buf);
 		res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator);
@@ -314,6 +344,8 @@
 	}
 	/* Trim trailing comma */
 	buf[buflen - 1] = '\0';
+
+	pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
 
 	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
 	ast_odbc_release_obj(obj);
@@ -351,27 +383,68 @@
 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
 {
 	const char *tmp;
+	int i;
 
 	if (!cfg || !catg) {
-		return -1;
+		return EINVAL;
 	}
 
 	*query = ast_calloc(1, sizeof(struct acf_odbc_query));
 	if (! (*query))
-		return -1;
-
-	if ((tmp = ast_variable_retrieve(cfg, catg, "dsn"))) {
-		ast_copy_string((*query)->dsn, tmp, sizeof((*query)->dsn));
+		return ENOMEM;
+
+	if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
+		char *tmp2 = ast_strdupa(tmp);
+		AST_DECLARE_APP_ARGS(write,
+			AST_APP_ARG(dsn)[5];
+		);
+		AST_NONSTANDARD_APP_ARGS(write, tmp2, ',');
+		for (i = 0; i < 5; i++) {
+			if (!ast_strlen_zero(write.dsn[i]))
+				ast_copy_string((*query)->writehandle[i], write.dsn[i], sizeof((*query)->writehandle[i]));
+		}
+	}
+
+	if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
+		char *tmp2 = ast_strdupa(tmp);
+		AST_DECLARE_APP_ARGS(read,
+			AST_APP_ARG(dsn)[5];
+		);
+		AST_NONSTANDARD_APP_ARGS(read, tmp2, ',');
+		for (i = 0; i < 5; i++) {
+			if (!ast_strlen_zero(read.dsn[i]))
+				ast_copy_string((*query)->readhandle[i], read.dsn[i], sizeof((*query)->readhandle[i]));
+		}
 	} else {
-		return -1;
-	}
+		/* If no separate readhandle, then use the writehandle for reading */
+		for (i = 0; i < 5; i++) {
+			if (!ast_strlen_zero((*query)->writehandle[i]))
+				ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
+		}
+ 	}
 
 	if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
+		ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s.  Please use 'readsql' instead.\n", catg);
 		ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
+	} else if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")))
+		ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
+
+	if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) {
+		free(*query);
+		ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
+		return EINVAL;
 	}
 
 	if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
+		ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s.  Please use 'writesql' instead.\n", catg);
 		ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
+	} else if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")))
+		ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
+
+	if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) {
+		free(*query);
+		ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
+		return EINVAL;
 	}
 
 	/* Allow escaping of embedded commas in fields to be turned off */
@@ -384,7 +457,7 @@
 	(*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
 	if (! (*query)->acf) {
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
@@ -396,7 +469,7 @@
 	if (!((*query)->acf->name)) {
 		free((*query)->acf);
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
@@ -405,7 +478,7 @@
 		free((char *)(*query)->acf->name);
 		free((*query)->acf);
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	(*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
@@ -432,15 +505,21 @@
 					"${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
 					"This function may only be set.\nSQL:\n%s\n",
 					(*query)->sql_write);
-	}
-
-	/* Could be out of memory, or could be we have neither sql_read nor sql_write */
+	} else {
+		free((char *)(*query)->acf->syntax);
+		free((char *)(*query)->acf->name);
+		free((*query)->acf);
+		free(*query);
+		ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute.  Ignoring.\n", catg);
+		return EINVAL;
+	}
+
 	if (! ((*query)->acf->desc)) {
 		free((char *)(*query)->acf->syntax);
 		free((char *)(*query)->acf->name);
 		free((*query)->acf);
 		free(*query);
-		return -1;
+		return ENOMEM;
 	}
 
 	if (ast_strlen_zero((*query)->sql_read)) {
@@ -475,7 +554,7 @@
 	return 0;
 }
 
-static int odbc_load_module(void)
+static int load_module(void)
 {
 	int res = 0;
 	struct ast_config *cfg;
@@ -494,10 +573,15 @@
 	     catg;
 	     catg = ast_category_browse(cfg, catg)) {
 		struct acf_odbc_query *query = NULL;
-
-		if (init_acf_query(cfg, catg, &query)) {
-			ast_log(LOG_ERROR, "Out of memory\n");
-			free_acf_query(query);
+		int err;
+
+		if ((err = init_acf_query(cfg, catg, &query))) {
+			if (err == ENOMEM)
+				ast_log(LOG_ERROR, "Out of memory\n");
+			else if (err == EINVAL)
+				ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
+			else
+				ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
 		} else {
 			AST_LIST_INSERT_HEAD(&queries, query, list);
 			ast_custom_function_register(query->acf);
@@ -505,15 +589,16 @@
 	}
 
 	ast_config_destroy(cfg);
-	ast_custom_function_register(&escape_function);
+	res |= ast_custom_function_register(&escape_function);
 
 	AST_LIST_UNLOCK(&queries);
 	return res;
 }
 
-static int odbc_unload_module(void)
+static int unload_module(void)
 {
 	struct acf_odbc_query *query;
+	int res = 0;
 
 	AST_LIST_LOCK(&queries);
 	while (!AST_LIST_EMPTY(&queries)) {
@@ -522,10 +607,11 @@
 		free_acf_query(query);
 	}
 
-	ast_custom_function_unregister(&escape_function);
+	res |= ast_custom_function_unregister(&escape_function);
 
 	/* Allow any threads waiting for this lock to pass (avoids a race) */
 	AST_LIST_UNLOCK(&queries);
+	usleep(1);
 	AST_LIST_LOCK(&queries);
 
 	AST_LIST_UNLOCK(&queries);
@@ -572,16 +658,6 @@
 	return res;
 }
 
-static int unload_module(void)
-{
-	return odbc_unload_module();
-}
-
-static int load_module(void)
-{
-	return odbc_load_module();
-}
-
 /* XXX need to revise usecount - set if query_lock is set */
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",

Modified: trunk/funcs/func_strings.c
URL: http://svn.digium.com/view/asterisk/trunk/funcs/func_strings.c?view=diff&rev=49030&r1=49029&r2=49030
==============================================================================
--- trunk/funcs/func_strings.c (original)
+++ trunk/funcs/func_strings.c Thu Dec 28 14:13:00 2006
@@ -155,6 +155,37 @@
 	.read = regex,
 };
 
+#define HASH_PREFIX	"~HASH~%s~"
+#define HASH_FORMAT	HASH_PREFIX "%s~"
+
+static char *app_clearhash = "ClearHash";
+static char *syn_clearhash = "Clear the keys from a specified hashname";
+static char *desc_clearhash =
+"ClearHash(<hashname>)\n"
+"  Clears all keys out of the specified hashname\n";
+
+/* This function probably should migrate to main/pbx.c, as pbx_builtin_clearvar_prefix() */
+static void clearvar_prefix(struct ast_channel *chan, const char *prefix)
+{
+	struct ast_var_t *var;
+	int len = strlen(prefix);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->varshead, var, entries) {
+		if (strncasecmp(prefix, ast_var_name(var), len) == 0) {
+			AST_LIST_REMOVE_CURRENT(&chan->varshead, entries);
+			free(var);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END
+}
+
+static int exec_clearhash(struct ast_channel *chan, void *data)
+{
+	char prefix[80];
+	snprintf(prefix, sizeof(prefix), HASH_PREFIX, data ? (char *)data : "null");
+	clearvar_prefix(chan, prefix);
+	return 0;
+}
+
 static int array(struct ast_channel *chan, char *cmd, char *var,
 		 const char *value)
 {
@@ -164,12 +195,22 @@
 	AST_DECLARE_APP_ARGS(arg2,
 			     AST_APP_ARG(val)[100];
 	);
-	char *value2;
-	int i;
+	char *origvar = "", *value2, varname[256];
+	int i, ishash = 0;
 
 	value2 = ast_strdupa(value);
 	if (!var || !value2)
 		return -1;
+
+	if (!strcmp(cmd, "HASH")) {
+		const char *var2 = pbx_builtin_getvar_helper(chan, "~ODBCFIELDS~");
+		origvar = var;
+		if (var2)
+			var = ast_strdupa(var2);
+		else
+			return -1;
+		ishash = 1;
+	}
 
 	/* The functions this will generally be used with are SORT and ODBC_*, which
 	 * both return comma-delimited lists.  However, if somebody uses literal lists,
@@ -194,16 +235,138 @@
 			ast_log(LOG_DEBUG, "array set value (%s=%s)\n", arg1.var[i],
 				arg2.val[i]);
 		if (i < arg2.argc) {
-			pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]);
+			if (ishash) {
+				snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
+				pbx_builtin_setvar_helper(chan, varname, arg2.val[i]);
+			} else {
+				pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]);
+			}
 		} else {
 			/* We could unset the variable, by passing a NULL, but due to
 			 * pushvar semantics, that could create some undesired behavior. */
-			pbx_builtin_setvar_helper(chan, arg1.var[i], "");
+			if (ishash) {
+				snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
+				pbx_builtin_setvar_helper(chan, varname, "");
+			} else {
+				pbx_builtin_setvar_helper(chan, arg1.var[i], "");
+			}
 		}
 	}
 
 	return 0;
 }
+
+static int hashkeys_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+	struct ast_var_t *newvar;
+	int plen;
+	char prefix[80];
+	snprintf(prefix, sizeof(prefix), HASH_PREFIX, data);
+	plen = strlen(prefix);
+
+	memset(buf, 0, len);
+	AST_LIST_TRAVERSE(&chan->varshead, newvar, entries) {
+		if (strncasecmp(prefix, ast_var_name(newvar), plen) == 0) {
+			/* Copy everything after the prefix */
+			strncat(buf, ast_var_name(newvar) + plen, len);
+			/* Trim the trailing ~ */
+			buf[strlen(buf) - 1] = ',';
+		}
+	}
+	/* Trim the trailing comma */
+	buf[strlen(buf) - 1] = '\0';
+	return 0;
+}
+
+static int hash_write(struct ast_channel *chan, char *cmd, char *var, const char *value)
+{
+	char varname[256];
+	AST_DECLARE_APP_ARGS(arg,
+		AST_APP_ARG(hashname);
+		AST_APP_ARG(hashkey);
+	);
+
+	if (!strchr(var, '|')) {
+		/* Single argument version */
+		return array(chan, "HASH", var, value);
+	}
+
+	AST_STANDARD_APP_ARGS(arg, var);
+	snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
+	pbx_builtin_setvar_helper(chan, varname, value);
+
+	return 0;
+}
+
+static int hash_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+	char varname[256];
+	const char *varvalue;
+	AST_DECLARE_APP_ARGS(arg,
+		AST_APP_ARG(hashname);
+		AST_APP_ARG(hashkey);
+	);
+
+	AST_STANDARD_APP_ARGS(arg, data);
+	if (arg.argc == 2) {
+		snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
+		varvalue = pbx_builtin_getvar_helper(chan, varname);
+		if (varvalue)
+			ast_copy_string(buf, varvalue, len);
+		else
+			*buf = '\0';
+	} else if (arg.argc == 1) {
+		char colnames[4096];
+		int i;
+		AST_DECLARE_APP_ARGS(arg2,
+			AST_APP_ARG(col)[100];
+		);
+
+		/* Get column names, in no particular order */
+		hashkeys_read(chan, "HASHKEYS", arg.hashname, colnames, sizeof(colnames));
+		pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
+
+		AST_NONSTANDARD_APP_ARGS(arg2, colnames, ',');
+		*buf = '\0';
+
+		/* Now get the corresponding column values, in exactly the same order */
+		for (i = 0; i < arg2.argc; i++) {
+			snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg2.col[i]);
+			varvalue = pbx_builtin_getvar_helper(chan, varname);
+			strncat(buf, varvalue, len);
+			strncat(buf, ",", len);
+		}
+
+		/* Strip trailing comma */
+		buf[strlen(buf) - 1] = '\0';
+	}
+
+	return 0;
+}
+
+static struct ast_custom_function hash_function = {
+	.name = "HASH",
+	.synopsis = "Implementation of a dialplan associative array",
+	.syntax = "HASH(hashname[|hashkey])",
+	.write = hash_write,
+	.read = hash_read,
+	.desc =
+		"In two argument mode, gets and sets values to corresponding keys within a named\n"
+		"associative array.  The single-argument mode will only work when assigned to from\n"
+		"a function defined by func_odbc.so.\n",
+};
+
+static struct ast_custom_function hashkeys_function = {
+	.name = "HASHKEYS",
+	.synopsis = "Retrieve the keys of a HASH()",
+	.syntax = "HASHKEYS(<hashname>)",
+	.read = hashkeys_read,
+	.desc =
+		"Returns a comma-delimited list of the current keys of an associative array\n"
+	   	"defined by the HASH() function.  Note that if you iterate over the keys of\n"
+		"the result, adding keys during iteration will cause the result of the HASHKEYS\n"
+		"function to change.\n",
+};
 
 static struct ast_custom_function array_function = {
 	.name = "ARRAY",
@@ -589,6 +752,9 @@
 	res |= ast_custom_function_unregister(&eval_function);
 	res |= ast_custom_function_unregister(&keypadhash_function);
 	res |= ast_custom_function_unregister(&sprintf_function);
+	res |= ast_custom_function_unregister(&hashkeys_function);
+	res |= ast_custom_function_unregister(&hash_function);
+	res |= ast_unregister_application(app_clearhash);
 
 	return res;
 }
@@ -608,6 +774,9 @@
 	res |= ast_custom_function_register(&eval_function);
 	res |= ast_custom_function_register(&keypadhash_function);
 	res |= ast_custom_function_register(&sprintf_function);
+	res |= ast_custom_function_register(&hashkeys_function);
+	res |= ast_custom_function_register(&hash_function);
+	res |= ast_register_application(app_clearhash, exec_clearhash, syn_clearhash, desc_clearhash);
 
 	return res;
 }



More information about the asterisk-commits mailing list