[asterisk-addons-commits] tilghman: trunk r638 - in /trunk: configs/ res/

SVN commits to the Asterisk addons project asterisk-addons-commits at lists.digium.com
Fri Jun 6 10:35:16 CDT 2008


Author: tilghman
Date: Fri Jun  6 10:35:16 2008
New Revision: 638

URL: http://svn.digium.com/view/asterisk-addons?view=rev&rev=638
Log:
Add adaptive realtime capabilities to the mysql native driver

Modified:
    trunk/configs/res_mysql.conf.sample
    trunk/res/res_config_mysql.c

Modified: trunk/configs/res_mysql.conf.sample
URL: http://svn.digium.com/view/asterisk-addons/trunk/configs/res_mysql.conf.sample?view=diff&rev=638&r1=637&r2=638
==============================================================================
--- trunk/configs/res_mysql.conf.sample (original)
+++ trunk/configs/res_mysql.conf.sample Fri Jun  6 10:35:16 2008
@@ -6,6 +6,21 @@
 ; to the local host is assumed and dbsock is used instead of TCP/IP
 ; to connect to the server.
 ;
+; In addition to a [general] section, this file may also be configured
+; with separate [read] and [write] sections, which permits separation
+; of read-only queries from queries which alter the database.  This
+; enables the use of master and slave replication servers for
+; scalability.
+;
+; The requirements parameter is available only in Asterisk 1.6.1 and
+; later and must be present in the [general] context.  It specifies
+; the behavior when a column name is required by the system.  The
+; default behavior is "warn" and simply sends a warning to the logger
+; that the column does not exist (or is of the wrong type or precision).
+; The other two possibilities are "createclose", which adds the column
+; with the right type and length, and "createchar", which adds the
+; column as a char type, with the appropriate length to accept the data.
+;
 [general]
 ;dbhost = 127.0.0.1
 ;dbname = asterisk
@@ -13,3 +28,4 @@
 ;dbpass = mypass
 ;dbport = 3306
 ;dbsock = /tmp/mysql.sock
+;requirements=warn ; or createclose or createchar

Modified: trunk/res/res_config_mysql.c
URL: http://svn.digium.com/view/asterisk-addons/trunk/res/res_config_mysql.c?view=diff&rev=638&r1=637&r2=638
==============================================================================
--- trunk/res/res_config_mysql.c (original)
+++ trunk/res/res_config_mysql.c Fri Jun  6 10:35:16 2008
@@ -77,18 +77,130 @@
 	time_t      connect_time;
 } dbread, dbwrite;
 
-static int parse_config(void);
+struct columns {
+	char *name;
+	char *type;
+	int len;
+	AST_LIST_ENTRY(columns) list;
+};
+
+struct tables {
+	ast_mutex_t lock;
+	AST_LIST_HEAD_NOLOCK(mysql_columns, columns) columns;
+	AST_LIST_ENTRY(tables) list;
+	char name[0];
+};
+
+static AST_LIST_HEAD_STATIC(mysql_tables, tables);
+
+static int parse_config(int reload);
 static int mysql_reconnect(struct mysql_conn *conn);
 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn);
+
+enum { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR } requirements;
 
 static struct ast_cli_entry cli_realtime_mysql_status[] = {
 	AST_CLI_DEFINE(handle_cli_realtime_mysql_status, "Shows connection information for the MySQL RealTime driver"),
+	AST_CLI_DEFINE(handle_cli_realtime_mysql_cache, "Shows cached tables within the MySQL realtime driver"),
 };
