[svn-commits] russell: branch group/addons-merge r204396 - /team/group/addons-merge/addons/

SVN commits to the Digium repositories svn-commits at lists.digium.com
Tue Jun 30 09:24:32 CDT 2009


Author: russell
Date: Tue Jun 30 09:24:29 2009
New Revision: 204396

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=204396
Log:
Add res_config_mysql, resolving bad compiler warnings in passing ...

It looks like our addons build system wasn't set up with -Wall or something?

Added:
    team/group/addons-merge/addons/res_config_mysql.c   (with props)

Added: team/group/addons-merge/addons/res_config_mysql.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/group/addons-merge/addons/res_config_mysql.c?view=auto&rev=204396
==============================================================================
--- team/group/addons-merge/addons/res_config_mysql.c (added)
+++ team/group/addons-merge/addons/res_config_mysql.c Tue Jun 30 09:24:29 2009
@@ -1,0 +1,1752 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999-2005, Digium, Inc.
+ *
+ * Mark Spencer <markster at digium.com>  - Asterisk Author
+ * Matthew Boehm <mboehm at cytelcom.com> - MySQL RealTime Driver Author
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief MySQL CDR backend
+ */
+
+/*** MODULEINFO
+	<depend>mysqlclient</depend>
+	<defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/stat.h>
+
+#include <mysql/mysql.h>
+#include <mysql/mysql_version.h>
+#include <mysql/errmsg.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/threadstorage.h"
+
+#define RES_CONFIG_MYSQL_CONF "res_mysql.conf"
+#define	READHANDLE	0
+#define	WRITEHANDLE	1
+
+#define ESCAPE_STRING(buf, var) \
+	do { \
+		if ((valsz = strlen(var)) * 2 + 1 > ast_str_size(buf)) { \
+			ast_str_make_space(&(buf), valsz * 2 + 1); \
+		} \
+		mysql_real_escape_string(&dbh->handle, ast_str_buffer(buf), var, valsz); \
+	} while (0)
+
+AST_THREADSTORAGE(sql_buf);
+AST_THREADSTORAGE(sql2_buf);
+AST_THREADSTORAGE(find_buf);
+AST_THREADSTORAGE(scratch_buf);
+AST_THREADSTORAGE(modify_buf);
+AST_THREADSTORAGE(modify2_buf);
+AST_THREADSTORAGE(modify3_buf);
+
+enum requirements { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR };
+
+struct mysql_conn {
+	AST_RWLIST_ENTRY(mysql_conn) list;
+	ast_mutex_t	lock;
+	MYSQL       handle;
+	char        host[50];
+	char        name[50];
+	char        user[50];
+	char        pass[50];
+	char        sock[50];
+	int         port;
+	int         connected;
+	time_t      connect_time;
+	enum requirements requirements;
+	char        unique_name[0];
+};
+
+struct columns {
+	char *name;
+	char *type;
+	char *dflt;
+	char null;
+	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;
+	struct mysql_conn *database;
+	char name[0];
+};
+
+static AST_LIST_HEAD_STATIC(mysql_tables, tables);
+static AST_RWLIST_HEAD_STATIC(databases, mysql_conn);
+
+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);
+static int require_mysql(const char *database, const char *tablename, va_list ap);
+static int internal_require(const char *database, const char *table, ...) attribute_sentinel;
+
+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"),
+};
+
+static struct mysql_conn *find_database(const char *database, int for_write)
+{
+	char *whichdb;
+	const char *ptr;
+	struct mysql_conn *cur;
+
+	if ((ptr = strchr(database, '/'))) {
+		/* Multiple databases encoded within string */
+		if (for_write) {
+			whichdb = ast_strdupa(ptr + 1);
+		} else {
+			whichdb = alloca(ptr - database + 1);
+			strncpy(whichdb, database, ptr - database);
+			whichdb[ptr - database] = '\0';
+		}
+	} else {
+		whichdb = ast_strdupa(database);
+	}
+
+	AST_RWLIST_RDLOCK(&databases);
+	AST_RWLIST_TRAVERSE(&databases, cur, list) {
+		if (!strcmp(cur->unique_name, whichdb)) {
+			ast_mutex_lock(&cur->lock);
+			break;
+		}
+	}
+	AST_RWLIST_UNLOCK(&databases);
+	return cur;
+}
+
+#define release_database(a)	ast_mutex_unlock(&(a)->lock)
+
+static int internal_require(const char *database, const char *table, ...)
+{
+	va_list ap;
+	int res;
+	va_start(ap, table);
+	res = require_mysql(database, table, ap);
+	va_end(ap);
+	return res;
+}
+
+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 *database, const char *tablename)
+{
+	struct columns *column;
+	struct tables *table;
+	struct ast_str *sql = ast_str_thread_get(&find_buf, 30);
+	char *fname, *ftype, *flen, *fdflt, *fnull;
+	struct mysql_conn *dbh;
+	MYSQL_RES *result;
+	MYSQL_ROW row;
+
+	if (!(dbh = find_database(database, 1))) {
+		return NULL;
+	}
+
+	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);
+			release_database(dbh);
+			return table;
+		}
+	}
+
+	/* Not found, scan the table */
+	ast_str_set(&sql, 0, "DESC %s", tablename);
+
+	if (!mysql_reconnect(dbh)) {
+		release_database(dbh);
+		AST_LIST_UNLOCK(&mysql_tables);
+		return NULL;
+	}
+
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_ERROR, "Failed to query database columns: %s\n", mysql_error(&dbh->handle));
+		release_database(dbh);
+		AST_LIST_UNLOCK(&mysql_tables);
+		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");
+		release_database(dbh);
+		AST_LIST_UNLOCK(&mysql_tables);
+		return NULL;
+	}
+	strcpy(table->name, tablename); /* SAFE */
+	table->database = dbh;
+	ast_mutex_init(&table->lock);
+	AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
+
+	if ((result = mysql_store_result(&dbh->handle))) {
+		while ((row = mysql_fetch_row(result))) {
+			fname = row[0];
+			ftype = row[1];
+			fnull = row[2];
+			fdflt = row[4];
+			ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
+
+			if (fdflt == NULL) {
+				fdflt = "";
+			}
+
+			if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + strlen(fdflt) + 3))) {
+				ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", tablename, fname);
+				destroy_table(table);
+				release_database(dbh);
+				AST_LIST_UNLOCK(&mysql_tables);
+				return NULL;
+			}
+
+			if ((flen = strchr(ftype, '('))) {
+				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;
+			column->dflt = (char *)column + sizeof(*column) + strlen(fname) + 1 + strlen(ftype) + 1;
+			strcpy(column->name, fname);
+			strcpy(column->type, ftype);
+			strcpy(column->dflt, fdflt);
+			column->null = (strcmp(fnull, "YES") == 0 ? 1 : 0);
+			AST_LIST_INSERT_TAIL(&table->columns, column, list);
+		}
+		mysql_free_result(result);
+	}
+
+	AST_LIST_INSERT_TAIL(&mysql_tables, table, list);
+	ast_mutex_lock(&table->lock);
+	AST_LIST_UNLOCK(&mysql_tables);
+	release_database(dbh);
+	return table;
+}
+
+static void release_table(struct tables *table)
+{
+	if (table) {
+		ast_mutex_unlock(&table->lock);
+	}
+}
+
+static struct columns *find_column(struct tables *table, const char *colname)
+{
+	struct columns *column;
+
+	AST_LIST_TRAVERSE(&table->columns, column, list) {
+		if (strcmp(column->name, colname) == 0) {
+			break;
+		}
+	}
+
+	return column;
+}
+
+static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap)
+{
+	struct mysql_conn *dbh;
+	MYSQL_RES *result;
+	MYSQL_ROW row;
+	MYSQL_FIELD *fields;
+	int numFields, i, valsz;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
+	struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
+	char *stringp;
+	char *chunk;
+	char *op;
+	const char *newparam, *newval;
+	struct ast_variable *var=NULL, *prev=NULL;
+
+	if (!(dbh = find_database(database, 0))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: %s\n", database);
+		return NULL;
+	}
+
+	if (!table) {
+		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		release_database(dbh);
+		return NULL;
+	}
+
+	/* Get the first parameter and first value in our list of passed paramater/value pairs */
+	newparam = va_arg(ap, const char *);
+	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");
+		release_database(dbh);
+		return NULL;
+	}
+
+	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
+	if (!mysql_reconnect(dbh)) {
+		release_database(dbh);
+		return NULL;
+	}
+
+	/* 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 (!strchr(newparam, ' ')) 
+		op = " ="; 
+	else 
+		op = "";
+
+	ESCAPE_STRING(buf, newval);
+	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
+	while ((newparam = va_arg(ap, const char *))) {
+		newval = va_arg(ap, const char *);
+		if (!strchr(newparam, ' ')) 
+			op = " ="; 
+		else
+			op = "";
+		ESCAPE_STRING(buf, newval);
+		ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
+	}
+	va_end(ap);
+
+	ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
+
+	/* Execution. */
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+		release_database(dbh);
+		return NULL;
+	}
+
+	if ((result = mysql_store_result(&dbh->handle))) {
+		numFields = mysql_num_fields(result);
+		fields = mysql_fetch_fields(result);
+
+		while ((row = mysql_fetch_row(result))) {
+			for (i = 0; i < numFields; i++) {
+				if (ast_strlen_zero(row[i]))
+					continue;
+				for (stringp = ast_strdupa(row[i]), chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
+					if (!chunk || ast_strlen_zero(ast_strip(chunk))) {
+						continue;
+					}
+					if (prev) {
+						if ((prev->next = ast_variable_new(fields[i].name, chunk, ""))) {
+							prev = prev->next;
+						}
+					} else {
+						prev = var = ast_variable_new(fields[i].name, chunk, "");
+					}
+				}
+			}
+		}
+	} else {
+		ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
+	}
+
+	release_database(dbh);
+	mysql_free_result(result);
+
+	return var;
+}
+
+static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap)
+{
+	struct mysql_conn *dbh;
+	MYSQL_RES *result;
+	MYSQL_ROW row;
+	MYSQL_FIELD *fields;
+	int numFields, i, valsz;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
+	struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
+	const char *initfield = NULL;
+	char *stringp;
+	char *chunk;
+	char *op;
+	const char *newparam, *newval;
+	struct ast_variable *var = NULL;
+	struct ast_config *cfg = NULL;
+	struct ast_category *cat = NULL;
+
+	if (!(dbh = find_database(database, 0))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s'\n", database);
+		return NULL;
+	}
+
+	if (!table) {
+		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		release_database(dbh);
+		return NULL;
+	}
+	
+	if (!(cfg = ast_config_new())) {
+		/* If I can't alloc memory at this point, why bother doing anything else? */
+		ast_log(LOG_WARNING, "Out of memory!\n");
+		release_database(dbh);
+		return NULL;
+	}
+
+	/* Get the first parameter and first value in our list of passed paramater/value pairs */
+	newparam = va_arg(ap, const char *);
+	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");
+		ast_config_destroy(cfg);
+		release_database(dbh);
+		return NULL;
+	}
+
+	initfield = ast_strdupa(newparam);
+	if (initfield && (op = strchr(initfield, ' '))) {
+		*op = '\0';
+	}
+
+	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
+	if (!mysql_reconnect(dbh)) {
+		release_database(dbh);
+		ast_config_destroy(cfg);
+		return NULL;
+	}
+
+	/* 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 (!strchr(newparam, ' '))
+		op = " =";
+	else
+		op = "";
+
+	ESCAPE_STRING(buf, newval);
+	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
+	while ((newparam = va_arg(ap, const char *))) {
+		newval = va_arg(ap, const char *);
+		if (!strchr(newparam, ' ')) op = " ="; else op = "";
+		ESCAPE_STRING(buf, newval);
+		ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
+	}
+
+	if (initfield) {
+		ast_str_append(&sql, 0, " ORDER BY %s", initfield);
+	}
+
+	va_end(ap);
+
+	ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
+
+	/* Execution. */
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+		release_database(dbh);
+		ast_config_destroy(cfg);
+		return NULL;
+	}
+
+	if ((result = mysql_store_result(&dbh->handle))) {
+		numFields = mysql_num_fields(result);
+		fields = mysql_fetch_fields(result);
+
+		while ((row = mysql_fetch_row(result))) {
+			var = NULL;
+			cat = ast_category_new("", "", -1);
+			if (!cat) {
+				ast_log(LOG_WARNING, "Out of memory!\n");
+				continue;
+			}
+			for (i = 0; i < numFields; i++) {
+				if (ast_strlen_zero(row[i]))
+					continue;
+				for (stringp = ast_strdupa(row[i]), chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
+					if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
+						if (initfield && !strcmp(initfield, fields[i].name)) {
+							ast_category_rename(cat, chunk);
+						}
+						var = ast_variable_new(fields[i].name, chunk, "");
+						ast_variable_append(cat, var);
+					}
+				}
+			}
+			ast_category_append(cfg, cat);
+		}
+	} else {
+		ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
+	}
+
+	release_database(dbh);
+	mysql_free_result(result);
+
+	return cfg;
+}
+
+static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, va_list ap)
+{
+	struct mysql_conn *dbh;
+	my_ulonglong numrows;
+	int valsz;
+	const char *newparam, *newval;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
+	struct tables *table;
+	struct columns *column = NULL;
+
+	if (!(dbh = find_database(database, 1))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s'.\n", database);
+		return -1;
+	}
+		
+	if (!tablename) {
+		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		release_database(dbh);
+		return -1;
+	}
+
+	if (!(table = find_table(database, tablename))) {
+		ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
+		release_database(dbh);
+		return -1;
+	}
+
+	if (!(column = find_column(table, keyfield))) {
+		ast_log(LOG_ERROR, "MySQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", keyfield, tablename);
+		release_table(table);
+		release_database(dbh);
+		return -1;
+	}
+
+	/* Get the first parameter and first value in our list of passed paramater/value pairs */
+	newparam = va_arg(ap, const char *);
+	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");
+		release_table(table);
+		release_database(dbh);
+		return -1;
+	}
+
+	/* Check that the column exists in the table */
+	if (!(column = find_column(table, newparam))) {
+		ast_log(LOG_ERROR, "MySQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
+		release_table(table);
+		release_database(dbh);
+		return -1;
+	}
+
+	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
+	if (!mysql_reconnect(dbh)) {
+		release_table(table);
+		release_database(dbh);
+		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 */
+
+	ESCAPE_STRING(buf, newval);
+	ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(buf));
+
+	/* If the column length isn't long enough, give a chance to lengthen it. */
+	if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
+		internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
+	}
+
+	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 (!(column = 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;
+		}
+
+		ESCAPE_STRING(buf, newval);
+		ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(buf));
+
+		/* If the column length isn't long enough, give a chance to lengthen it. */
+		if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
+			internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
+		}
+	}
+	va_end(ap);
+
+	ESCAPE_STRING(buf, lookup);
+	ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(buf));
+
+	ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
+
+	/* Execution. */
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+		release_table(table);
+		release_database(dbh);
+		return -1;
+	}
+
+	numrows = mysql_affected_rows(&dbh->handle);
+	release_table(table);
+	release_database(dbh);
+
+	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
+	 * 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.)
+	*/
+
+	return (int)numrows;
+}
+
+static int update2_mysql(const char *database, const char *tablename, va_list ap)
+{
+	struct mysql_conn *dbh;
+	my_ulonglong numrows;
+	int first = 1;
+	const char *newparam, *newval;
+	size_t valsz;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
+	struct ast_str *where = ast_str_thread_get(&sql2_buf, 100);
+	struct tables *table;
+	struct columns *column = NULL;
+
+	if (!tablename) {
+		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		return -1;
+	}
+
+	if (!(dbh = find_database(database, 1))) {
+		ast_log(LOG_ERROR, "Invalid database specified: %s\n", database);
+		return -1;
+	}
+
+	if (!(table = find_table(database, tablename))) {
+		ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
+		release_database(dbh);
+		return -1;
+	}
+
+	if (!sql || !buf || !where) {
+		release_database(dbh);
+		release_table(table);
+		return -1;
+	}
+
+	ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
+	ast_str_set(&where, 0, "WHERE");
+
+	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
+	if (!mysql_reconnect(dbh)) {
+		release_table(table);
+		release_database(dbh);
+		return -1;
+	}
+
+	while ((newparam = va_arg(ap, const char *))) {
+		if (!(column = find_column(table, newparam))) {
+			ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
+			release_table(table);
+			release_database(dbh);
+			return -1;
+		}
+		if (!(newval = va_arg(ap, const char *))) {
+			ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
+			release_table(table);
+			release_database(dbh);
+			return -1;
+		}
+		ESCAPE_STRING(buf, newval);
+		ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, ast_str_buffer(buf));
+		first = 0;
+
+		/* If the column length isn't long enough, give a chance to lengthen it. */
+		if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
+			internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
+		}
+	}
+
+	first = 1;
+	while ((newparam = va_arg(ap, const char *))) {
+		if (!(newval = va_arg(ap, const char *))) {
+			ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
+			release_table(table);
+			release_database(dbh);
+			return -1;
+		}
+
+		/* If the column is not within the table, then skip it */
+		if (!(column = 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;
+		}
+
+		ESCAPE_STRING(buf, newval);
+		ast_str_append(&sql, 0, "%s %s = '%s'", first ? "" : ",", newparam, ast_str_buffer(buf));
+
+		/* If the column length isn't long enough, give a chance to lengthen it. */
+		if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
+			internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
+		}
+	}
+	va_end(ap);
+	release_table(table);
+
+	ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
+
+	ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
+
+	/* Execution. */
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+		release_table(table);
+		release_database(dbh);
+		return -1;
+	}
+
+	numrows = mysql_affected_rows(&dbh->handle);
+	release_database(dbh);
+
+	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
+	 * 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.)
+	*/
+
+	return (int)numrows;
+}
+ 
+static int store_mysql(const char *database, const char *table, va_list ap)
+{
+	struct mysql_conn *dbh;
+	my_ulonglong insertid;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
+	struct ast_str *sql2 = ast_str_thread_get(&sql2_buf, 16);
+	struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
+	int valsz;
+	const char *newparam, *newval;
+
+	if (!(dbh = find_database(database, 1))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s'.\n", database);
+		return -1;
+	}
+
+	if (!table) {
+		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		release_database(dbh);
+		return -1;
+	}
+	/* Get the first parameter and first value in our list of passed paramater/value pairs */
+	newparam = va_arg(ap, const char *);
+	newval = va_arg(ap, const char *);
+	if (!newparam || !newval)  {
+		ast_log(LOG_WARNING, "MySQL RealTime: Realtime storage requires at least 1 parameter and 1 value to search on.\n");
+		release_database(dbh);
+		return -1;
+	}
+	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
+	if (!mysql_reconnect(dbh)) {
+		release_database(dbh);
+		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 */
+	ESCAPE_STRING(buf, newval);
+	ast_str_set(&sql, 0, "INSERT INTO %s (%s", table, newparam);
+	ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
+
+	internal_require(database, table, newparam, RQ_CHAR, valsz, SENTINEL);
+
+	while ((newparam = va_arg(ap, const char *))) {
+		if ((newval = va_arg(ap, const char *))) {
+			ESCAPE_STRING(buf, newval);
+		} else {
+			valsz = 0;
+			ast_str_reset(buf);
+		}
+		if (internal_require(database, table, newparam, RQ_CHAR, valsz, SENTINEL) == 0) {
+			ast_str_append(&sql, 0, ", %s", newparam);
+			ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
+		}
+	}
+	va_end(ap);
+	ast_str_append(&sql, 0, "%s)", ast_str_buffer(sql2));
+	ast_debug(1,"MySQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql));
+
+	/* Execution. */
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+		release_database(dbh);
+		return -1;
+	}
+
+	/*!\note The return value is non-portable and may change in future versions. */
+	insertid = mysql_insert_id(&dbh->handle);
+	release_database(dbh);
+
+	ast_debug(1, "MySQL RealTime: row inserted on table: %s, id: %llu\n", table, insertid);
+
+	/* From http://dev.mysql.com/doc/mysql/en/mysql-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.)
+	*/
+	return (int)insertid;
+}
+
+static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
+{
+	struct mysql_conn *dbh;
+	my_ulonglong numrows;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
+	struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
+	int valsz;
+	const char *newparam, *newval;
+
+	if (!(dbh = find_database(database, 1))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s'.\n", database);
+		return -1;
+	}
+
+	if (!table) {
+		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
+		release_database(dbh);
+		return -1;
+	}
+
+	/* Get the first parameter and first value in our list of passed paramater/value pairs */
+	/* newparam = va_arg(ap, const char *);
+	newval = va_arg(ap, const char *);*/
+	if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup))  {
+		ast_log(LOG_WARNING, "MySQL RealTime: Realtime destroying requires at least 1 parameter and 1 value to search on.\n");
+		release_database(dbh);
+		return -1;
+	}
+
+	/* Must connect to the server before anything else, as the escape function requires the mysql handle. */
+	if (!mysql_reconnect(dbh)) {
+		release_database(dbh);
+		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 */
+	ESCAPE_STRING(buf, lookup);
+	ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, keyfield, ast_str_buffer(buf));
+	while ((newparam = va_arg(ap, const char *))) {
+		newval = va_arg(ap, const char *);
+		ESCAPE_STRING(buf, newval);
+		ast_str_append(&sql, 0, " AND %s = '%s'", newparam, ast_str_buffer(buf));
+	}
+	va_end(ap);
+
+	ast_debug(1, "MySQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
+
+	/* Execution. */
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+		release_database(dbh);
+		return -1;
+	}
+
+	numrows = mysql_affected_rows(&dbh->handle);
+	release_database(dbh);
+
+	ast_debug(1, "MySQL RealTime: Deleted %llu rows on table: %s\n", numrows, table);
+
+	/* From http://dev.mysql.com/doc/mysql/en/mysql-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.)
+	*/
+
+	return (int)numrows;
+}
+ 
+static struct ast_config *config_mysql(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags config_flags, const char *unused, const char *who_asked)
+{
+	struct mysql_conn *dbh;
+	MYSQL_RES *result;
+	MYSQL_ROW row;
+	my_ulonglong num_rows;
+	struct ast_variable *new_v;
+	struct ast_category *cur_cat = NULL;
+	struct ast_str *sql = ast_str_thread_get(&sql_buf, 200);
+	char last[80] = "";
+	int last_cat_metric = 0;
+
+	ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+
+	if (!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n");
+		return NULL;
+	}
+
+	if (!(dbh = find_database(database, 0))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s'\n", database);
+		return NULL;
+	}
+
+	ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s WHERE filename='%s' and commented=1 ORDER BY filename, cat_metric desc, var_metric asc, category, var_name, var_val, id", table, file);
+
+	ast_debug(1, "MySQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
+
+	/* We now have our complete statement; Lets connect to the server and execute it. */
+	if (!mysql_reconnect(dbh)) {
+		return NULL;
+	}
+
+	if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
+		ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
+		ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbh->handle));
+		release_database(dbh);
+		return NULL;
+	}
+
+	if ((result = mysql_store_result(&dbh->handle))) {
+		num_rows = mysql_num_rows(result);
+		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. */
+
+		while ((row = mysql_fetch_row(result))) {
+			if (!strcmp(row[1], "#include")) {
+				if (!ast_config_internal_load(row[2], cfg, config_flags, "", who_asked)) {
+					mysql_free_result(result);
+					release_database(dbh);
+					ast_config_destroy(cfg);
+					return NULL;
+				}
+				continue;
+			}
+
+			if (strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) {
+				if (!(cur_cat = ast_category_new(row[0], "", -1))) {
+					ast_log(LOG_WARNING, "Out of memory!\n");
+					break;
+				}
+				strcpy(last, row[0]);
+				last_cat_metric = atoi(row[3]);
+				ast_category_append(cfg, cur_cat);
+			}
+			new_v = ast_variable_new(row[1], row[2], "");
+			if (cur_cat)
+				ast_variable_append(cur_cat, new_v);
+		}
+	} else {
+		ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file);
+	}
+
+	mysql_free_result(result);
+	release_database(dbh);
+
+	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 modify_mysql(const char *database, const char *tablename, struct columns *column, require_type type, int len)
+{
+	/*!\note Cannot use ANY of the same scratch space as is used in other functions, as this one is interspersed. */
+	struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *escbuf = ast_str_thread_get(&modify2_buf, 100);
+	struct ast_str *typestr = ast_str_thread_get(&modify3_buf, 30);
+	int waschar = strncasecmp(column->type, "char", 4) == 0 ? 1 : 0;
+	int wasvarchar = strncasecmp(column->type, "varchar", 7) == 0 ? 1 : 0;
+	int res = 0;
+	struct mysql_conn *dbh;
+
+	if (!(dbh = find_database(database, 1))) {
+		return -1;
+	}
+
+	do {
+		if (type == RQ_CHAR || waschar || wasvarchar) {
+			if (wasvarchar) {
+				ast_str_set(&typestr, 0, "VARCHAR(%d)", len);
+			} else {
+				ast_str_set(&typestr, 0, "CHAR(%d)", len);
+			}
+		} else if (type == RQ_UINTEGER1) {
+			ast_str_set(&typestr, 0, "tinyint(3) unsigned");
+		} else if (type == RQ_INTEGER1) {
+			ast_str_set(&typestr, 0, "tinyint(4)");
+		} else if (type == RQ_UINTEGER2) {
+			ast_str_set(&typestr, 0, "smallint(5) unsigned");
+		} else if (type == RQ_INTEGER2) {
+			ast_str_set(&typestr, 0, "smallint(6)");
+		} else if (type == RQ_UINTEGER3) {
+			ast_str_set(&typestr, 0, "mediumint(8) unsigned");
+		} else if (type == RQ_INTEGER3) {
+			ast_str_set(&typestr, 0, "mediumint(8)");
+		} else if (type == RQ_UINTEGER4) {
+			ast_str_set(&typestr, 0, "int(10) unsigned");
+		} else if (type == RQ_INTEGER4) {
+			ast_str_set(&typestr, 0, "int(11)");
+		} else if (type == RQ_UINTEGER8) {
+			ast_str_set(&typestr, 0, "bigint(19) unsigned");
+		} else if (type == RQ_INTEGER8) {
+			ast_str_set(&typestr, 0, "bigint(20)");
+		} else if (type == RQ_DATETIME) {
+			ast_str_set(&typestr, 0, "datetime");
+		} else if (type == RQ_DATE) {
+			ast_str_set(&typestr, 0, "date");
+		} else if (type == RQ_FLOAT) {
+			ast_str_set(&typestr, 0, "FLOAT(%d,2)", len);
+		} else {
+			ast_log(LOG_ERROR, "Unknown type (should NEVER happen)\n");
+			res = -1;
+			break;
+		}
+		ast_str_set(&sql, 0, "ALTER TABLE %s MODIFY %s %s", tablename, column->name, ast_str_buffer(typestr));
+		if (!column->null) {
+			ast_str_append(&sql, 0, " NOT NULL");
+		}
+		if (!ast_strlen_zero(column->dflt)) {
+			size_t valsz;
+			ESCAPE_STRING(escbuf, column->dflt);
+			ast_str_append(&sql, 0, " DEFAULT '%s'", ast_str_buffer(escbuf));
+		}
+
+		if (!mysql_reconnect(dbh)) {
+			ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
+			res = -1;
+			break;
+		}
+
+		/* Execution. */
+		if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
+			ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
+			ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
+			res = -1;
+		}
+	} while (0);
+
+	release_database(dbh);
+	return res;
+}
+
+#define PICK_WHICH_ALTER_ACTION(stringtype) \
+	if (table->database->requirements == RQ_WARN) {                                                                       \
+		ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for "            \
+			"the required data length: %d (detected stringtype)\n",                                      \
+			tablename, database, column->name, size);                                                    \
+		res = -1;                                                                                        \
+	} else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {     \
+		table_altered = 1;                                                                               \
+	} else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {   \
+		table_altered = 1;                                                                               \
+	} else {                                                                                             \
+		res = -1;                                                                                        \
+	}
+
+static int require_mysql(const char *database, const char *tablename, va_list ap)
+{
+	struct columns *column;
+	struct tables *table = find_table(database, 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) {
+					if ((size > column->len) && column->len != -1) {
+						if (table->database->requirements == RQ_WARN) {
+							ast_log(LOG_WARNING, "Realtime table %s@%s: Column '%s' should be at least %d long, but is only %d long.\n", database, tablename, column->name, size, column->len);
+							res = -1;
+						} else if (modify_mysql(database, tablename, column, type, size) == 0) {
+							table_altered = 1;
+						} else {
+							res = -1;
+						}
+					}
+				} else if (strcasestr(column->type, "unsigned")) {
+					if (!ast_rq_is_int(type)) {
+						if (table->database->requirements == RQ_WARN) {
+							ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
+								database, tablename, column->name, column->type,
+								type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
+								type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" : "a rather stiff drink");
+							res = -1;
+						} else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
+							table_altered = 1;
+						} else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
+							table_altered = 1;
+						} else {
+							res = -1;
+						}
+					} else if (strncasecmp(column->type, "tinyint", 1) == 0) {
+						if (type != RQ_UINTEGER1) {
+							PICK_WHICH_ALTER_ACTION(unsigned tinyint)
+						}
+					} else if (strncasecmp(column->type, "smallint", 1) == 0) {
+						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
+							PICK_WHICH_ALTER_ACTION(unsigned smallint)
+						}
+					} else if (strncasecmp(column->type, "mediumint", 1) == 0) {
+						if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
+							type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
+							type != RQ_UINTEGER3) {

[... 613 lines stripped ...]



More information about the svn-commits mailing list