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"
50 #define RES_CONFIG_MYSQL_CONF "res_config_mysql.conf"
51 #define RES_CONFIG_MYSQL_CONF_OLD "res_mysql.conf"
55 #define ESCAPE_STRING(buf, var) \
57 if ((valsz = strlen(var)) * 2 + 1 > ast_str_size(buf)) { \
58 ast_str_make_space(&(buf), valsz * 2 + 1); \
60 mysql_real_escape_string(&dbh->handle, ast_str_buffer(buf), var, valsz); \
63 AST_THREADSTORAGE(sql_buf);
64 AST_THREADSTORAGE(sql2_buf);
65 AST_THREADSTORAGE(find_buf);
66 AST_THREADSTORAGE(scratch_buf);
67 AST_THREADSTORAGE(modify_buf);
68 AST_THREADSTORAGE(modify2_buf);
69 AST_THREADSTORAGE(modify3_buf);
71 enum requirements { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR };
74 AST_RWLIST_ENTRY(mysql_conn) list;
85 enum requirements requirements;
95 AST_LIST_ENTRY(columns) list;
100 AST_LIST_HEAD_NOLOCK(mysql_columns, columns) columns;
101 AST_LIST_ENTRY(tables) list;
102 struct mysql_conn *database;
106 static AST_LIST_HEAD_STATIC(mysql_tables, tables);
107 static AST_RWLIST_HEAD_STATIC(databases, mysql_conn);
109 static int parse_config(int reload);
110 static int mysql_reconnect(struct mysql_conn *conn);
111 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
112 static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
113 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn);
114 static int require_mysql(const char *database, const char *tablename, va_list ap);
115 static int internal_require(const char *database, const char *table, ...) attribute_sentinel;
117 static struct ast_cli_entry cli_realtime_mysql_status[] = {
118 AST_CLI_DEFINE(handle_cli_realtime_mysql_status, "Shows connection information for the MySQL RealTime driver"),
119 AST_CLI_DEFINE(handle_cli_realtime_mysql_cache, "Shows cached tables within the MySQL realtime driver"),
122 static struct mysql_conn *find_database(const char *database, int for_write)
126 struct mysql_conn *cur;
128 if ((ptr = strchr(database, '/'))) {
129 /* Multiple databases encoded within string */
131 whichdb = ast_strdupa(ptr + 1);
133 whichdb = alloca(ptr - database + 1);
134 strncpy(whichdb, database, ptr - database);
135 whichdb[ptr - database] = '\0';
138 whichdb = ast_strdupa(database);
141 AST_RWLIST_RDLOCK(&databases);
142 AST_RWLIST_TRAVERSE(&databases, cur, list) {
143 if (!strcmp(cur->unique_name, whichdb)) {
144 ast_mutex_lock(&cur->lock);
148 AST_RWLIST_UNLOCK(&databases);
152 #define release_database(a) ast_mutex_unlock(&(a)->lock)
154 static int internal_require(const char *database, const char *table, ...)
159 res = require_mysql(database, table, ap);
164 static void destroy_table(struct tables *table)
166 struct columns *column;
167 ast_mutex_lock(&table->lock);
168 while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
171 ast_mutex_unlock(&table->lock);
172 ast_mutex_destroy(&table->lock);
176 static struct tables *find_table(const char *database, const char *tablename)
178 struct columns *column;
179 struct tables *table;
180 struct ast_str *sql = ast_str_thread_get(&find_buf, 30);
181 char *fname, *ftype, *flen, *fdflt, *fnull;
182 struct mysql_conn *dbh;
186 if (!(dbh = find_database(database, 1))) {
190 AST_LIST_LOCK(&mysql_tables);
191 AST_LIST_TRAVERSE(&mysql_tables, table, list) {
192 if (!strcasecmp(table->name, tablename)) {
193 ast_mutex_lock(&table->lock);
194 AST_LIST_UNLOCK(&mysql_tables);
195 release_database(dbh);
200 /* Not found, scan the table */
201 ast_str_set(&sql, 0, "DESC %s", tablename);
203 if (!mysql_reconnect(dbh)) {
204 release_database(dbh);
205 AST_LIST_UNLOCK(&mysql_tables);
209 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
210 ast_log(LOG_ERROR, "Failed to query database '%s', table '%s' columns: %s\n", database, tablename, mysql_error(&dbh->handle));
211 release_database(dbh);
212 AST_LIST_UNLOCK(&mysql_tables);
216 if (!(table = ast_calloc(1, sizeof(*table) + strlen(tablename) + 1))) {
217 ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
218 release_database(dbh);
219 AST_LIST_UNLOCK(&mysql_tables);
222 strcpy(table->name, tablename); /* SAFE */
223 table->database = dbh;
224 ast_mutex_init(&table->lock);
225 AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
227 if ((result = mysql_store_result(&dbh->handle))) {
228 while ((row = mysql_fetch_row(result))) {
233 ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
239 if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + strlen(fdflt) + 3))) {
240 ast_log(LOG_ERROR, "Unable to allocate column element %s for %s\n", fname, tablename);
241 destroy_table(table);
242 release_database(dbh);
243 AST_LIST_UNLOCK(&mysql_tables);
247 if ((flen = strchr(ftype, '('))) {
248 sscanf(flen, "(%30d)", &column->len);
250 /* Columns like dates, times, and timestamps don't have a length */
254 column->name = (char *)column + sizeof(*column);
255 column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
256 column->dflt = (char *)column + sizeof(*column) + strlen(fname) + 1 + strlen(ftype) + 1;
257 strcpy(column->name, fname);
258 strcpy(column->type, ftype);
259 strcpy(column->dflt, fdflt);
260 column->null = (strcmp(fnull, "YES") == 0 ? 1 : 0);
261 AST_LIST_INSERT_TAIL(&table->columns, column, list);
263 mysql_free_result(result);
266 AST_LIST_INSERT_TAIL(&mysql_tables, table, list);
267 ast_mutex_lock(&table->lock);
268 AST_LIST_UNLOCK(&mysql_tables);
269 release_database(dbh);
273 static void release_table(struct tables *table)
276 ast_mutex_unlock(&table->lock);
280 static struct columns *find_column(struct tables *table, const char *colname)
282 struct columns *column;
284 AST_LIST_TRAVERSE(&table->columns, column, list) {
285 if (strcmp(column->name, colname) == 0) {
293 static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap)
295 struct mysql_conn *dbh;
299 int numFields, i, valsz;
300 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
301 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
305 const char *newparam, *newval;
306 struct ast_variable *var=NULL, *prev=NULL;
308 if (!(dbh = find_database(database, 0))) {
309 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: %s (check res_mysql.conf)\n", database);
314 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
315 release_database(dbh);
319 /* Get the first parameter and first value in our list of passed paramater/value pairs */
320 newparam = va_arg(ap, const char *);
321 newval = va_arg(ap, const char *);
322 if (!newparam || !newval) {
323 ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
324 release_database(dbh);
328 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
329 if (!mysql_reconnect(dbh)) {
330 release_database(dbh);
334 /* Create the first part of the query using the first parameter/value pairs we just extracted
335 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
337 if (!strchr(newparam, ' '))
342 ESCAPE_STRING(buf, newval);
343 ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
344 while ((newparam = va_arg(ap, const char *))) {
345 newval = va_arg(ap, const char *);
346 if (!strchr(newparam, ' '))
350 ESCAPE_STRING(buf, newval);
351 ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
355 ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
358 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
359 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
360 release_database(dbh);
364 if ((result = mysql_store_result(&dbh->handle))) {
365 numFields = mysql_num_fields(result);
366 fields = mysql_fetch_fields(result);
368 while ((row = mysql_fetch_row(result))) {
369 for (i = 0; i < numFields; i++) {
370 if (ast_strlen_zero(row[i]))
372 for (stringp = ast_strdupa(row[i]), chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
373 if (!chunk || ast_strlen_zero(ast_strip(chunk))) {
377 if ((prev->next = ast_variable_new(fields[i].name, chunk, ""))) {
381 prev = var = ast_variable_new(fields[i].name, chunk, "");
387 ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
390 release_database(dbh);
391 mysql_free_result(result);
396 static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap)
398 struct mysql_conn *dbh;
402 int numFields, i, valsz;
403 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
404 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
405 const char *initfield = NULL;
409 const char *newparam, *newval;
410 struct ast_variable *var = NULL;
411 struct ast_config *cfg = NULL;
412 struct ast_category *cat = NULL;
414 if (!(dbh = find_database(database, 0))) {
415 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
420 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
421 release_database(dbh);
425 if (!(cfg = ast_config_new())) {
426 /* If I can't alloc memory at this point, why bother doing anything else? */
427 ast_log(LOG_WARNING, "Out of memory!\n");
428 release_database(dbh);
432 /* Get the first parameter and first value in our list of passed paramater/value pairs */
433 newparam = va_arg(ap, const char *);
434 newval = va_arg(ap, const char *);
435 if (!newparam || !newval) {
436 ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
437 ast_config_destroy(cfg);
438 release_database(dbh);
442 initfield = ast_strdupa(newparam);
443 if (initfield && (op = strchr(initfield, ' '))) {
447 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
448 if (!mysql_reconnect(dbh)) {
449 release_database(dbh);
450 ast_config_destroy(cfg);
454 /* Create the first part of the query using the first parameter/value pairs we just extracted
455 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
457 if (!strchr(newparam, ' '))
462 ESCAPE_STRING(buf, newval);
463 ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(buf));
464 while ((newparam = va_arg(ap, const char *))) {
465 newval = va_arg(ap, const char *);
466 if (!strchr(newparam, ' ')) op = " ="; else op = "";
467 ESCAPE_STRING(buf, newval);
468 ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(buf));
472 ast_str_append(&sql, 0, " ORDER BY %s", initfield);
477 ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
480 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
481 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
482 release_database(dbh);
483 ast_config_destroy(cfg);
487 if ((result = mysql_store_result(&dbh->handle))) {
488 numFields = mysql_num_fields(result);
489 fields = mysql_fetch_fields(result);
491 while ((row = mysql_fetch_row(result))) {
493 cat = ast_category_new("", "", -1);
495 ast_log(LOG_WARNING, "Out of memory!\n");
498 for (i = 0; i < numFields; i++) {
499 if (ast_strlen_zero(row[i]))
501 for (stringp = ast_strdupa(row[i]), chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
502 if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
503 if (initfield && !strcmp(initfield, fields[i].name)) {
504 ast_category_rename(cat, chunk);
506 var = ast_variable_new(fields[i].name, chunk, "");
507 ast_variable_append(cat, var);
511 ast_category_append(cfg, cat);
514 ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
517 release_database(dbh);
518 mysql_free_result(result);
523 static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, va_list ap)
525 struct mysql_conn *dbh;
526 my_ulonglong numrows;
528 const char *newparam, *newval;
529 struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
530 struct tables *table;
531 struct columns *column = NULL;
533 if (!(dbh = find_database(database, 1))) {
534 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
539 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
540 release_database(dbh);
544 if (!(table = find_table(database, tablename))) {
545 ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
546 release_database(dbh);
550 if (!(column = find_column(table, keyfield))) {
551 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);
552 release_table(table);
553 release_database(dbh);
557 /* Get the first parameter and first value in our list of passed paramater/value pairs */
558 newparam = va_arg(ap, const char *);
559 newval = va_arg(ap, const char *);
560 if (!newparam || !newval) {
561 ast_log(LOG_WARNING, "MySQL RealTime: Realtime update requires at least 1 parameter and 1 value to update.\n");
562 release_table(table);
563 release_database(dbh);
567 /* Check that the column exists in the table */
568 if (!(column = find_column(table, newparam))) {
569 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);
570 release_table(table);
571 release_database(dbh);
575 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
576 if (!mysql_reconnect(dbh)) {
577 release_table(table);
578 release_database(dbh);
582 /* Create the first part of the query using the first parameter/value pairs we just extracted
583 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
585 ESCAPE_STRING(buf, newval);
586 ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(buf));
588 /* If the column length isn't long enough, give a chance to lengthen it. */
589 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
590 internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
593 while ((newparam = va_arg(ap, const char *))) {
594 newval = va_arg(ap, const char *);
596 /* If the column is not within the table, then skip it */
597 if (!(column = find_column(table, newparam))) {
598 ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
602 ESCAPE_STRING(buf, newval);
603 ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(buf));
605 /* If the column length isn't long enough, give a chance to lengthen it. */
606 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
607 internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
612 ESCAPE_STRING(buf, lookup);
613 ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(buf));
615 ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
618 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
619 ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
620 release_table(table);
621 release_database(dbh);
625 numrows = mysql_affected_rows(&dbh->handle);
626 release_table(table);
627 release_database(dbh);
629 ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
631 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
632 * An integer greater than zero indicates the number of rows affected
633 * Zero indicates that no records were updated
634 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
640 static int update2_mysql(const char *database, const char *tablename, va_list ap)
642 struct mysql_conn *dbh;
643 my_ulonglong numrows;
645 const char *newparam, *newval;
647 struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
648 struct ast_str *where = ast_str_thread_get(&sql2_buf, 100);
649 struct tables *table;
650 struct columns *column = NULL;
653 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
657 if (!(dbh = find_database(database, 1))) {
658 ast_log(LOG_ERROR, "Invalid database specified: %s\n", database);
662 if (!(table = find_table(database, tablename))) {
663 ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
664 release_database(dbh);
668 if (!sql || !buf || !where) {
669 release_database(dbh);
670 release_table(table);
674 ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
675 ast_str_set(&where, 0, "WHERE");
677 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
678 if (!mysql_reconnect(dbh)) {
679 release_table(table);
680 release_database(dbh);
684 while ((newparam = va_arg(ap, const char *))) {
685 if (!(column = find_column(table, newparam))) {
686 ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
687 release_table(table);
688 release_database(dbh);
691 if (!(newval = va_arg(ap, const char *))) {
692 ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
693 release_table(table);
694 release_database(dbh);
697 ESCAPE_STRING(buf, newval);
698 ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, ast_str_buffer(buf));
701 /* If the column length isn't long enough, give a chance to lengthen it. */
702 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
703 internal_require(database, tablename, newparam, RQ_CHAR, valsz, SENTINEL);
708 while ((newparam = va_arg(ap, const char *))) {
709 if (!(newval = va_arg(ap, const char *))) {
710 ast_log(LOG_ERROR, "Invalid arguments: no value specified for column '%s' on '%s@%s'\n", newparam, tablename, database);
711 release_table(table);
712 release_database(dbh);
716 /* If the column is not within the table, then skip it */
717 if (!(column = find_column(table, newparam))) {
718 ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
722 ESCAPE_STRING(buf, newval);
723 ast_str_append(&sql, 0, "%s %s = '%s'", first ? "" : ",", 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, valsz, SENTINEL);
731 release_table(table);
733 ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
735 ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
738 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
739 ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
740 release_table(table);
741 release_database(dbh);
745 numrows = mysql_affected_rows(&dbh->handle);
746 release_database(dbh);
748 ast_debug(1, "MySQL RealTime: Updated %llu rows on table: %s\n", numrows, tablename);
750 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
751 * An integer greater than zero indicates the number of rows affected
752 * Zero indicates that no records were updated
753 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
759 static int store_mysql(const char *database, const char *table, va_list ap)
761 struct mysql_conn *dbh;
762 my_ulonglong insertid;
763 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
764 struct ast_str *sql2 = ast_str_thread_get(&sql2_buf, 16);
765 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
767 const char *newparam, *newval;
769 if (!(dbh = find_database(database, 1))) {
770 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
775 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
776 release_database(dbh);
779 /* Get the first parameter and first value in our list of passed paramater/value pairs */
780 newparam = va_arg(ap, const char *);
781 newval = va_arg(ap, const char *);
782 if (!newparam || !newval) {
783 ast_log(LOG_WARNING, "MySQL RealTime: Realtime storage requires at least 1 parameter and 1 value to search on.\n");
784 release_database(dbh);
787 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
788 if (!mysql_reconnect(dbh)) {
789 release_database(dbh);
792 /* Create the first part of the query using the first parameter/value pairs we just extracted
793 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
794 ESCAPE_STRING(buf, newval);
795 ast_str_set(&sql, 0, "INSERT INTO %s (%s", table, newparam);
796 ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
798 internal_require(database, table, newparam, RQ_CHAR, valsz, SENTINEL);
800 while ((newparam = va_arg(ap, const char *))) {
801 if ((newval = va_arg(ap, const char *))) {
802 ESCAPE_STRING(buf, newval);
807 if (internal_require(database, table, newparam, RQ_CHAR, valsz, SENTINEL) == 0) {
808 ast_str_append(&sql, 0, ", %s", newparam);
809 ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
813 ast_str_append(&sql, 0, "%s)", ast_str_buffer(sql2));
814 ast_debug(1,"MySQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql));
817 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
818 ast_log(LOG_WARNING, "MySQL RealTime: Failed to insert into database: %s\n", mysql_error(&dbh->handle));
819 release_database(dbh);
823 /*!\note The return value is non-portable and may change in future versions. */
824 insertid = mysql_insert_id(&dbh->handle);
825 release_database(dbh);
827 ast_debug(1, "MySQL RealTime: row inserted on table: %s, id: %llu\n", table, insertid);
829 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
830 * An integer greater than zero indicates the number of rows affected
831 * Zero indicates that no records were updated
832 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
834 return (int)insertid;
837 static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
839 struct mysql_conn *dbh;
840 my_ulonglong numrows;
841 struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
842 struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
844 const char *newparam, *newval;
846 if (!(dbh = find_database(database, 1))) {
847 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
852 ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
853 release_database(dbh);
857 /* Get the first parameter and first value in our list of passed paramater/value pairs */
858 /* newparam = va_arg(ap, const char *);
859 newval = va_arg(ap, const char *);*/
860 if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup)) {
861 ast_log(LOG_WARNING, "MySQL RealTime: Realtime destroying requires at least 1 parameter and 1 value to search on.\n");
862 release_database(dbh);
866 /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
867 if (!mysql_reconnect(dbh)) {
868 release_database(dbh);
872 /* Create the first part of the query using the first parameter/value pairs we just extracted
873 If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
874 ESCAPE_STRING(buf, lookup);
875 ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, keyfield, ast_str_buffer(buf));
876 while ((newparam = va_arg(ap, const char *))) {
877 newval = va_arg(ap, const char *);
878 ESCAPE_STRING(buf, newval);
879 ast_str_append(&sql, 0, " AND %s = '%s'", newparam, ast_str_buffer(buf));
883 ast_debug(1, "MySQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
886 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
887 ast_log(LOG_WARNING, "MySQL RealTime: Failed to delete from database: %s\n", mysql_error(&dbh->handle));
888 release_database(dbh);
892 numrows = mysql_affected_rows(&dbh->handle);
893 release_database(dbh);
895 ast_debug(1, "MySQL RealTime: Deleted %llu rows on table: %s\n", numrows, table);
897 /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
898 * An integer greater than zero indicates the number of rows affected
899 * Zero indicates that no records were updated
900 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
906 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)
908 struct mysql_conn *dbh;
911 my_ulonglong num_rows;
912 struct ast_variable *new_v;
913 struct ast_category *cur_cat = NULL;
914 struct ast_str *sql = ast_str_thread_get(&sql_buf, 200);
916 int last_cat_metric = 0;
918 ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
920 if (!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) {
921 ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n");
925 if (!(dbh = find_database(database, 0))) {
926 ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
930 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);
932 ast_debug(1, "MySQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
934 /* We now have our complete statement; Lets connect to the server and execute it. */
935 if (!mysql_reconnect(dbh)) {
939 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
940 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
941 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
942 ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbh->handle));
943 release_database(dbh);
947 if ((result = mysql_store_result(&dbh->handle))) {
948 num_rows = mysql_num_rows(result);
949 ast_debug(1, "MySQL RealTime: Found %llu rows.\n", num_rows);
951 /* There might exist a better way to access the column names other than counting,
952 * but I believe that would require another loop that we don't need. */
954 while ((row = mysql_fetch_row(result))) {
955 if (!strcmp(row[1], "#include")) {
956 if (!ast_config_internal_load(row[2], cfg, config_flags, "", who_asked)) {
957 mysql_free_result(result);
958 release_database(dbh);
959 ast_config_destroy(cfg);
965 if (strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) {
966 if (!(cur_cat = ast_category_new(row[0], "", -1))) {
967 ast_log(LOG_WARNING, "Out of memory!\n");
970 strcpy(last, row[0]);
971 last_cat_metric = atoi(row[3]);
972 ast_category_append(cfg, cur_cat);
974 new_v = ast_variable_new(row[1], row[2], "");
976 ast_variable_append(cur_cat, new_v);
979 ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file);
982 mysql_free_result(result);
983 release_database(dbh);
988 static int unload_mysql(const char *database, const char *tablename)
991 AST_LIST_LOCK(&mysql_tables);
992 AST_LIST_TRAVERSE_SAFE_BEGIN(&mysql_tables, cur, list) {
993 if (strcmp(cur->name, tablename) == 0) {
994 AST_LIST_REMOVE_CURRENT(list);
999 AST_LIST_TRAVERSE_SAFE_END
1000 AST_LIST_UNLOCK(&mysql_tables);
1001 return cur ? 0 : -1;
1004 static int modify_mysql(const char *database, const char *tablename, struct columns *column, require_type type, int len)
1006 /*!\note Cannot use ANY of the same scratch space as is used in other functions, as this one is interspersed. */
1007 struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *escbuf = ast_str_thread_get(&modify2_buf, 100);
1008 struct ast_str *typestr = ast_str_thread_get(&modify3_buf, 30);
1009 int waschar = strncasecmp(column->type, "char", 4) == 0 ? 1 : 0;
1010 int wasvarchar = strncasecmp(column->type, "varchar", 7) == 0 ? 1 : 0;
1012 struct mysql_conn *dbh;
1014 if (!(dbh = find_database(database, 1))) {
1019 if (type == RQ_CHAR || waschar || wasvarchar) {
1021 ast_str_set(&typestr, 0, "VARCHAR(%d)", len);
1023 ast_str_set(&typestr, 0, "CHAR(%d)", len);
1025 } else if (type == RQ_UINTEGER1) {
1026 ast_str_set(&typestr, 0, "tinyint(3) unsigned");
1027 } else if (type == RQ_INTEGER1) {
1028 ast_str_set(&typestr, 0, "tinyint(4)");
1029 } else if (type == RQ_UINTEGER2) {
1030 ast_str_set(&typestr, 0, "smallint(5) unsigned");
1031 } else if (type == RQ_INTEGER2) {
1032 ast_str_set(&typestr, 0, "smallint(6)");
1033 } else if (type == RQ_UINTEGER3) {
1034 ast_str_set(&typestr, 0, "mediumint(8) unsigned");
1035 } else if (type == RQ_INTEGER3) {
1036 ast_str_set(&typestr, 0, "mediumint(8)");
1037 } else if (type == RQ_UINTEGER4) {
1038 ast_str_set(&typestr, 0, "int(10) unsigned");
1039 } else if (type == RQ_INTEGER4) {
1040 ast_str_set(&typestr, 0, "int(11)");
1041 } else if (type == RQ_UINTEGER8) {
1042 ast_str_set(&typestr, 0, "bigint(19) unsigned");
1043 } else if (type == RQ_INTEGER8) {
1044 ast_str_set(&typestr, 0, "bigint(20)");
1045 } else if (type == RQ_DATETIME) {
1046 ast_str_set(&typestr, 0, "datetime");
1047 } else if (type == RQ_DATE) {
1048 ast_str_set(&typestr, 0, "date");
1049 } else if (type == RQ_FLOAT) {
1050 ast_str_set(&typestr, 0, "FLOAT(%d,2)", len);
1052 ast_log(LOG_ERROR, "Unknown type (should NEVER happen)\n");
1056 ast_str_set(&sql, 0, "ALTER TABLE %s MODIFY %s %s", tablename, column->name, ast_str_buffer(typestr));
1057 if (!column->null) {
1058 ast_str_append(&sql, 0, " NOT NULL");
1060 if (!ast_strlen_zero(column->dflt)) {
1062 ESCAPE_STRING(escbuf, column->dflt);
1063 ast_str_append(&sql, 0, " DEFAULT '%s'", ast_str_buffer(escbuf));
1066 if (!mysql_reconnect(dbh)) {
1067 ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1073 if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
1074 ast_log(LOG_WARNING, "MySQL RealTime: Failed to modify database: %s\n", mysql_error(&dbh->handle));
1075 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
1080 release_database(dbh);
1084 #define PICK_WHICH_ALTER_ACTION(stringtype) \
1085 if (table->database->requirements == RQ_WARN) { \
1086 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
1087 "the required data length: %d (detected stringtype)\n", \
1088 tablename, database, column->name, size); \
1090 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) { \
1091 table_altered = 1; \
1092 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) { \
1093 table_altered = 1; \
1098 static int require_mysql(const char *database, const char *tablename, va_list ap)
1100 struct columns *column;
1101 struct tables *table = find_table(database, tablename);
1103 int type, size, res = 0, table_altered = 0;
1106 ast_log(LOG_WARNING, "Table %s not found in database. This table should exist if you're using realtime.\n", tablename);
1110 while ((elm = va_arg(ap, char *))) {
1111 type = va_arg(ap, require_type);
1112 size = va_arg(ap, int);
1113 AST_LIST_TRAVERSE(&table->columns, column, list) {
1114 if (strcmp(column->name, elm) == 0) {
1115 /* Char can hold anything, as long as it is large enough */
1116 if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
1117 if ((size > column->len) && column->len != -1) {
1118 if (table->database->requirements == RQ_WARN) {
1119 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);
1121 } else if (modify_mysql(database, tablename, column, type, size) == 0) {
1127 } else if (strcasestr(column->type, "unsigned")) {
1128 if (!ast_rq_is_int(type)) {
1129 if (table->database->requirements == RQ_WARN) {
1130 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
1131 database, tablename, column->name, column->type,
1132 type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
1133 type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" : "a rather stiff drink");
1135 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1137 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1142 } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
1143 if (type != RQ_UINTEGER1) {
1144 PICK_WHICH_ALTER_ACTION(unsigned tinyint)
1146 } else if (strncasecmp(column->type, "smallint", 1) == 0) {
1147 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
1148 PICK_WHICH_ALTER_ACTION(unsigned smallint)
1150 } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
1151 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1152 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1153 type != RQ_UINTEGER3) {
1154 PICK_WHICH_ALTER_ACTION(unsigned mediumint)
1156 } else if (strncasecmp(column->type, "int", 1) == 0) {
1157 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1158 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1159 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1160 type != RQ_UINTEGER4) {
1161 PICK_WHICH_ALTER_ACTION(unsigned int)
1163 } else if (strncasecmp(column->type, "bigint", 1) == 0) {
1164 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1165 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1166 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1167 type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1168 type != RQ_UINTEGER8) {
1169 PICK_WHICH_ALTER_ACTION(unsigned bigint)
1172 } else if (strcasestr(column->type, "int")) {
1173 if (!ast_rq_is_int(type)) {
1174 if (table->database->requirements == RQ_WARN) {
1175 ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
1176 database, tablename, column->name, column->type,
1177 type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
1178 type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" :
1179 "to get a life, rather than writing silly error messages");
1181 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1183 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1188 } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
1189 if (type != RQ_INTEGER1) {
1190 PICK_WHICH_ALTER_ACTION(tinyint)
1192 } else if (strncasecmp(column->type, "smallint", 1) == 0) {
1193 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
1194 PICK_WHICH_ALTER_ACTION(smallint)
1196 } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
1197 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1198 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1199 type != RQ_INTEGER3) {
1200 PICK_WHICH_ALTER_ACTION(mediumint)
1202 } else if (strncasecmp(column->type, "int", 1) == 0) {
1203 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1204 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1205 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1206 type != RQ_INTEGER4) {
1207 PICK_WHICH_ALTER_ACTION(int)
1209 } else if (strncasecmp(column->type, "bigint", 1) == 0) {
1210 if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1211 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1212 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1213 type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1214 type != RQ_INTEGER8) {
1215 PICK_WHICH_ALTER_ACTION(bigint)
1218 } else if (strncmp(column->type, "float", 5) == 0 && !ast_rq_is_int(type) && type != RQ_FLOAT) {
1219 if (table->database->requirements == RQ_WARN) {
1220 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1222 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1224 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1229 } else if ((strncmp(column->type, "datetime", 8) == 0 || strncmp(column->type, "timestamp", 9)) && type != RQ_DATETIME) {
1230 if (table->database->requirements == RQ_WARN) {
1231 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1233 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1235 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1240 } else if ((strncmp(column->type, "date", 4) == 0) && type != RQ_DATE) {
1241 if (table->database->requirements == RQ_WARN) {
1242 ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
1244 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1246 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1251 } else { /* Other, possibly unsupported types? */
1252 if (table->database->requirements == RQ_WARN) {
1253 ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
1255 } else if (table->database->requirements == RQ_CREATECLOSE && modify_mysql(database, tablename, column, type, size) == 0) {
1257 } else if (table->database->requirements == RQ_CREATECHAR && modify_mysql(database, tablename, column, RQ_CHAR, size) == 0) {
1267 if (table->database->requirements == RQ_WARN) {
1268 ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
1270 struct ast_str *sql = ast_str_thread_get(&modify_buf, 100), *fieldtype = ast_str_thread_get(&modify3_buf, 16);
1272 if (table->database->requirements == RQ_CREATECHAR || type == RQ_CHAR) {
1273 ast_str_set(&fieldtype, 0, "CHAR(%d)", size);
1274 } else if (type == RQ_UINTEGER1 || type == RQ_UINTEGER2 || type == RQ_UINTEGER3 || type == RQ_UINTEGER4 || type == RQ_UINTEGER8) {
1275 if (type == RQ_UINTEGER1) {
1276 ast_str_set(&fieldtype, 0, "TINYINT(3) UNSIGNED");
1277 } else if (type == RQ_UINTEGER2) {
1278 ast_str_set(&fieldtype, 0, "SMALLINT(5) UNSIGNED");
1279 } else if (type == RQ_UINTEGER3) {
1280 ast_str_set(&fieldtype, 0, "MEDIUMINT(8) UNSIGNED");
1281 } else if (type == RQ_UINTEGER4) {
1282 ast_str_set(&fieldtype, 0, "INT(10) UNSIGNED");
1283 } else if (type == RQ_UINTEGER8) {
1284 ast_str_set(&fieldtype, 0, "BIGINT(20) UNSIGNED");
1286 ast_log(LOG_WARNING, "Somebody should check this code for a rather large bug... it's about to squash Tokyo.\n");
1289 } else if (ast_rq_is_int(type)) {
1290 if (type == RQ_INTEGER1) {
1291 ast_str_set(&fieldtype, 0, "TINYINT(3)");
1292 } else if (type == RQ_INTEGER2) {
1293 ast_str_set(&fieldtype, 0, "SMALLINT(5)");
1294 } else if (type == RQ_INTEGER3) {
1295 ast_str_set(&fieldtype, 0, "MEDIUMINT(8)");
1296 } else if (type == RQ_INTEGER4) {
1297 ast_str_set(&fieldtype, 0, "INT(10)");
1298 } else if (type == RQ_INTEGER8) {
1299 ast_str_set(&fieldtype, 0, "BIGINT(20)");
1301 ast_log(LOG_WARNING, "Somebody should check this code for a rather large bug... it's about to eat Cincinnati.\n");
1304 } else if (type == RQ_FLOAT) {
1305 ast_str_set(&fieldtype, 0, "FLOAT");
1306 } else if (type == RQ_DATE) {
1307 ast_str_set(&fieldtype, 0, "DATE");
1308 } else if (type == RQ_DATETIME) {
1309 ast_str_set(&fieldtype, 0, "DATETIME");
1313 ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, ast_str_buffer(fieldtype));
1315 ast_mutex_lock(&table->database->lock);
1316 if (!mysql_reconnect(table->database)) {
1317 ast_mutex_unlock(&table->database->lock);
1318 ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
1323 if (mysql_real_query(&table->database->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
1324 ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
1325 ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
1326 ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&table->database->handle));
1333 release_table(table);
1335 /* If we altered the table, we must refresh the cache */
1336 if (table_altered) {
1337 unload_mysql(database, tablename);
1338 release_table(find_table(database, tablename));
1343 static struct ast_config_engine mysql_engine = {
1345 .load_func = config_mysql,
1346 .realtime_func = realtime_mysql,
1347 .realtime_multi_func = realtime_multi_mysql,
1348 .store_func = store_mysql,
1349 .destroy_func = destroy_mysql,
1350 .update_func = update_mysql,
1351 .update2_func = update2_mysql,
1352 .require_func = require_mysql,
1353 .unload_func = unload_mysql,
1356 static int load_module(void)
1360 ast_config_engine_register(&mysql_engine);
1361 if (option_verbose > 1)
1362 ast_verbose(VERBOSE_PREFIX_2 "MySQL RealTime driver loaded.\n");
1363 ast_cli_register_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
1367 static int unload_module(void)
1369 struct mysql_conn *cur;
1370 struct tables *table;
1372 ast_cli_unregister_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
1373 ast_config_engine_deregister(&mysql_engine);
1374 if (option_verbose > 1)
1375 ast_verbose(VERBOSE_PREFIX_2 "MySQL RealTime unloaded.\n");
1377 ast_module_user_hangup_all();
1380 AST_RWLIST_WRLOCK(&databases);
1381 while ((cur = AST_RWLIST_REMOVE_HEAD(&databases, list))) {
1382 mysql_close(&cur->handle);
1383 ast_mutex_destroy(&cur->lock);
1386 AST_RWLIST_UNLOCK(&databases);
1388 /* Destroy cached table info */
1389 AST_LIST_LOCK(&mysql_tables);
1390 while ((table = AST_LIST_REMOVE_HEAD(&mysql_tables, list))) {
1391 destroy_table(table);
1393 AST_LIST_UNLOCK(&mysql_tables);
1398 static int reload(void)
1402 if (option_verbose > 1) {
1403 ast_verb(2, "MySQL RealTime reloaded.\n");
1409 static int parse_config(int reload)
1411 struct ast_config *config = NULL;
1412 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1414 struct mysql_conn *cur;
1416 if ((config = ast_config_load(RES_CONFIG_MYSQL_CONF, config_flags)) == CONFIG_STATUS_FILEMISSING) {
1417 /* Support old config file name */
1418 config = ast_config_load(RES_CONFIG_MYSQL_CONF_OLD, config_flags);
1421 if (config == CONFIG_STATUS_FILEMISSING) {
1423 } else if (config == CONFIG_STATUS_FILEUNCHANGED) {
1425 } else if (config == CONFIG_STATUS_FILEINVALID) {
1426 ast_log(LOG_ERROR, "Not %sloading " RES_CONFIG_MYSQL_CONF "\n", reload ? "re" : "");
1429 AST_RWLIST_WRLOCK(&databases);
1430 for (catg = ast_category_browse(config, NULL); catg; catg = ast_category_browse(config, catg)) {
1431 /* Does this category already exist? */
1432 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1433 if (!strcmp(cur->unique_name, catg)) {
1439 if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(catg) + 1))) {
1440 ast_log(LOG_WARNING, "Could not allocate space for MySQL database '%s'\n", catg);
1444 strcpy(cur->unique_name, catg); /* SAFE */
1445 ast_mutex_init(&cur->lock);
1446 AST_RWLIST_INSERT_TAIL(&databases, cur, list);
1449 load_mysql_config(config, catg, cur);
1451 AST_RWLIST_UNLOCK(&databases);
1453 ast_config_destroy(config);
1458 static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn)
1462 if (!(s = ast_variable_retrieve(config, category, "dbuser"))) {
1463 ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n");
1466 ast_copy_string(conn->user, s, sizeof(conn->user));
1468 if (!(s = ast_variable_retrieve(config, category, "dbpass"))) {
1469 ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n");
1472 ast_copy_string(conn->pass, s, sizeof(conn->pass));
1474 if (!(s = ast_variable_retrieve(config, category, "dbhost"))) {
1475 ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n");
1478 ast_copy_string(conn->host, s, sizeof(conn->host));
1480 if (!(s = ast_variable_retrieve(config, category, "dbname"))) {
1481 ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n");
1484 ast_copy_string(conn->name, s, sizeof(conn->name));
1486 if (!(s = ast_variable_retrieve(config, category, "dbport"))) {
1487 ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n");
1490 conn->port = atoi(s);
1492 if (!(s = ast_variable_retrieve(config, category, "dbsock"))) {
1493 if (ast_strlen_zero(conn->host)) {
1494 char *paths[3] = { "/tmp/mysql.sock", "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock" };
1497 for (i = 0; i < 3; i++) {
1498 if (!stat(paths[i], &st)) {
1499 ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '%s' as default.\n", paths[i]);
1500 ast_copy_string(conn->sock, paths[i], sizeof(conn->sock));
1504 ast_log(LOG_WARNING, "MySQL RealTime: No database socket found (and unable to detect a suitable path).\n");
1509 ast_copy_string(conn->sock, s, sizeof(conn->sock));
1511 if (!(s = ast_variable_retrieve(config, category, "requirements"))) {
1512 ast_log(LOG_WARNING, "MySQL realtime: no requirements setting found, using 'warn' as default.\n");
1513 conn->requirements = RQ_WARN;
1514 } else if (!strcasecmp(s, "createclose")) {
1515 conn->requirements = RQ_CREATECLOSE;
1516 } else if (!strcasecmp(s, "createchar")) {
1517 conn->requirements = RQ_CREATECHAR;
1518 } else if (!strcasecmp(s, "warn")) {
1519 conn->requirements = RQ_WARN;
1521 ast_log(LOG_WARNING, "MySQL realtime: unrecognized requirements setting '%s', using 'warn'\n", s);
1522 conn->requirements = RQ_WARN;
1525 if (!ast_strlen_zero(conn->host)) {
1526 ast_debug(1, "MySQL RealTime host: %s\n", conn->host);
1527 ast_debug(1, "MySQL RealTime port: %i\n", conn->port);
1529 ast_debug(1, "MySQL RealTime socket: %s\n", conn->sock);
1530 ast_debug(1, "MySQL RealTime database name: %s\n", conn->name);
1531 ast_debug(1, "MySQL RealTime user: %s\n", conn->user);
1532 ast_debug(1, "MySQL RealTime password: %s\n", conn->pass);
1537 static int mysql_reconnect(struct mysql_conn *conn)
1539 #ifdef MYSQL_OPT_RECONNECT
1540 my_bool trueval = 1;
1543 /* mutex lock should have been locked before calling this function. */
1546 if ((!conn->connected) && (!ast_strlen_zero(conn->host) || conn->sock) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
1547 if (!mysql_init(&conn->handle)) {
1548 ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
1549 conn->connected = 0;
1552 if (mysql_real_connect(&conn->handle, conn->host, conn->user, conn->pass, conn->name, conn->port, conn->sock, 0)) {
1553 #ifdef MYSQL_OPT_RECONNECT
1554 /* The default is no longer to automatically reconnect on failure,
1555 * (as of 5.0.3) so we have to set that option here. */
1556 mysql_options(&conn->handle, MYSQL_OPT_RECONNECT, &trueval);
1558 ast_debug(1, "MySQL RealTime: Successfully connected to database.\n");
1559 conn->connected = 1;
1560 conn->connect_time = time(NULL);
1563 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));
1564 ast_debug(1, "MySQL RealTime: Cannot Connect (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
1565 conn->connected = 0;
1566 conn->connect_time = 0;
1570 /* MySQL likes to return an error, even if it reconnects successfully.
1571 * So the postman pings twice. */
1572 if (mysql_ping(&conn->handle) != 0 && (usleep(1) + 2 > 0) && mysql_ping(&conn->handle) != 0) {
1573 conn->connected = 0;
1574 conn->connect_time = 0;
1575 ast_log(LOG_ERROR, "MySQL RealTime: Ping failed (%d). Trying an explicit reconnect.\n", mysql_errno(&conn->handle));
1576 ast_debug(1, "MySQL RealTime: Server Error (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
1577 goto reconnect_tryagain;
1580 if (!conn->connected) {
1581 conn->connected = 1;
1582 conn->connect_time = time(NULL);
1585 if (mysql_select_db(&conn->handle, conn->name) != 0) {
1586 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));
1590 ast_debug(1, "MySQL RealTime: Connection okay.\n");
1595 static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1603 e->command = "realtime mysql cache";
1605 "Usage: realtime mysql cache [<database> <table>]\n"
1606 " Shows table cache for the MySQL RealTime driver\n";
1609 if (a->argc < 4 || a->argc > 5) {
1612 l = strlen(a->word);
1615 AST_LIST_LOCK(&mysql_tables);
1616 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1617 if (!strcasecmp(a->argv[3], cur->database->unique_name) && !strncasecmp(a->word, cur->name, l) && ++which > a->n) {
1618 ret = ast_strdup(cur->name);
1622 AST_LIST_UNLOCK(&mysql_tables);
1624 struct mysql_conn *cur;
1625 AST_RWLIST_RDLOCK(&databases);
1626 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1627 if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
1628 ret = ast_strdup(cur->unique_name);
1632 AST_RWLIST_UNLOCK(&databases);
1638 /* List of tables */
1639 AST_LIST_LOCK(&mysql_tables);
1640 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1641 ast_cli(a->fd, "%20.20s %s\n", cur->database->unique_name, cur->name);
1643 AST_LIST_UNLOCK(&mysql_tables);
1644 } else if (a->argc == 4) {
1646 /* List of tables */
1647 AST_LIST_LOCK(&mysql_tables);
1648 AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
1649 if (!strcasecmp(cur->database->unique_name, a->argv[3])) {
1650 ast_cli(a->fd, "%s\n", cur->name);
1654 AST_LIST_UNLOCK(&mysql_tables);
1656 ast_cli(a->fd, "No tables cached within %s database\n", a->argv[3]);
1658 } else if (a->argc == 5) {
1659 /* List of columns */
1660 if ((cur = find_table(a->argv[3], a->argv[4]))) {
1661 struct columns *col;
1662 ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[3]);
1663 ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s\n", "Name", "Type", "Len");
1664 AST_LIST_TRAVERSE(&cur->columns, col, list) {
1665 ast_cli(a->fd, "%-20.20s %-20.20s %3d\n", col->name, col->type, col->len);
1669 ast_cli(a->fd, "No such table '%s'\n", a->argv[3]);
1675 static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1677 char status[256], status2[100] = "", type[20];
1679 int ctime = 0, found = 0;
1680 struct mysql_conn *cur;
1681 int l = 0, which = 0;
1685 e->command = "realtime mysql status";
1687 "Usage: realtime mysql status [<database>]\n"
1688 " Shows connection information for the MySQL RealTime driver\n";
1692 AST_RWLIST_RDLOCK(&databases);
1693 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1694 if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
1695 ret = ast_strdup(cur->unique_name);
1699 AST_RWLIST_UNLOCK(&databases);
1705 return CLI_SHOWUSAGE;
1707 AST_RWLIST_RDLOCK(&databases);
1708 AST_RWLIST_TRAVERSE(&databases, cur, list) {
1709 if (a->argc == 3 || (a->argc == 4 && !strcasecmp(a->argv[3], cur->unique_name))) {
1712 if (mysql_reconnect(cur)) {
1713 snprintf(type, sizeof(type), "connected to");
1714 ctime = time(NULL) - cur->connect_time;
1716 snprintf(type, sizeof(type), "configured for");
1720 if (!ast_strlen_zero(cur->host)) {
1721 snprintf(status, sizeof(status), "%s %s %s@%s, port %d", cur->unique_name, type, cur->name, cur->host, cur->port);
1723 snprintf(status, sizeof(status), "%s %s %s on socket file %s", cur->unique_name, type, cur->name, cur->sock);
1726 if (!ast_strlen_zero(cur->user)) {
1727 snprintf(status2, sizeof(status2), " with username %s", cur->user);
1732 if (ctime > 31536000) {
1733 ast_cli(a->fd, "%s%s for %.1f years.\n", status, status2, (double)ctime / 31536000.0);
1734 } else if (ctime > 86400 * 30) {
1735 ast_cli(a->fd, "%s%s for %d days.\n", status, status2, ctime / 86400);
1736 } else if (ctime > 86400) {
1737 ast_cli(a->fd, "%s%s for %d days, %d hours.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600);
1738 } else if (ctime > 3600) {
1739 ast_cli(a->fd, "%s%s for %d hours, %d minutes.\n", status, status2, ctime / 3600, (ctime % 3600) / 60);
1740 } else if (ctime > 60) {
1741 ast_cli(a->fd, "%s%s for %d minutes.\n", status, status2, ctime / 60);
1742 } else if (ctime > -1) {
1743 ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
1745 ast_cli(a->fd, "%s%s.\n", status, status2);
1749 AST_RWLIST_UNLOCK(&databases);
1752 ast_cli(a->fd, "No connections configured.\n");
1757 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MySQL RealTime Configuration Driver",
1758 .load = load_module,
1759 .unload = unload_module,