-/*
-        { "realtime", "mysql", "status", NULL }, realtime_mysql_status,
-        "Shows connection information for the MySQL RealTime driver", cli_realtime_mysql_status_usage, NULL };
-*/
+
+static void destroy_table(struct tables *table)
+{
+	struct columns *column;
+	ast_mutex_lock(&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_free(table);
+}
+
+static struct tables *find_table(const char *tablename)
+{
+	struct columns *column;
+	struct tables *table;
+	struct ast_str *sql = ast_str_create(30);
+	char *fname, *ftype, *flen;
+	MYSQL_RES *result;
+	MYSQL_ROW row;
+
+	AST_LIST_LOCK(&mysql_tables);
+	AST_LIST_TRAVERSE(&mysql_tables, table, list) {
+		if (!strcasecmp(table->name, tablename)) {
+			ast_mutex_lock(&table->lock);
+			AST_LIST_UNLOCK(&mysql_tables);
+			return table;
+		}
+	}
+
+	/* Not found, scan the table */
+	ast_str_set(&sql, 0, "DESC %s", tablename);
+
+	ast_mutex_lock(&dbread.lock);
+	if (!mysql_reconnect(&dbread)) {
+		ast_mutex_unlock(&dbread.lock);
+		AST_LIST_UNLOCK(&mysql_tables);
+		return NULL;
+	}
+
+	if (mysql_real_query(&dbread.handle, sql->str, sql->used)) {
+		ast_log(LOG_ERROR, "Failed to query database columns: %s\n", mysql_error(&dbread.handle));
+		AST_LIST_UNLOCK(&mysql_tables);
+		ast_mutex_unlock(&dbread.lock);
+		return NULL;
+	}
+
+	if (!(table = ast_calloc(1, sizeof(*table) + strlen(tablename) + 1))) {
+		ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
+		AST_LIST_UNLOCK(&mysql_tables);
+		ast_mutex_unlock(&dbread.lock);
+		return NULL;
+	}
+	strcpy(table->name, tablename); /* SAFE */
+	ast_mutex_init(&table->lock);
+	AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
+
+	if ((result = mysql_store_result(&dbread.handle))) {
+		while ((row = mysql_fetch_row(result))) {
+			fname = row[0];
+			ftype = row[1];
+			ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
+
+			if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + 2))) {
+				ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", tablename, fname);
+				destroy_table(table);
+				AST_LIST_UNLOCK(&mysql_tables);
+				ast_mutex_unlock(&dbread.lock);
+				return NULL;
+			}
+
+			if ((flen = strchr(ftype, '('))) {
+				*flen++ = '\0';
+				sscanf(flen, "%d", &column->len);
+			} else {
+				/* Columns like dates, times, and timestamps don't have a length */
+				column->len = -1;
+			}
+
+			column->name = (char *)column + sizeof(*column);
+			column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
+			strcpy(column->name, fname);
+			strcpy(column->type, ftype);
+			AST_LIST_INSERT_TAIL(&table->columns, column, list);
+		}
+		mysql_free_result(result);
+	}
+
+	ast_mutex_unlock(&dbread.lock);
+	AST_LIST_INSERT_TAIL(&mysql_tables, table, list);
+	ast_mutex_lock(&table->lock);
+	AST_LIST_UNLOCK(&mysql_tables);
+	return table;
+}
+
 static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap)
 {
 	MYSQL_RES *result;
@@ -317,16 +429,41 @@
 	return cfg;
 }
 
