[asterisk-commits] russell: branch group/addons-merge r204396 - /team/group/addons-merge/addons/
SVN commits to the Asterisk project
asterisk-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 asterisk-commits
mailing list