2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999-2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com> - Asterisk Author
7 * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
22 * \brief MySQL CDR backend
26 <depend>mysqlclient</depend>
27 <defaultenabled>no</defaultenabled>
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36 #include <mysql/mysql.h>
37 #include <mysql/mysql_version.h>
38 #include <mysql/errmsg.h>
40 #include "asterisk/channel.h"
41 #include "asterisk/logger.h"
42 #include "asterisk/config.h"
43 #include "asterisk/module.h"
44 #include "asterisk/lock.h"
45 #include "asterisk/options.h"
46 #include "asterisk/cli.h"
47 #include "asterisk/utils.h"
48 #include "asterisk/threadstorage.h"
49 #include "asterisk/strings.h"
51 #define RES_CONFIG_MYSQL_CONF "res_config_mysql.conf"
52 #define RES_CONFIG_MYSQL_CONF_OLD "res_mysql.conf"
56 #define ESCAPE_STRING(buf, var) \
58 struct ast_str *semi = ast_str_thread_get(&scratch2_buf, strlen(var) * 3 + 1); \
59 const char *chunk = var; \
60 ast_str_reset(semi); \
61 for (; *chunk; chunk++) { \
62 if (strchr(";^", *chunk)) { \
63 ast_str_append(&semi, 0, "^%02hhX", *chunk); \
65 ast_str_append(&semi, 0, "%c", *chunk); \
68 if (ast_str_strlen(semi) * 2 + 1 > ast_str_size(buf)) { \
69 ast_str_make_space(&(buf), ast_str_strlen(semi) * 2 + 1); \
71 mysql_real_escape_string(&dbh->handle, ast_str_buffer(buf), ast_str_buffer(semi), ast_str_strlen(semi)); \
74 AST_THREADSTORAGE(sql_buf);
75 AST_THREADSTORAGE(sql2_buf);
76 AST_THREADSTORAGE(find_buf);
77 AST_THREADSTORAGE(scratch_buf);
78 AST_THREADSTORAGE(scratch2_buf);
79 AST_THREADSTORAGE(modify_buf);
80 AST_THREADSTORAGE(modify2_buf);
81 AST_THREADSTORAGE(modify3_buf);
83 enum requirements { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR };
86 AST_RWLIST_ENTRY(mysql_conn) list;
98 enum requirements requirements;
108 AST_LIST_ENTRY(columns) list;
113 AST_LIST_HEAD_NOLOCK(mysql_columns, columns) columns;
114 AST_LIST_ENTRY(tables) list;
115 struct mysql_conn *database;
119 static AST_LIST_HEAD_STATIC(mysql_tables, tables);
120 static AST_RWLIST_HEAD_STATIC(databases, mysql_conn);
122 static int parse_config(int reload);
123 static int mysql_reconnect(struct mysql_conn *conn);
124 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
125 static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
126 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn);
127 static int require_mysql(const char *database, const char *tablename, va_list ap);
128 static int internal_require(const char *database, const char *table, ...) attribute_sentinel;
130 static struct ast_cli_entry cli_realtime_mysql_status[] = {
131 AST_CLI_DEFINE(handle_cli_realtime_mysql_status, "Shows connection information for the MySQL RealTime driver"),
132 AST_CLI_DEFINE(handle_cli_realtime_mysql_cache, "Shows cached tables within the MySQL realtime driver"),
135 static struct mysql_conn *find_database(const char *database, int for_write)
139 struct mysql_conn *cur;
141 if ((ptr = strchr(database, '/'))) {
142 /* Multiple databases encoded within string */
144 whichdb = ast_strdupa(ptr + 1);
146 whichdb = alloca(ptr - database + 1);
147 strncpy(whichdb, database, ptr - database);
148 whichdb[ptr - database] = '\0';
151 whichdb = ast_strdupa(database);
154 AST_RWLIST_RDLOCK(&databases);
155 AST_RWLIST_TRAVERSE(&databases, cur, list) {
156 if (!strcmp(cur->unique_name, whichdb)) {
157 ast_mutex_lock(&cur->lock);
161 AST_RWLIST_UNLOCK(&databases);
165 #define release_database(a) ast_mutex_unlock(&(a)->lock)
167 static int internal_require(const char *database, const char *table, ...)
172 res = require_mysql(database, table, ap);
177 static void destroy_table(struct tables *table)
179 struct columns *column;
180 ast_mutex_lock(&table->lock);
181 while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
184 ast_mutex_unlock(&table->lock);
185 ast_mutex_destroy(&table->lock);
189 static struct tables *find_table(const char *database, const char *tablename)
191 struct columns *column;
192 struct tables *table;
193 struct ast_str *sql = ast_str_thread_get(&find_buf, 30);
194 char *fname, *ftype, *flen, *fdflt, *fnull;
195 struct mysql_conn *dbh;
199 if (!(dbh = find_database(database, 1))) {
203 AST_LIST_LOCK(&mysql_tables);
204 AST_LIST_TRAVERSE(&mysql_tables, table, list) {
205 if (!strcasecmp(table->name, tablename)) {
206 ast_mutex_lock(&table->lock);
207 AST_LIST_UNLOCK(&mysql_tables);
208 release_database(dbh);
213 /* Not found, scan the table */
214 ast_str_set(&sql, 0, "DESC %s", tablename);
216 if (!mysql_reconnect(dbh)) {
217 release_database(dbh);
218 AST_LIST_UNLOCK(&mysql_tables);
222 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
223 ast_log(LOG_ERROR, "Failed to query database '%s', table '%s' columns: %s\n", database, tablename, mysql_error(&dbh->handle));
224 release_database(dbh);
225 AST_LIST_UNLOCK(&mysql_tables);
229 if (!(table = ast_calloc(1, sizeof(*table) + strlen(tablename) + 1))) {
230 ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
231 release_database(dbh);
232 AST_LIST_UNLOCK(&mysql_tables);
235 strcpy(table->name, tablename); /* SAFE */
236 table->database = dbh;
237 ast_mutex_init(&table->lock);
238 AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
240 if ((result = mysql_store_result(&dbh->handle))) {
241 while ((row = mysql_fetch_row(result))) {
246 ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
252 if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + strlen(fdflt) + 3))) {
253 ast_log(LOG_ERROR, "Unable to allocate column element %s for %s\n", fname, tablename);
254 destroy_table(table);
255 release_database(dbh);
256 AST_LIST_UNLOCK(&mysql_tables);
260 if ((flen = strchr(ftype, '('))) {
261 sscanf(flen, "(%30d)", &column->len);
263 /* Columns like dates, times, and timestamps don't have a length */
267 column->name = (char *)column + sizeof(*column);
268 column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
269 column->dflt = (char *)column + sizeof(*column) + strlen(fname) + 1 + strlen(ftype) + 1;
270 strcpy(column->name, fname);
271 strcpy(column->type, ftype);
272 strcpy(column->dflt, fdflt);
273 column->null = (strcmp(fnull, "YES") == 0 ? 1 : 0);
274 AST_LIST_INSERT_TAIL(&table->columns, column, list);
276 mysql_free_result(result);
279 AST_LIST_INSERT_TAIL(&mysql_tables, table, list);
280 ast_mutex_lock(&table->lock);
281 AST_LIST_UNLOCK(&mysql_tables);
282 release_database(dbh);
286 static void release_table(struct tables *table)
289 ast_mutex_unlock(&table->lock);
293 static struct columns *find_column(struct tables *table, const char *colname)
295 struct columns *column;
297 AST_LIST_TRAVERSE(&table->columns, column, list) {
298 if (strcmp(column->name, colname) == 0) {
306 static char *decode_chunk(char *chunk)
309 for (; *chunk; chunk++) {
310 if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
311 sscanf(chunk + 1, "%02hhX", chunk);
312 memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
318 static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap)
320 struct mysql_conn *dbh;
325 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
326 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
330 const char *newparam, *newval;
331 struct ast_variable *var=NULL, *prev=NULL;
333 if (!(dbh = find_database(database, 0))) {
334 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: %s (check res_mysql.conf)\n", database);
339 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
340 release_database(dbh);
344 /* Get the first parameter and first value in our list of passed paramater/value pairs */
345 newparam = va_arg(ap, const char *);
346 newval = va_arg(ap, const char *);
347 if (!newparam || !newval) {
348 ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
349 release_database(dbh);
353 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
354 if (!mysql_reconnect(dbh)) {
355 release_database(dbh);
359 /* Create the first part of the query using the first parameter/value pairs we just extracted
360 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
362 if (!strchr(newparam, ' '))
367 ESCAPE_STRING(buf, newval);
368 ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
369 while ((newparam = va_arg(ap, const char *))) {
370 newval = va_arg(ap, const char *);
371 if (!strchr(newparam, ' '))
375 ESCAPE_STRING(buf, newval);
376 ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
380 ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
383 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
384 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
385 release_database(dbh);
389 if ((result = mysql_store_result(&dbh->handle))) {
390 numFields = mysql_num_fields(result);
391 fields = mysql_fetch_fields(result);
393 while ((row = mysql_fetch_row(result))) {
394 for (i = 0; i < numFields; i++) {
395 /* Encode NULL values separately from blank values, for the Realtime API */
396 if (row[i] == NULL) {
398 } else if (ast_strlen_zero(row[i])) {
401 for (stringp = ast_strdupa(row[i]), chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
403 if ((prev->next = ast_variable_new(fields[i].name, decode_chunk(chunk), ""))) {
407 prev = var = ast_variable_new(fields[i].name, decode_chunk(chunk), "");
413 ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
416 release_database(dbh);
417 mysql_free_result(result);
422 static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap)
424 struct mysql_conn *dbh;
429 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
430 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
431 const char *initfield = NULL;
435 const char *newparam, *newval;
436 struct ast_variable *var = NULL;
437 struct ast_config *cfg = NULL;
438 struct ast_category *cat = NULL;
440 if (!(dbh = find_database(database, 0))) {
441 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
446 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
447 release_database(dbh);
451 if (!(cfg = ast_config_new())) {
452 /* If I can't alloc memory at this point, why bother doing anything else? */
453 ast_log(LOG_WARNING, "Out of memory!\n");
454 release_database(dbh);
458 /* Get the first parameter and first value in our list of passed paramater/value pairs */
459 newparam = va_arg(ap, const char *);
460 newval = va_arg(ap, const char *);
461 if (!newparam || !newval) {
462 ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
463 ast_config_destroy(cfg);
464 release_database(dbh);
468 initfield = ast_strdupa(newparam);
469 if (initfield && (op = strchr(initfield, ' '))) {
473 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
474 if (!mysql_reconnect(dbh)) {
475 release_database(dbh);
476 ast_config_destroy(cfg);
480 /* Create the first part of the query using the first parameter/value pairs we just extracted
481 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
483 if (!strchr(newparam, ' '))
488 ESCAPE_STRING(buf, newval);
489 ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
490 while ((newparam = va_arg(ap, const char *))) {
491 newval = va_arg(ap, const char *);
492 if (!strchr(newparam, ' ')) op = " ="; else op = "";
493 ESCAPE_STRING(buf, newval);
494 ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
498 ast_str_append(&sql, 0, " ORDER BY %s", initfield);
503 ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
506 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
507 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
508 release_database(dbh);
509 ast_config_destroy(cfg);
513 if ((result = mysql_store_result(&dbh->handle))) {
514 numFields = mysql_num_fields(result);
515 fields = mysql_fetch_fields(result);
517 while ((row = mysql_fetch_row(result))) {
519 cat = ast_category_new("", "", -1);
521 ast_log(LOG_WARNING, "Out of memory!\n");
524 for (i = 0; i < numFields; i++) {
525 if (ast_strlen_zero(row[i]))
527 for (stringp = ast_strdupa(row[i]), chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
528 if (chunk && !ast_strlen_zero(decode_chunk(ast_strip(chunk)))) {
529 if (initfield && !strcmp(initfield, fields[i].name)) {
530 ast_category_rename(cat, chunk);
532 var = ast_variable_new(fields[i].name, chunk, "");
533 ast_variable_append(cat, var);
537 ast_category_append(cfg, cat);
540 ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
543 release_database(dbh);
544 mysql_free_result(result);
549 static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, va_list ap)
551 struct mysql_conn *dbh;
552 my_ulonglong numrows;
553 const char *newparam, *newval;
554 struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
555 struct tables *table;
556 struct columns *column = NULL;
558 if (!(dbh = find_database(database, 1))) {
559 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
564 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
565 release_database(dbh);
569 if (!(table = find_table(database, tablename))) {
570 ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
571 release_database(dbh);
575 if (!(column = find_column(table, keyfield))) {
576 ast_log(LOG_ERROR, "MySQL RealTime: Updating on column '%s', but that column does not exist within the table '%s' (db '%s')!\n", keyfield, tablename, database);
577 release_table(table);
578 release_database(dbh);
582 /* Get the first parameter and first value in our list of passed paramater/value pairs */
583 newparam = va_arg(ap, const char *);
584 newval = va_arg(ap, const char *);
585 if (!newparam || !newval) {
586 ast_log(LOG_WARNING, "MySQL RealTime: Realtime update requires at least 1 parameter and 1 value to update.\n");
587 release_table(table);
588 release_database(dbh);
592 /* Check that the column exists in the table */
593 if (!(column = find_column(table, newparam))) {
594 ast_log(LOG_ERROR, "MySQL RealTime: Updating column '%s', but that column does not exist within the table '%s' (first pair MUST exist)!\n", newparam, tablename);
595 release_table(table);
596 release_database(dbh);
600 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
601 if (!mysql_reconnect(dbh)) {
602 release_table(table);
603 release_database(dbh);
607 /* Create the first part of the query using the first parameter/value pairs we just extracted
608 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
610 ESCAPE_STRING(buf, newval);
611 ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(buf));
613 /* If the column length isn't long enough, give a chance to lengthen it. */
614 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
615 internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
618 while ((newparam = va_arg(ap, const char *))) {
619 newval = va_arg(ap, const char *);
621 /* If the column is not within the table, then skip it */
622 if (!(column = find_column(table, newparam))) {
623 ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
627 ESCAPE_STRING(buf, newval);
628 ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(buf));
630 /* If the column length isn't long enough, give a chance to lengthen it. */
631 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
632 internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
637 ESCAPE_STRING(buf, lookup);
638 ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(buf));
640 ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
643 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
644 ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
645 release_table(table);
646 release_database(dbh);
650 numrows = mysql_affected_rows(&dbh->handle);
651 release_table(table);
652 release_database(dbh);
654 ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
656 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
657 * An integer greater than zero indicates the number of rows affected
658 * Zero indicates that no records were updated
659 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
665 static int update2_mysql(const char *database, const char *tablename, va_list ap)
667 struct mysql_conn *dbh;
668 my_ulonglong numrows;
670 const char *newparam, *newval;
671 struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
672 struct ast_str *where = ast_str_thread_get(&sql2_buf, 100);
673 struct tables *table;
674 struct columns *column = NULL;
677 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
681 if (!(dbh = find_database(database, 1))) {
682 ast_log(LOG_ERROR, "Invalid database specified: %s\n", database);
686 if (!(table = find_table(database, tablename))) {
687 ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
688 release_database(dbh);
692 if (!sql || !buf || !where) {
693 release_database(dbh);
694 release_table(table);
698 ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
699 ast_str_set(&where, 0, "WHERE");
701 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
702 if (!mysql_reconnect(dbh)) {
703 release_table(table);
704 release_database(dbh);
708 while ((newparam = va_arg(ap, const char *))) {
709 if (!(column = find_column(table, newparam))) {
710 ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
711 release_table(table);
712 release_database(dbh);
715 if (!(newval = va_arg(ap, const char *))) {
716 ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
717 release_table(table);
718 release_database(dbh);
721 ESCAPE_STRING(buf, newval);
722 ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, ast_str_buffer(buf));
725 /* If the column length isn't long enough, give a chance to lengthen it. */
726 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
727 internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
732 while ((newparam = va_arg(ap, const char *))) {
733 if (!(newval = va_arg(ap, const char *))) {
734 ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
735 release_table(table);
736 release_database(dbh);
740 /* If the column is not within the table, then skip it */
741 if (!(column = find_column(table, newparam))) {
742 ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
746 ESCAPE_STRING(buf, newval);
747 ast_str_append(&sql, 0, "%s %s = '%s'", first ? "" : ",", newparam, ast_str_buffer(buf));
750 /* If the column length isn't long enough, give a chance to lengthen it. */
751 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
752 internal_require(database, tablename, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
756 release_table(table);
758 ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
760 ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
763 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
764 ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
765 release_table(table);
766 release_database(dbh);
770 numrows = mysql_affected_rows(&dbh->handle);
771 release_database(dbh);
773 ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
775 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
776 * An integer greater than zero indicates the number of rows affected
777 * Zero indicates that no records were updated
778 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
784 static int store_mysql(const char *database, const char *table, va_list ap)
786 struct mysql_conn *dbh;
787 my_ulonglong insertid;
788 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
789 struct ast_str *sql2 = ast_str_thread_get(&sql2_buf, 16);
790 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
791 const char *newparam, *newval;
793 if (!(dbh = find_database(database, 1))) {
794 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
799 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
800 release_database(dbh);
803 /* Get the first parameter and first value in our list of passed paramater/value pairs */
804 newparam = va_arg(ap, const char *);
805 newval = va_arg(ap, const char *);
806 if (!newparam || !newval) {
807 ast_log(LOG_WARNING, "MySQL RealTime: Realtime storage requires at least 1 parameter and 1 value to search on.\n");
808 release_database(dbh);
811 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
812 if (!mysql_reconnect(dbh)) {
813 release_database(dbh);
816 /* Create the first part of the query using the first parameter/value pairs we just extracted
817 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
818 ESCAPE_STRING(buf, newval);
819 ast_str_set(&sql, 0, "INSERT INTO %s (%s", table, newparam);
820 ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
822 internal_require(database, table, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL);
824 while ((newparam = va_arg(ap, const char *))) {
825 if ((newval = va_arg(ap, const char *))) {
826 ESCAPE_STRING(buf, newval);
830 if (internal_require(database, table, newparam, RQ_CHAR, ast_str_strlen(buf), SENTINEL) == 0) {
831 ast_str_append(&sql, 0, ", %s", newparam);
832 ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
836 ast_str_append(&sql, 0, "%s)", ast_str_buffer(sql2));
837 ast_debug(1,"MySQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql));
840 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
841 ast_log(LOG_WARNING, "MySQL RealTime: Failed to insert into database: %s\n", mysql_error(&dbh->handle));
842 release_database(dbh);
846 /*!\note The return value is non-portable and may change in future versions. */
847 insertid = mysql_insert_id(&dbh->handle);
848 release_database(dbh);
850 ast_debug(1, "MySQL RealTime: row inserted on table: %s, id: %llu\n", table, insertid);
852 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
853 * An integer greater than zero indicates the number of rows affected
854 * Zero indicates that no records were updated
855 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
857 return (int)insertid;
860 static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
862 struct mysql_conn *dbh;
863 my_ulonglong numrows;
864 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
865 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
866 const char *newparam, *newval;
868 if (!(dbh = find_database(database, 1))) {
869 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
874 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
875 release_database(dbh);
879 /* Get the first parameter and first value in our list of passed paramater/value pairs */
880 /* newparam = va_arg(ap, const char *);
881 newval = va_arg(ap, const char *);*/
882 if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup)) {
883 ast_log(LOG_WARNING, "MySQL RealTime: Realtime destroying requires at least 1 parameter and 1 value to search on.\n");
884 release_database(dbh);
888 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
889 if (!mysql_reconnect(dbh)) {
890 release_database(dbh);
894 /* Create the first part of the query using the first parameter/value pairs we just extracted
895 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
896 ESCAPE_STRING(buf, lookup);
897 ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, keyfield, ast_str_buffer(buf));
898 while ((newparam = va_arg(ap, const char *))) {
899 newval = va_arg(ap, const char *);
900 ESCAPE_STRING(buf, newval);
901 ast_str_append(&sql, 0, " AND %s = '%s'", newparam, ast_str_buffer(buf));
905 ast_debug(1, "MySQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
908 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
909 ast_log(LOG_WARNING, "MySQL RealTime: Failed to delete from database: %s\n", mysql_error(&dbh->handle));
910 release_database(dbh);
914 numrows = mysql_affected_rows(&dbh->handle);
915 release_database(dbh);
917 ast_debug(1, "MySQL RealTime: Deleted %llu rows on table: %s\n", numrows, table);
919 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
920 * An integer greater than zero indicates the number of rows affected
921 * Zero indicates that no records were updated
922 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
928 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)
930 struct mysql_conn *dbh;
933 my_ulonglong num_rows;
934 struct ast_variable *new_v;
935 struct ast_category *cur_cat = NULL;
936 struct ast_str *sql = ast_str_thread_get(&sql_buf, 200);
938 int last_cat_metric = 0;
940 ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
942 if (!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) {
943 ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n");
947 if (!(dbh = find_database(database, 0))) {
948 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
952 ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s WHERE filename='%s' and commented=0 ORDER BY filename, cat_metric desc, var_metric asc, category, var_name, var_val, id", table, file);
954 ast_debug(1, "MySQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
956 /* We now have our complete statement; Lets connect to the server and execute it. */
957 if (!mysql_reconnect(dbh)) {
961 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
962 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
963 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
964 ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbh->handle));
965 release_database(dbh);
969 if ((result = mysql_store_result(&dbh->handle))) {
970 num_rows = mysql_num_rows(result);
971 ast_debug(1, "MySQL RealTime: Found %llu rows.\n", num_rows);
973 /* There might exist a better way to access the column names other than counting,
974 * but I believe that would require another loop that we don't need. */
976 while ((row = mysql_fetch_row(result))) {
977 if (!strcmp(row[1], "#include")) {
978 if (!ast_config_internal_load(row[2], cfg, config_flags, "", who_asked)) {
979 mysql_free_result(result);
980 release_database(dbh);
981 ast_config_destroy(cfg);
987 if (strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) {
988 if (!(cur_cat = ast_category_new(row[0], "", -1))) {
989 ast_log(LOG_WARNING, "Out of memory!\n");
992 strcpy(last, row[0]);
993 last_cat_metric = atoi(row[3]);
994 ast_category_append(cfg, cur_cat);
996 new_v = ast_variable_new(row[1], row[2], "");
998 ast_variable_append(cur_cat, new_v);
1001 ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file);
1004 mysql_free_result(result);
1005 release_database(dbh);
1010 static int unload_mysql(const char *database, const char *tablename)
1013 AST_LIST_LOCK(&mysql_tables);
1014 AST_LIST_TRAVERSE_SAFE_BEGIN(&mysql_tables, cur, list) {
1015 if (strcmp(cur->name, tablename) == 0) {
1016 AST_LIST_REMOVE_CURRENT(list);
1021 AST_LIST_TRAVERSE_SAFE_END
1022 AST_LIST_UNLOCK(&mysql_tables);
1023 return cur ? 0 : -1;
1026 static int modify_mysql(const char *database, const char *tablename, struct columns *column, require_type type, int len)
1028 /*!\note Cannot use ANY of the same scratch space as is used in other functions, as this one is interspersed. */
1029 struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *escbuf = ast_str_thread_get(&modify2_buf, 100);
1030 struct ast_str *typestr = ast_str_thread_get(&modify3_buf, 30);
1031 int waschar = strncasecmp(column->type, "char", 4) == 0 ? 1 : 0;
1032 int wasvarchar = strncasecmp(column->type, "varchar", 7) == 0 ? 1 : 0;
1034 struct mysql_conn *dbh;
1036 if (!(dbh = find_database(database, 1))) {
1041 if (type == RQ_CHAR || waschar || wasvarchar) {
1043 ast_str_set(&typestr, 0, "VARCHAR(%d)", len);
1045 ast_str_set(&typestr, 0, "CHAR(%d)", len);
1047 } else if (type == RQ_UINTEGER1) {
1048 ast_str_set(&typestr, 0, "tinyint(3) unsigned");
1049 } else if (type == RQ_INTEGER1) {
1050 ast_str_set(&typestr, 0, "tinyint(4)");
1051 } else if (type == RQ_UINTEGER2) {
1052 ast_str_set(&typestr, 0, "smallint(5) unsigned");
1053 } else if (type == RQ_INTEGER2) {
1054 ast_str_set(&typestr, 0, "smallint(6)");
1055 } else if (type == RQ_UINTEGER3) {
1056 ast_str_set(&typestr, 0, "mediumint(8) unsigned");
1057 } else if (type == RQ_INTEGER3) {
1058 ast_str_set(&typestr, 0, "mediumint(8)");
1059 } else if (type == RQ_UINTEGER4) {
1060 ast_str_set(&typestr, 0, "int(10) unsigned");
1061 } else if (type == RQ_INTEGER4) {
1062 ast_str_set(&typestr, 0, "int(11)");
1063 } else if (type == RQ_UINTEGER8) {
1064 ast_str_set(&typestr, 0, "bigint(19) unsigned");
1065 } else if (type == RQ_INTEGER8) {
1066 ast_str_set(&typestr, 0, "bigint(20)");
1067 } else if (type == RQ_DATETIME) {
1068 ast_str_set(&typestr, 0, "datetime");
1069 } else if (type == RQ_DATE) {
1070 ast_str_set(&typestr, 0, "date");
1071 } else if (type == RQ_FLOAT) {
1072 ast_str_set(&typestr, 0, "FLOAT(%d,2)", len);
1074 ast_log(LOG_ERROR, "Unknown type (should NEVER happen)\n");
1078 ast_str_set(&sql, 0, "ALTER TABLE %s MODIFY %s %s", tablename, column->name, ast_str_buffer(typestr));
1079 if (!column->null) {
1080 ast_str_append(&sql, 0, " NOT NULL");
1082 if (!ast_strlen_zero(column->dflt)) {
1083 ESCAPE_STRING(escbuf, column->dflt);
1084 ast_str_append(&sql, 0, " DEFAULT '%s'", ast_str_buffer(escbuf));
1087 if (!mysql_reconnect(dbh)) {
1088 ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1094 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
1095 ast_log(LOG_WARNING, "MySQL RealTime: Failed to modify database: %s\n", mysql_error(&dbh->handle));
1096 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
1101 release_database(dbh);
1105 #define PICK_WHICH_ALTER_ACTION(stringtype) \
1106 if (table->database->requirements == RQ_WARN) { \
1107 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
1108 "the required data length: %d (detected stringtype)\n", \
1109 tablename, database, column->name, size); \
1111 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) { \
1112 table_altered = 1; \
1113 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) { \
1114 table_altered = 1; \
1119 static int require_mysql(const char *database, const char *tablename, va_list ap)
1121 struct columns *column;
1122 struct tables *table = find_table(database, tablename);
1124 int type, size, res = 0, table_altered = 0;
1127 ast_log(LOG_WARNING, "Table %s not found in database. This table should exist if you're using realtime.\n", tablename);
1131 while ((elm = va_arg(ap, char *))) {
1132 type = va_arg(ap, require_type);
1133 size = va_arg(ap, int);
1134 AST_LIST_TRAVERSE(&table->columns, column, list) {
1135 if (strcmp(column->name, elm) == 0) {
1136 /* Char can hold anything, as long as it is large enough */
1137 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
1138 if ((size > column->len) && column->len != -1) {
1139 if (table->database->requirements == RQ_WARN) {
1140 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);
1142 } else if (modify_mysql(database, tablename, column, type, size) == 0) {
1148 } else if (strcasestr(column->type, "unsigned")) {
1149 if (!ast_rq_is_int(type)) {
1150 if (table->database->requirements == RQ_WARN) {
1151 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
1152 database, tablename, column->name, column->type,
1153 type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
1154 type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" : "a rather stiff drink");
1156 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1158 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1163 } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
1164 if (type != RQ_UINTEGER1) {
1165 PICK_WHICH_ALTER_ACTION(unsigned tinyint)
1167 } else if (strncasecmp(column->type, "smallint", 1) == 0) {
1168 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
1169 PICK_WHICH_ALTER_ACTION(unsigned smallint)
1171 } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
1172 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1173 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1174 type != RQ_UINTEGER3) {
1175 PICK_WHICH_ALTER_ACTION(unsigned mediumint)
1177 } else if (strncasecmp(column->type, "int", 1) == 0) {
1178 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1179 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1180 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1181 type != RQ_UINTEGER4) {
1182 PICK_WHICH_ALTER_ACTION(unsigned int)
1184 } else if (strncasecmp(column->type, "bigint", 1) == 0) {
1185 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1186 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1187 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1188 type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1189 type != RQ_UINTEGER8) {
1190 PICK_WHICH_ALTER_ACTION(unsigned bigint)
1193 } else if (strcasestr(column->type, "int")) {
1194 if (!ast_rq_is_int(type)) {
1195 if (table->database->requirements == RQ_WARN) {
1196 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
1197 database, tablename, column->name, column->type,
1198 type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
1199 type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" :
1200 "to get a life, rather than writing silly error messages");
1202 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1204 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1209 } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
1210 if (type != RQ_INTEGER1) {
1211 PICK_WHICH_ALTER_ACTION(tinyint)
1213 } else if (strncasecmp(column->type, "smallint", 1) == 0) {
1214 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
1215 PICK_WHICH_ALTER_ACTION(smallint)
1217 } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
1218 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1219 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1220 type != RQ_INTEGER3) {
1221 PICK_WHICH_ALTER_ACTION(mediumint)
1223 } else if (strncasecmp(column->type, "int", 1) == 0) {
1224 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1225 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1226 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1227 type != RQ_INTEGER4) {
1228 PICK_WHICH_ALTER_ACTION(int)
1230 } else if (strncasecmp(column->type, "bigint", 1) == 0) {
1231 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1232 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1233 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1234 type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1235 type != RQ_INTEGER8) {
1236 PICK_WHICH_ALTER_ACTION(bigint)
1239 } else if (strncmp(column->type, "float", 5) == 0 && !ast_rq_is_int(type) && type != RQ_FLOAT) {
1240 if (table->database->requirements == RQ_WARN) {
1241 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1243 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1245 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1250 } else if ((strncmp(column->type, "datetime", 8) == 0 || strncmp(column->type, "timestamp", 9) == 0) && type != RQ_DATETIME) {
1251 if (table->database->requirements == RQ_WARN) {
1252 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1254 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1256 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1261 } else if ((strncmp(column->type, "date", 4) == 0) && type != RQ_DATE) {
1262 if (table->database->requirements == RQ_WARN) {
1263 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1265 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1267 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1272 } else { /* Other, possibly unsupported types? */
1273 if (table->database->requirements == RQ_WARN) {
1274 ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
1276 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1278 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1288 if (table->database->requirements == RQ_WARN) {
1289 ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
1291 struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *fieldtype = ast_str_thread_get(&modify3_buf, 16);
1293 if (table->database->requirements == RQ_CREATECHAR || type == RQ_CHAR) {
1294 ast_str_set(&fieldtype, 0, "CHAR(%d)", size);
1295 } else if (type == RQ_UINTEGER1 || type == RQ_UINTEGER2 || type == RQ_UINTEGER3 || type == RQ_UINTEGER4 || type == RQ_UINTEGER8) {
1296 if (type == RQ_UINTEGER1) {
1297 ast_str_set(&fieldtype, 0, "TINYINT(3) UNSIGNED");
1298 } else if (type == RQ_UINTEGER2) {
1299 ast_str_set(&fieldtype, 0, "SMALLINT(5) UNSIGNED");
1300 } else if (type == RQ_UINTEGER3) {
1301 ast_str_set(&fieldtype, 0, "MEDIUMINT(8) UNSIGNED");
1302 } else if (type == RQ_UINTEGER4) {
1303 ast_str_set(&fieldtype, 0, "INT(10) UNSIGNED");
1304 } else if (type == RQ_UINTEGER8) {
1305 ast_str_set(&fieldtype, 0, "BIGINT(20) UNSIGNED");
1307 ast_log(LOG_WARNING, "Somebody should check this code for a rather large bug... it's about to squash Tokyo.\n");
1310 } else if (ast_rq_is_int(type)) {
1311 if (type == RQ_INTEGER1) {
1312 ast_str_set(&fieldtype, 0, "TINYINT(3)");
1313 } else if (type == RQ_INTEGER2) {
1314 ast_str_set(&fieldtype, 0, "SMALLINT(5)");
1315 } else if (type == RQ_INTEGER3) {
1316 ast_str_set(&fieldtype, 0, "MEDIUMINT(8)");
1317 } else if (type == RQ_INTEGER4) {
1318 ast_str_set(&fieldtype, 0, "INT(10)");
1319 } else if (type == RQ_INTEGER8) {
1320 ast_str_set(&fieldtype, 0, "BIGINT(20)");
1322 ast_log(LOG_WARNING, "Somebody should check this code for a rather large bug... it's about to eat Cincinnati.\n");
1325 } else if (type == RQ_FLOAT) {
1326 ast_str_set(&fieldtype, 0, "FLOAT");
1327 } else if (type == RQ_DATE) {
1328 ast_str_set(&fieldtype, 0, "DATE");
1329 } else if (type == RQ_DATETIME) {
1330 ast_str_set(&fieldtype, 0, "DATETIME");
1334 ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, ast_str_buffer(fieldtype));
1336 ast_mutex_lock(&table->database->lock);
1337 if (!mysql_reconnect(table->database)) {
1338 ast_mutex_unlock(&table->database->lock);
1339 ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1344 if (mysql_real_query(&table->database->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
1345 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
1346 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
1347 ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&table->database->handle));
1354 release_table(table);
1356 /* If we altered the table, we must refresh the cache */
1357 if (table_altered) {
1358 unload_mysql(database, tablename);
1359 release_table(find_table(database, tablename));
1364 static struct ast_config_engine mysql_engine = {
1366 .load_func = config_mysql,
1367 .realtime_func = realtime_mysql,
1368 .realtime_multi_func = realtime_multi_mysql,
1369 .store_func = store_mysql,
1370 .destroy_func = destroy_mysql,
1371 .update_func = update_mysql,
1372 .update2_func = update2_mysql,
1373 .require_func = require_mysql,
1374 .unload_func = unload_mysql,
1377 static int load_module(void)
1381 ast_config_engine_register(&mysql_engine);
1382 if (option_verbose > 1)
1383 ast_verbose(VERBOSE_PREFIX_2 "MySQL RealTime driver loaded.\n");
1384 ast_cli_register_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
1388 static int unload_module(void)
1390 struct mysql_conn *cur;
1391 struct tables *table;
1393 ast_cli_unregister_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
1394 ast_config_engine_deregister(&mysql_engine);
1395 if (option_verbose > 1)
1396 ast_verbose(VERBOSE_PREFIX_2 "MySQL RealTime unloaded.\n");
1398 ast_module_user_hangup_all();
1401 AST_RWLIST_WRLOCK(&databases);
1402 while ((cur = AST_RWLIST_REMOVE_HEAD(&databases, list))) {
1403 mysql_close(&cur->handle);
1404 ast_mutex_destroy(&cur->lock);
1407 AST_RWLIST_UNLOCK(&databases);
1409 /* Destroy cached table info */
1410 AST_LIST_LOCK(&mysql_tables);
1411 while ((table = AST_LIST_REMOVE_HEAD(&mysql_tables, list))) {
1412 destroy_table(table);
1414 AST_LIST_UNLOCK(&mysql_tables);
1419 static int reload(void)
1423 if (option_verbose > 1) {
1424 ast_verb(2, "MySQL RealTime reloaded.\n");
1430 static int parse_config(int reload)
1432 struct ast_config *config = NULL;
1433 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1435 struct mysql_conn *cur;
1437 if ((config = ast_config_load(RES_CONFIG_MYSQL_CONF, config_flags)) == CONFIG_STATUS_FILEMISSING) {
1438 /* Support old config file name */
1439 config = ast_config_load(RES_CONFIG_MYSQL_CONF_OLD, config_flags);
1442 if (config == CONFIG_STATUS_FILEMISSING) {
1444 } else if (config == CONFIG_STATUS_FILEUNCHANGED) {
1446 } else if (config == CONFIG_STATUS_FILEINVALID) {
1447 ast_log(LOG_ERROR, "Not %sloading " RES_CONFIG_MYSQL_CONF "\n", reload ? "re" : "");
1450 AST_RWLIST_WRLOCK(&databases);
1451 for (catg = ast_category_browse(config, NULL); catg; catg = ast_category_browse(config, catg)) {
1452 /* Does this category already exist? */
1453 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1454 if (!strcmp(cur->unique_name, catg)) {
1460 if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(catg) + 1))) {
1461 ast_log(LOG_WARNING, "Could not allocate space for MySQL database '%s'\n", catg);
1465 strcpy(cur->unique_name, catg); /* SAFE */
1466 ast_mutex_init(&cur->lock);
1467 AST_RWLIST_INSERT_TAIL(&databases, cur, list);
1470 load_mysql_config(config, catg, cur);
1472 AST_RWLIST_UNLOCK(&databases);
1474 ast_config_destroy(config);
1479 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn)
1483 if (!(s = ast_variable_retrieve(config, category, "dbuser"))) {
1484 ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n");
1487 ast_copy_string(conn->user, s, sizeof(conn->user));
1489 if (!(s = ast_variable_retrieve(config, category, "dbpass"))) {
1490 ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n");
1493 ast_copy_string(conn->pass, s, sizeof(conn->pass));
1495 if (!(s = ast_variable_retrieve(config, category, "dbhost"))) {
1496 ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n");
1499 ast_copy_string(conn->host, s, sizeof(conn->host));
1501 if (!(s = ast_variable_retrieve(config, category, "dbname"))) {
1502 ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n");
1505 ast_copy_string(conn->name, s, sizeof(conn->name));
1507 if (!(s = ast_variable_retrieve(config, category, "dbport"))) {
1508 ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n");
1511 conn->port = atoi(s);
1513 if (!(s = ast_variable_retrieve(config, category, "dbsock"))) {
1514 if (ast_strlen_zero(conn->host)) {
1515 char *paths[3] = { "/tmp/mysql.sock", "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock" };
1518 for (i = 0; i < 3; i++) {
1519 if (!stat(paths[i], &st)) {
1520 ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '%s' as default.\n", paths[i]);
1521 ast_copy_string(conn->sock, paths[i], sizeof(conn->sock));
1525 ast_log(LOG_WARNING, "MySQL RealTime: No database socket found (and unable to detect a suitable path).\n");
1530 ast_copy_string(conn->sock, s, sizeof(conn->sock));
1532 if ((s = ast_variable_retrieve(config, category, "dbcharset"))) {
1533 ast_copy_string(conn->charset, s, sizeof(conn->charset));
1536 if (!(s = ast_variable_retrieve(config, category, "requirements"))) {
1537 ast_log(LOG_WARNING, "MySQL realtime: no requirements setting found, using 'warn' as default.\n");
1538 conn->requirements = RQ_WARN;
1539 } else if (!strcasecmp(s, "createclose")) {
1540 conn->requirements = RQ_CREATECLOSE;
1541 } else if (!strcasecmp(s, "createchar")) {
1542 conn->requirements = RQ_CREATECHAR;
1543 } else if (!strcasecmp(s, "warn")) {
1544 conn->requirements = RQ_WARN;
1546 ast_log(LOG_WARNING, "MySQL realtime: unrecognized requirements setting '%s', using 'warn'\n", s);
1547 conn->requirements = RQ_WARN;
1550 if (!ast_strlen_zero(conn->host)) {
1551 ast_debug(1, "MySQL RealTime host: %s\n", conn->host);
1552 ast_debug(1, "MySQL RealTime port: %i\n", conn->port);
1554 ast_debug(1, "MySQL RealTime socket: %s\n", conn->sock);
1555 ast_debug(1, "MySQL RealTime database name: %s\n", conn->name);
1556 ast_debug(1, "MySQL RealTime user: %s\n", conn->user);
1557 ast_debug(1, "MySQL RealTime password: %s\n", conn->pass);
1559 ast_debug(1, "MySQL RealTime charset: %s\n", conn->charset);
1564 static int mysql_reconnect(struct mysql_conn *conn)
1566 #ifdef MYSQL_OPT_RECONNECT
1567 my_bool trueval = 1;
1570 /* mutex lock should have been locked before calling this function. */
1573 if ((!conn->connected) && (!ast_strlen_zero(conn->host) || conn->sock) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
1574 if (!mysql_init(&conn->handle)) {
1575 ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
1576 conn->connected = 0;
1579 if(conn->charset && strlen(conn->charset) > 2){
1580 char set_names[255];
1581 char statement[512];
1582 snprintf(set_names, sizeof(set_names), "SET NAMES %s", conn->charset);
1583 mysql_real_escape_string(&conn->handle, statement, set_names, sizeof(set_names));
1584 mysql_options(&conn->handle, MYSQL_INIT_COMMAND, set_names);
1585 mysql_options(&conn->handle, MYSQL_SET_CHARSET_NAME, conn->charset);
1588 if (mysql_real_connect(&conn->handle, conn->host, conn->user, conn->pass, conn->name, conn->port, conn->sock, 0)) {
1589 #ifdef MYSQL_OPT_RECONNECT
1590 /* The default is no longer to automatically reconnect on failure,
1591 * (as of 5.0.3) so we have to set that option here. */
1592 mysql_options(&conn->handle, MYSQL_OPT_RECONNECT, &trueval);
1594 ast_debug(1, "MySQL RealTime: Successfully connected to database.\n");
1595 conn->connected = 1;
1596 conn->connect_time = time(NULL);
1599 ast_log(LOG_ERROR, "MySQL RealTime: Failed to connect database server %s on %s (err %d). Check debug for more info.\n", conn->name, !ast_strlen_zero(conn->host) ? conn->host : conn->sock, mysql_errno(&conn->handle));
1600 ast_debug(1, "MySQL RealTime: Cannot Connect (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
1601 conn->connected = 0;
1602 conn->connect_time = 0;
1606 /* MySQL likes to return an error, even if it reconnects successfully.
1607 * So the postman pings twice. */
1608 if (mysql_ping(&conn->handle) != 0 && (usleep(1) + 2 > 0) && mysql_ping(&conn->handle) != 0) {
1609 conn->connected = 0;
1610 conn->connect_time = 0;
1611 ast_log(LOG_ERROR, "MySQL RealTime: Ping failed (%d). Trying an explicit reconnect.\n", mysql_errno(&conn->handle));
1612 ast_debug(1, "MySQL RealTime: Server Error (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
1613 goto reconnect_tryagain;
1616 if (!conn->connected) {
1617 conn->connected = 1;
1618 conn->connect_time = time(NULL);
1621 if (mysql_select_db(&conn->handle, conn->name) != 0) {
1622 ast_log(LOG_WARNING, "MySQL RealTime: Unable to select database: %s. Still Connected (%u) - %s.\n", conn->name, mysql_errno(&conn->handle), mysql_error(&conn->handle));
1626 ast_debug(1, "MySQL RealTime: Connection okay.\n");
1631 static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1639 e->command = "realtime mysql cache";
1641 "Usage: realtime mysql cache [<database> <table>]\n"
1642 " Shows table cache for the MySQL RealTime driver\n";
1645 if (a->argc < 4 || a->argc > 5) {
1648 l = strlen(a->word);
1651 AST_LIST_LOCK(&mysql_tables);
1652 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1653 if (!strcasecmp(a->argv[3], cur->database->unique_name) && !strncasecmp(a->word, cur->name, l) && ++which > a->n) {
1654 ret = ast_strdup(cur->name);
1658 AST_LIST_UNLOCK(&mysql_tables);
1660 struct mysql_conn *cur;
1661 AST_RWLIST_RDLOCK(&databases);
1662 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1663 if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
1664 ret = ast_strdup(cur->unique_name);
1668 AST_RWLIST_UNLOCK(&databases);
1674 /* List of tables */
1675 AST_LIST_LOCK(&mysql_tables);
1676 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1677 ast_cli(a->fd, "%20.20s %s\n", cur->database->unique_name, cur->name);
1679 AST_LIST_UNLOCK(&mysql_tables);
1680 } else if (a->argc == 4) {
1682 /* List of tables */
1683 AST_LIST_LOCK(&mysql_tables);
1684 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1685 if (!strcasecmp(cur->database->unique_name, a->argv[3])) {
1686 ast_cli(a->fd, "%s\n", cur->name);
1690 AST_LIST_UNLOCK(&mysql_tables);
1692 ast_cli(a->fd, "No tables cached within %s database\n", a->argv[3]);
1694 } else if (a->argc == 5) {
1695 /* List of columns */
1696 if ((cur = find_table(a->argv[3], a->argv[4]))) {
1697 struct columns *col;
1698 ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[3]);
1699 ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s\n", "Name", "Type", "Len");
1700 AST_LIST_TRAVERSE(&cur->columns, col, list) {
1701 ast_cli(a->fd, "%-20.20s %-20.20s %3d\n", col->name, col->type, col->len);
1705 ast_cli(a->fd, "No such table '%s'\n", a->argv[3]);
1711 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1713 char status[256], status2[100] = "", type[20];
1715 int ctime = 0, found = 0;
1716 struct mysql_conn *cur;
1717 int l = 0, which = 0;
1721 e->command = "realtime mysql status";
1723 "Usage: realtime mysql status [<database>]\n"
1724 " Shows connection information for the MySQL RealTime driver\n";
1728 AST_RWLIST_RDLOCK(&databases);
1729 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1730 if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
1731 ret = ast_strdup(cur->unique_name);
1735 AST_RWLIST_UNLOCK(&databases);
1741 return CLI_SHOWUSAGE;
1743 AST_RWLIST_RDLOCK(&databases);
1744 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1745 if (a->argc == 3 || (a->argc == 4 && !strcasecmp(a->argv[3], cur->unique_name))) {
1748 if (mysql_reconnect(cur)) {
1749 snprintf(type, sizeof(type), "connected to");
1750 ctime = time(NULL) - cur->connect_time;
1752 snprintf(type, sizeof(type), "configured for");
1756 if (!ast_strlen_zero(cur->host)) {
1757 snprintf(status, sizeof(status), "%s %s %s@%s, port %d", cur->unique_name, type, cur->name, cur->host, cur->port);
1759 snprintf(status, sizeof(status), "%s %s %s on socket file %s", cur->unique_name, type, cur->name, cur->sock);
1762 if (!ast_strlen_zero(cur->user)) {
1763 snprintf(status2, sizeof(status2), " with username %s", cur->user);
1768 if (ctime > 31536000) {
1769 ast_cli(a->fd, "%s%s for %.1f years.\n", status, status2, (double)ctime / 31536000.0);
1770 } else if (ctime > 86400 * 30) {
1771 ast_cli(a->fd, "%s%s for %d days.\n", status, status2, ctime / 86400);
1772 } else if (ctime > 86400) {
1773 ast_cli(a->fd, "%s%s for %d days, %d hours.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600);
1774 } else if (ctime > 3600) {
1775 ast_cli(a->fd, "%s%s for %d hours, %d minutes.\n", status, status2, ctime / 3600, (ctime % 3600) / 60);
1776 } else if (ctime > 60) {
1777 ast_cli(a->fd, "%s%s for %d minutes.\n", status, status2, ctime / 60);
1778 } else if (ctime > -1) {
1779 ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
1781 ast_cli(a->fd, "%s%s.\n", status, status2);
1785 AST_RWLIST_UNLOCK(&databases);
1788 ast_cli(a->fd, "No connections configured.\n");
1793 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "MySQL RealTime Configuration Driver",
1794 .load = load_module,
1795 .unload = unload_module,
1797 .load_pri = AST_MODPRI_REALTIME_DRIVER,