-static int update_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
+static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, va_list ap)
 {
 	my_ulonglong numrows;
-	char sql[512];
-	char buf[511]; /* Keep this size uneven as it is 2n+1. */
 	int valsz;
 	const char *newparam, *newval;
-
-	if (!table) {
+	struct ast_str *sql = ast_str_create(100), *buf = ast_str_create(513);
+	struct tables *table;
+	struct columns *column = NULL;
+
+	if (!tablename) {
 		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		ast_free(sql);
+		ast_free(buf);
+		return -1;
+	}
+
+	if (!(table = find_table(tablename))) {
+		ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
+		ast_free(sql);
+		ast_free(buf);
+		return -1;
+	}
+
+	/* Check that the column exists in the table */
+	AST_LIST_TRAVERSE(&table->columns, column, list) {
+		if (strcmp(column->name, keyfield) == 0) {
+			break;
+		}
+	}
+
+	if (!column) {
+		ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", keyfield, tablename);
+		ast_mutex_unlock(&table->lock);
+		ast_free(sql);
+		ast_free(buf);
 		return -1;
 	}
 
@@ -335,51 +472,96 @@
 	newval = va_arg(ap, const char *);
 	if (!newparam || !newval)  {
 		ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
-               return -1;
+		ast_mutex_unlock(&table->lock);
+		ast_free(sql);
+		ast_free(buf);
+		return -1;
+	}
+
+	/* Check that the column exists in the table */
+	AST_LIST_TRAVERSE(&table->columns, column, list) {
+		if (strcmp(column->name, newparam) == 0) {
+			break;
+		}
+	}
+
+	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);
+		ast_free(buf);
+		return -1;
 	}
 
 	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
 	ast_mutex_lock(&dbwrite.lock);
 	if (!mysql_reconnect(&dbwrite)) {
+		ast_mutex_unlock(&table->lock);
 		ast_mutex_unlock(&dbwrite.lock);
+		ast_free(sql);
+		ast_free(buf);
 		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 */
 
-	if ((valsz = strlen (newval)) * 2 + 1 > sizeof(buf))
-		valsz = (sizeof(buf) - 1) / 2;
-	mysql_real_escape_string(&dbwrite.handle, buf, newval, valsz);
-	snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, buf);
-	while((newparam = va_arg(ap, const char *))) {
+	if ((valsz = strlen(newval)) * 2 + 1 > buf->len) {
+		ast_str_make_space(&buf, valsz * 2 + 1);
+	}
+	mysql_real_escape_string(&dbwrite.handle, buf->str, newval, valsz);
+	ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, buf->str);
+
+	while ((newparam = va_arg(ap, const char *))) {
 		newval = va_arg(ap, const char *);
-		if ((valsz = strlen (newval)) * 2 + 1 > sizeof(buf))
-			valsz = (sizeof(buf) - 1) / 2;
-		mysql_real_escape_string(&dbwrite.handle, buf, newval, valsz);
-		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam, buf);
+
+		/* 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) {
+			ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
+			continue;
+		}
+
+		if ((valsz = strlen(newval)) * 2 + 1 > buf->len) {
+			ast_str_make_space(&buf, valsz * 2 + 1);
+		}
+		mysql_real_escape_string(&dbwrite.handle, buf->str, newval, valsz);
+		ast_str_append(&sql, 0, ", %s = '%s'", newparam, buf->str);
 	}
 	va_end(ap);
-	if ((valsz = strlen (lookup)) * 2 + 1 > sizeof(buf))
-		valsz = (sizeof(buf) - 1) / 2;
-	mysql_real_escape_string(&dbwrite.handle, buf, lookup, valsz);
-	snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield, buf);
-
-	ast_debug(1,"MySQL RealTime: Update SQL: %s\n", sql);
+
+	if ((valsz = strlen(lookup)) * 2 + 1 > buf->len) {
+		ast_str_make_space(&buf, valsz * 2 + 1);
+	}
+	mysql_real_escape_string(&dbwrite.handle, buf->str, lookup, valsz);
+	ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, buf->str);
+
+	ast_debug(1, "MySQL RealTime: Update SQL: %s\n", sql->str);
 
 	/* Execution. */
-	if (mysql_real_query(&dbwrite.handle, sql, strlen(sql))) {
+	if (mysql_real_query(&dbwrite.handle, sql->str, sql->used)) {
 		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
-		ast_debug(1, "MySQL RealTime: Query: %s\n", sql);
+		ast_debug(1, "MySQL RealTime: Query: %s\n", sql->str);
 		ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbwrite.handle));
 		ast_mutex_unlock(&dbwrite.lock);
-		return -1;
-	}
+		ast_mutex_unlock(&table->lock);
+		ast_free(sql);
+		ast_free(buf);
+		return -1;
+	}
+
+	ast_free(sql);
+	ast_free(buf);
 
 	numrows = mysql_affected_rows(&dbwrite.handle);
 	ast_mutex_unlock(&dbwrite.lock);
 
-	ast_debug(1,"MySQL RealTime: Updated %llu rows on table: %s\n", numrows, table);
+	ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
 
 	/* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
 	 * An integer greater than zero indicates the number of rows affected
@@ -578,7 +760,7 @@
 		ast_debug(1, "MySQL RealTime: Found %llu rows.\n", num_rows);
 
 		/* There might exist a better way to access the column names other than counting,
-                   but I believe that would require another loop that we don't need. */
+		 * but I believe that would require another loop that we don't need. */
 
 		while ((row = mysql_fetch_row(result))) {
 			if (!strcmp(row[1], "#include")) {
@@ -615,6 +797,129 @@
 	return cfg;
 }
 
+static int unload_mysql(const char *database, const char *tablename)
+{
+	struct tables *cur;
+	AST_LIST_LOCK(&mysql_tables);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&mysql_tables, cur, list) {
+		if (strcmp(cur->name, tablename) == 0) {
+			AST_LIST_REMOVE_CURRENT(list);
+			destroy_table(cur);
+			break;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END
+	AST_LIST_UNLOCK(&mysql_tables);
+	return cur ? 0 : -1;
+}
+
+static int require_mysql(const char *database, const char *tablename, va_list ap)
+{
+	struct columns *column;
+	struct tables *table = find_table(tablename);
+	char *elm;
+	int type, size, res = 0, table_altered = 0;
+
+	if (!table) {
+		ast_log(LOG_WARNING, "Table %s not found in database.  This table should exist if you're using realtime.\n", tablename);
+		return -1;
+	}
+
+	while ((elm = va_arg(ap, char *))) {
+		type = va_arg(ap, require_type);
+		size = va_arg(ap, int);
+		AST_LIST_TRAVERSE(&table->columns, column, list) {
+			if (strcmp(column->name, elm) == 0) {
+				/* Char can hold anything, as long as it is large enough */
+				if ((strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0 || strcmp(column->type, "bpchar") == 0)) {
+					if ((size > column->len) && column->len != -1) {
+						ast_log(LOG_WARNING, "Column '%s' should be at least %d long, but is only %d long.\n", column->name, size, column->len);
+						res = -1;
+					}
+				} else if (strcasestr(column->type, "int")) {
+					int typesize = (
+						!strncasecmp(column->type, "tiny", 1) ? 3 :
+						!strncasecmp(column->type, "small", 1) ? 5 :
+						!strncasecmp(column->type, "medium", 1) ? 8 :
+						!strncasecmp(column->type, "int", 1) ? 11 :
+						23); /* bigint */
+					/* Integers can hold only other integers */
+					if (type == RQ_INTEGER && (typesize < size)) {
+						ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d (detected only %d)\n", column->name, size, typesize);
+						res = -1;
+					} else if (type != RQ_INTEGER) {
+						ast_log(LOG_WARNING, "Column '%s' is of the incorrect type: (need %s(%d) but saw %s)\n", column->name, type == RQ_CHAR ? "char" : "something else ", size, column->type);
+						res = -1;
+					}
+				} else if (strncmp(column->type, "float", 5) == 0 && type != RQ_INTEGER && type != RQ_FLOAT) {
+					ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
+					res = -1;
+				} else { /* There are other types that no module implements yet */
+					ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
+					res = -1;
+				}
+				break;
+			}
+		}
+
+		if (!column) {
+			if (requirements == RQ_WARN) {
+				ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
+			} else {
+				struct ast_str *sql = ast_str_create(100), *fieldtype = ast_str_create(16);
+
+				if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
+					ast_str_set(&fieldtype, 0, "CHAR(%d)", size);
+				} else if (type == RQ_INTEGER) {
+					ast_str_set(&fieldtype, 0, "%sINT(%d)", size <= 3 ? "TINY" : size <= 5 ? "SMALL" : size <= 8 ? "MEDIUM" : size <= 11 ? "" : "BIG", size);
+				} else if (type == RQ_FLOAT) {
+					ast_str_set(&fieldtype, 0, "FLOAT");
+				} else if (type == RQ_DATE) {
+					ast_str_set(&fieldtype, 0, "DATE");
+				} else if (type == RQ_DATETIME) {
+					ast_str_set(&fieldtype, 0, "DATETIME");
+				} else {
+					ast_free(sql);
+					ast_free(fieldtype);
+					continue;
+				}
+				ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, fieldtype->str);
+
+				ast_mutex_lock(&dbwrite.lock);
+				if (!mysql_reconnect(&dbwrite)) {
+					ast_mutex_unlock(&dbwrite.lock);
+					ast_log(LOG_ERROR, "Unable to add column: %s\n", sql->str);
+					ast_free(sql);
+					ast_free(fieldtype);
+					continue;
+				}
+
+				/* Execution. */
+				if (mysql_real_query(&dbwrite.handle, sql->str, sql->used)) {
+					ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
+					ast_debug(1, "MySQL RealTime: Query: %s\n", sql->str);
+					ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbwrite.handle));
+					ast_mutex_unlock(&dbwrite.lock);
+				} else {
+					table_altered = 1;
+				}
+
+				ast_free(sql);
+				ast_free(fieldtype);
+			}
+		}
+	}
+	ast_mutex_unlock(&table->lock);
+
+	/* If we altered the table, we must refresh the cache */
+	if (table_altered) {
+		unload_mysql(database, tablename);
+		table = find_table(tablename);
+		ast_mutex_unlock(&table->lock);
+	}
+	return res;
+}
+
 static struct ast_config_engine mysql_engine = {
 	.name = "mysql",
 	.load_func = config_mysql,
@@ -622,13 +927,15 @@
 	.realtime_multi_func = realtime_multi_mysql,
 	.store_func = store_mysql,
 	.destroy_func = destroy_mysql,
-	.update_func = update_mysql
+	.update_func = update_mysql,
+	.require_func = require_mysql,
+	.unload_func = unload_mysql,
 };
 
 static int load_module(void)
 {
 	int writeokay = 0;
-	parse_config();
+	parse_config(0);
 
 	ast_mutex_init(&dbread.lock);
 	ast_mutex_init(&dbwrite.lock);
@@ -653,6 +960,8 @@
 
 static int unload_module(void)
 {
+	struct tables *table;
+
 	ast_cli_unregister_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
 	ast_config_engine_deregister(&mysql_engine);
 	if (option_verbose > 1)
@@ -667,6 +976,13 @@
 	mysql_close(&dbwrite.handle);
 	mysql_close(&dbread.handle);
 
+	/* Destroy cached table info */
+	AST_LIST_LOCK(&mysql_tables);
+	while ((table = AST_LIST_REMOVE_HEAD(&mysql_tables, list))) {
+		destroy_table(table);
+	}
+	AST_LIST_UNLOCK(&mysql_tables);
+
 	return 0;
 }
 
@@ -678,7 +994,7 @@
 	ast_mutex_lock(&dbread.lock);
 	mysql_close(&dbwrite.handle);
 	mysql_close(&dbread.handle);
-	parse_config();
+	parse_config(1);
 
 	if (!mysql_reconnect(&dbwrite))
 		ast_log(LOG_WARNING, "MySQL RealTime: Cannot connect write handle: %s\n", mysql_error(&dbwrite.handle));
@@ -720,16 +1036,18 @@
 	return 0;
 }
 
-static int parse_config(void)
+static int parse_config(int reload)
 {
 	struct ast_config *config = NULL;
-	struct ast_flags config_flags = { 0 };
-	const char *catg = "write";
+	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+	const char *catg = "write", *s;
 	int haswriteconfig = 0;
 
 	if (!(config = ast_config_load(RES_CONFIG_MYSQL_CONF, config_flags))) {
 		set_defaults(&dbwrite);
 		set_defaults(&dbread);
+		return 0;
+	} else if (config == CONFIG_STATUS_FILEUNCHANGED) {
 		return 0;
 	}
 
@@ -757,6 +1075,19 @@
 	} else
 		load_mysql_config(config, "read", &dbread);
 
+	if (!(s = ast_variable_retrieve(config, "general", "requirements"))) {
+		ast_log(LOG_WARNING, "MySQL realtime: no requirements setting found, using 'warn' as default.\n");
+		requirements = RQ_WARN;
+	} else if (!strcasecmp(s, "createclose")) {
+		requirements = RQ_CREATECLOSE;
+	} else if (!strcasecmp(s, "createchar")) {
+		requirements = RQ_CREATECHAR;
+	} else if (!strcasecmp(s, "warn")) {
+		requirements = RQ_WARN;
+	} else {
+		ast_log(LOG_WARNING, "MySQL realtime: unrecognized requirements setting '%s', using 'warn'\n", s);
+		requirements = RQ_WARN;
+	}
 	ast_config_destroy(config);
 
 	return 0;
@@ -870,6 +1201,60 @@
 	}
 }
 
+static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct tables *cur;
+	int l, which;
+	char *ret = NULL;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "realtime mysql cache";
+		e->usage =
+			"Usage: realtime mysql cache [<table>]\n"
+			"       Shows table cache for the MySQL RealTime driver\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->argc != 4) {
+			return NULL;
+		}
+		l = strlen(a->word);
+		which = 0;
+		AST_LIST_LOCK(&mysql_tables);
+		AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
+			if (!strncasecmp(a->word, cur->name, l) && ++which > a->n) {
+				ret = ast_strdup(cur->name);
+				break;
+			}
+		}
+		AST_LIST_UNLOCK(&mysql_tables);
+		return ret;
+	}
+
+	if (a->argc == 3) {
+		/* List of tables */
+		AST_LIST_LOCK(&mysql_tables);
+		AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
+			ast_cli(a->fd, "%s\n", cur->name);
+		}
+		AST_LIST_UNLOCK(&mysql_tables);
+	} else if (a->argc == 4) {
+		/* List of columns */
+		if ((cur = find_table(a->argv[3]))) {
+			struct columns *col;
+			ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[3]);
+			ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s\n", "Name", "Type", "Len");
+			AST_LIST_TRAVERSE(&cur->columns, col, list) {
+				ast_cli(a->fd, "%-20.20s %-20.20s %3d\n", col->name, col->type, col->len);
+			}
+			ast_mutex_unlock(&cur->lock);
+		} else {
+			ast_cli(a->fd, "No such table '%s'\n", a->argv[3]);
+		}
+	}
+	return 0;
+}
+
 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	char status[256], status2[100] = "";




More information about the asterisk-addons-commits mailing list