2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (c) 2005, 2006 Tilghman Lesher
6 * Tilghman Lesher <func_odbc__200508@the-tilghman.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
24 * \author Tilghman Lesher <func_odbc__200508@the-tilghman.com>
30 <depend>unixodbc_or_iodbc</depend>
32 <depend>res_odbc</depend>
39 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
41 #include "asterisk/module.h"
42 #include "asterisk/file.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/pbx.h"
45 #include "asterisk/config.h"
46 #include "asterisk/res_odbc.h"
47 #include "asterisk/app.h"
49 static char *config = "func_odbc.conf";
52 OPT_ESCAPECOMMAS = (1 << 0),
53 OPT_MULTIROW = (1 << 1),
56 struct acf_odbc_query {
57 AST_RWLIST_ENTRY(acf_odbc_query) list;
58 char readhandle[5][30];
59 char writehandle[5][30];
64 struct ast_custom_function *acf;
67 static void odbc_datastore_free(void *data);
69 struct ast_datastore_info odbc_info = {
71 .destroy = odbc_datastore_free,
74 /* For storing each result row */
75 struct odbc_datastore_row {
76 AST_LIST_ENTRY(odbc_datastore_row) list;
80 /* For storing each result set */
81 struct odbc_datastore {
82 AST_LIST_HEAD(, odbc_datastore_row);
86 AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query);
88 static int resultcount = 0;
90 AST_THREADSTORAGE(coldata_buf);
91 AST_THREADSTORAGE(colnames_buf);
93 static void odbc_datastore_free(void *data)
95 struct odbc_datastore *result = data;
96 struct odbc_datastore_row *row;
97 AST_LIST_LOCK(result);
98 while ((row = AST_LIST_REMOVE_HEAD(result, list))) {
101 AST_LIST_UNLOCK(result);
102 AST_LIST_HEAD_DESTROY(result);
106 static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
112 res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
113 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
114 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
118 res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS);
119 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
120 ast_log(LOG_WARNING, "SQL Exec Direct failed![%s]\n", sql);
121 SQLCloseCursor(stmt);
122 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
130 * Master control routine
132 static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
134 struct odbc_obj *obj = NULL;
135 struct acf_odbc_query *query;
136 char *t, varname[15];
137 int i, dsn, bogus_chan = 0;
138 AST_DECLARE_APP_ARGS(values,
139 AST_APP_ARG(field)[100];
141 AST_DECLARE_APP_ARGS(args,
142 AST_APP_ARG(field)[100];
144 SQLHSTMT stmt = NULL;
146 struct ast_str *buf = ast_str_create(16);
152 AST_RWLIST_RDLOCK(&queries);
153 AST_RWLIST_TRAVERSE(&queries, query, list) {
154 if (!strcmp(query->acf->name, cmd)) {
160 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
161 AST_RWLIST_UNLOCK(&queries);
167 if ((chan = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/func_odbc")))
172 ast_autoservice_start(chan);
174 ast_str_make_space(&buf, strlen(query->sql_write) * 2);
176 /* Parse our arguments */
177 t = value ? ast_strdupa(value) : "";
180 ast_log(LOG_ERROR, "Out of memory\n");
181 AST_RWLIST_UNLOCK(&queries);
183 ast_autoservice_stop(chan);
185 ast_channel_free(chan);
190 AST_STANDARD_APP_ARGS(args, s);
191 for (i = 0; i < args.argc; i++) {
192 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
193 pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
196 /* Parse values, just like arguments */
197 AST_STANDARD_APP_ARGS(values, t);
198 for (i = 0; i < values.argc; i++) {
199 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
200 pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
203 /* Additionally set the value as a whole (but push an empty string if value is NULL) */
204 pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : "");
206 pbx_substitute_variables_helper(chan, query->sql_write, buf->str, buf->len - 1);
208 /* Restore prior values */
209 for (i = 0; i < args.argc; i++) {
210 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
211 pbx_builtin_setvar_helper(chan, varname, NULL);
214 for (i = 0; i < values.argc; i++) {
215 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
216 pbx_builtin_setvar_helper(chan, varname, NULL);
218 pbx_builtin_setvar_helper(chan, "VALUE", NULL);
220 AST_RWLIST_UNLOCK(&queries);
222 for (dsn = 0; dsn < 5; dsn++) {
223 if (!ast_strlen_zero(query->writehandle[dsn])) {
224 obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
226 stmt = ast_odbc_direct_execute(obj, generic_execute, buf);
234 SQLRowCount(stmt, &rows);
237 /* Output the affected rows, for all cases. In the event of failure, we
238 * flag this as -1 rows. Note that this is different from 0 affected rows
239 * which would be the case if we succeeded in our query, but the values did
241 snprintf(varname, sizeof(varname), "%d", (int)rows);
242 pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
245 SQLCloseCursor(stmt);
246 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
249 ast_odbc_release_obj(obj);
252 ast_autoservice_stop(chan);
254 ast_channel_free(chan);
260 static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len)
262 struct odbc_obj *obj = NULL;
263 struct acf_odbc_query *query;
264 char varname[15], rowcount[12] = "-1";
265 struct ast_str *colnames = ast_str_thread_get(&colnames_buf, 16);
266 int res, x, y, buflen = 0, escapecommas, rowlimit = 1, dsn, bogus_chan = 0;
267 AST_DECLARE_APP_ARGS(args,
268 AST_APP_ARG(field)[100];
270 SQLHSTMT stmt = NULL;
271 SQLSMALLINT colcount=0;
273 SQLSMALLINT collength;
274 struct odbc_datastore *resultset = NULL;
275 struct odbc_datastore_row *row = NULL;
276 struct ast_str *sql = ast_str_create(16);
282 AST_RWLIST_RDLOCK(&queries);
283 AST_RWLIST_TRAVERSE(&queries, query, list) {
284 if (!strcmp(query->acf->name, cmd)) {
290 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
291 AST_RWLIST_UNLOCK(&queries);
292 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
298 if ((chan = ast_channel_alloc(0, 0, "", "", "", "", "", 0, "Bogus/func_odbc")))
303 ast_autoservice_start(chan);
305 AST_STANDARD_APP_ARGS(args, s);
306 for (x = 0; x < args.argc; x++) {
307 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
308 pbx_builtin_pushvar_helper(chan, varname, args.field[x]);
311 ast_str_make_space(&sql, strlen(query->sql_read) * 2);
312 pbx_substitute_variables_helper(chan, query->sql_read, sql->str, sql->len - 1);
314 /* Restore prior values */
315 for (x = 0; x < args.argc; x++) {
316 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
317 pbx_builtin_setvar_helper(chan, varname, NULL);
320 /* Save these flags, so we can release the lock */
321 escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS);
322 if (ast_test_flag(query, OPT_MULTIROW)) {
323 resultset = ast_calloc(1, sizeof(*resultset));
324 AST_LIST_HEAD_INIT(resultset);
326 rowlimit = query->rowlimit;
330 AST_RWLIST_UNLOCK(&queries);
332 for (dsn = 0; dsn < 5; dsn++) {
333 if (!ast_strlen_zero(query->readhandle[dsn])) {
334 obj = ast_odbc_request_obj(query->readhandle[dsn], 0);
336 stmt = ast_odbc_direct_execute(obj, generic_execute, sql->str);
343 ast_log(LOG_ERROR, "Unable to execute query [%s]\n", sql->str);
345 ast_odbc_release_obj(obj);
346 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
348 ast_autoservice_stop(chan);
350 ast_channel_free(chan);
355 res = SQLNumResultCols(stmt, &colcount);
356 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
357 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql->str);
358 SQLCloseCursor(stmt);
359 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
360 ast_odbc_release_obj(obj);
361 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
363 ast_autoservice_stop(chan);
365 ast_channel_free(chan);
370 res = SQLFetch(stmt);
371 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
373 if (res == SQL_NO_DATA) {
374 ast_verb(4, "Found no rows [%s]\n", sql->str);
377 ast_copy_string(rowcount, "0", sizeof(rowcount));
379 ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql->str);
381 SQLCloseCursor(stmt);
382 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
383 ast_odbc_release_obj(obj);
384 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
386 ast_autoservice_stop(chan);
388 ast_channel_free(chan);
393 for (y = 0; y < rowlimit; y++) {
394 for (x = 0; x < colcount; x++) {
396 struct ast_str *coldata = ast_str_thread_get(&coldata_buf, 16);
402 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, &maxcol, NULL, NULL);
403 ast_debug(3, "Got collength of %d and maxcol of %d for column '%s' (offset %d)\n", (int)collength, (int)maxcol, colname, x);
404 if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
405 snprintf(colname, sizeof(colname), "field%d", x);
408 if (coldata->len < maxcol + 1) {
409 ast_str_make_space(&coldata, maxcol + 1);
412 if (colnames->used) {
413 ast_str_append(&colnames, 0, ",");
415 ast_str_make_space(&colnames, strlen(colname) * 2 + 1 + colnames->used);
417 /* Copy data, encoding '\' and ',' for the argument parser */
418 for (i = 0; i < sizeof(colname); i++) {
419 if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) {
420 colnames->str[colnames->used++] = '\\';
422 colnames->str[colnames->used++] = colname[i];
424 if (colname[i] == '\0') {
430 void *tmp = ast_realloc(resultset, sizeof(*resultset) + colnames->used + 1);
432 ast_log(LOG_ERROR, "No space for a new resultset?\n");
434 SQLCloseCursor(stmt);
435 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
436 ast_odbc_release_obj(obj);
437 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
439 ast_autoservice_stop(chan);
441 ast_channel_free(chan);
446 strcpy((char *)resultset + sizeof(*resultset), colnames->str);
450 buflen = strlen(buf);
451 res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata->str, coldata->len, &indicator);
452 if (indicator == SQL_NULL_DATA) {
453 ast_debug(3, "Got NULL data\n");
454 ast_str_reset(coldata);
458 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
459 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql->str);
465 ast_debug(2, "Got coldata of '%s'\n", coldata->str);
466 coldata->used = strlen(coldata->str);
468 /* Copy data, encoding '\' and ',' for the argument parser */
469 for (i = 0; i < coldata->used; i++) {
470 if (escapecommas && (coldata->str[i] == '\\' || coldata->str[i] == ',')) {
471 buf[buflen++] = '\\';
473 buf[buflen++] = coldata->str[i];
475 if (buflen >= len - 2)
478 if (coldata->str[i] == '\0')
484 ast_debug(2, "buf is now set to '%s'\n", buf);
486 /* Trim trailing comma */
487 buf[buflen - 1] = '\0';
488 ast_debug(2, "buf is now set to '%s'\n", buf);
491 row = ast_calloc(1, sizeof(*row) + buflen);
493 ast_log(LOG_ERROR, "Unable to allocate space for more rows in this resultset.\n");
496 strcpy((char *)row + sizeof(*row), buf);
497 AST_LIST_INSERT_TAIL(resultset, row, list);
500 res = SQLFetch(stmt);
501 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
502 if (res != SQL_NO_DATA)
503 ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql->str);
511 snprintf(rowcount, sizeof(rowcount), "%d", y);
512 pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
513 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames->str);
516 struct ast_datastore *odbc_store;
517 uid = ast_atomic_fetchadd_int(&resultcount, +1) + 1;
518 snprintf(buf, len, "%d", uid);
519 odbc_store = ast_datastore_alloc(&odbc_info, buf);
521 ast_log(LOG_ERROR, "Rows retrieved, but unable to store it in the channel. Results fail.\n");
522 odbc_datastore_free(resultset);
523 SQLCloseCursor(stmt);
524 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
525 ast_odbc_release_obj(obj);
527 ast_autoservice_stop(chan);
529 ast_channel_free(chan);
533 odbc_store->data = resultset;
534 ast_channel_datastore_add(chan, odbc_store);
536 SQLCloseCursor(stmt);
537 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
538 ast_odbc_release_obj(obj);
540 ast_autoservice_stop(chan);
542 ast_channel_free(chan);
547 static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
551 for (; *data && out - buf < len; data++) {
563 static struct ast_custom_function escape_function = {
565 .synopsis = "Escapes single ticks for use in SQL statements",
566 .syntax = "SQL_ESC(<string>)",
568 "Used in SQL templates to escape data which may contain single ticks (') which\n"
569 "are otherwise used to delimit data. For example:\n"
570 "SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n",
575 static int acf_fetch(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
577 struct ast_datastore *store;
578 struct odbc_datastore *resultset;
579 struct odbc_datastore_row *row;
580 store = ast_channel_datastore_find(chan, &odbc_info, data);
582 pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "FAILURE");
585 resultset = store->data;
586 AST_LIST_LOCK(resultset);
587 row = AST_LIST_REMOVE_HEAD(resultset, list);
588 AST_LIST_UNLOCK(resultset);
590 /* Cleanup datastore */
591 ast_channel_datastore_remove(chan, store);
592 ast_datastore_free(store);
593 pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "FAILURE");
596 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", resultset->names);
597 ast_copy_string(buf, row->data, len);
599 pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "SUCCESS");
603 static struct ast_custom_function fetch_function = {
604 .name = "ODBC_FETCH",
605 .synopsis = "Fetch a row from a multirow query",
606 .syntax = "ODBC_FETCH(<result-id>)",
608 "For queries which are marked as mode=multirow, the original query returns a\n"
609 "result-id from which results may be fetched. This function implements the\n"
610 "actual fetch of the results.\n"
611 "This function also sets ODBC_FETCH_STATUS to one of \"SUCCESS\" or \"FAILURE\",\n"
612 "depending upon whether there were rows available or not.\n",
617 static char *app_odbcfinish = "ODBCFinish";
618 static char *syn_odbcfinish = "Clear the resultset of a successful multirow query";
619 static char *desc_odbcfinish =
620 "ODBCFinish(<result-id>)\n"
621 " Clears any remaining rows of the specified resultset\n";
624 static int exec_odbcfinish(struct ast_channel *chan, void *data)
626 struct ast_datastore *store = ast_channel_datastore_find(chan, &odbc_info, data);
627 if (!store) /* Already freed; no big deal. */
629 ast_channel_datastore_remove(chan, store);
630 ast_datastore_free(store);
634 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
643 *query = ast_calloc(1, sizeof(struct acf_odbc_query));
647 if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
648 char *tmp2 = ast_strdupa(tmp);
649 AST_DECLARE_APP_ARGS(writeconf,
652 AST_STANDARD_APP_ARGS(writeconf, tmp2);
653 for (i = 0; i < 5; i++) {
654 if (!ast_strlen_zero(writeconf.dsn[i]))
655 ast_copy_string((*query)->writehandle[i], writeconf.dsn[i], sizeof((*query)->writehandle[i]));
659 if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
660 char *tmp2 = ast_strdupa(tmp);
661 AST_DECLARE_APP_ARGS(readconf,
664 AST_STANDARD_APP_ARGS(readconf, tmp2);
665 for (i = 0; i < 5; i++) {
666 if (!ast_strlen_zero(readconf.dsn[i]))
667 ast_copy_string((*query)->readhandle[i], readconf.dsn[i], sizeof((*query)->readhandle[i]));
670 /* If no separate readhandle, then use the writehandle for reading */
671 for (i = 0; i < 5; i++) {
672 if (!ast_strlen_zero((*query)->writehandle[i]))
673 ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
677 if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")))
678 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
679 else if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
680 ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s. Please use 'readsql' instead.\n", catg);
681 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
684 if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) {
687 ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
691 if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")))
692 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
693 else if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
694 ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s. Please use 'writesql' instead.\n", catg);
695 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
698 if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) {
701 ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
705 /* Allow escaping of embedded commas in fields to be turned off */
706 ast_set_flag((*query), OPT_ESCAPECOMMAS);
707 if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
709 ast_clear_flag((*query), OPT_ESCAPECOMMAS);
712 if ((tmp = ast_variable_retrieve(cfg, catg, "mode"))) {
713 if (strcasecmp(tmp, "multirow") == 0)
714 ast_set_flag((*query), OPT_MULTIROW);
715 if ((tmp = ast_variable_retrieve(cfg, catg, "rowlimit")))
716 sscanf(tmp, "%d", &((*query)->rowlimit));
719 (*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
720 if (! (*query)->acf) {
726 if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
727 asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg);
729 asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg);
732 if (!((*query)->acf->name)) {
733 ast_free((*query)->acf);
739 if ((tmp = ast_variable_retrieve(cfg, catg, "syntax")) && !ast_strlen_zero(tmp)) {
740 asprintf((char **)&((*query)->acf->syntax), "%s(%s)", (*query)->acf->name, tmp);
742 asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
745 if (!((*query)->acf->syntax)) {
746 ast_free((char *)(*query)->acf->name);
747 ast_free((*query)->acf);
753 if ((tmp = ast_variable_retrieve(cfg, catg, "synopsis")) && !ast_strlen_zero(tmp)) {
754 (*query)->acf->synopsis = ast_strdup(tmp);
756 (*query)->acf->synopsis = ast_strdup("Runs the referenced query with the specified arguments");
759 if (!((*query)->acf->synopsis)) {
760 ast_free((char *)(*query)->acf->name);
761 ast_free((char *)(*query)->acf->syntax);
762 ast_free((*query)->acf);
768 if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) {
769 asprintf((char **)&((*query)->acf->desc),
770 "Runs the following query, as defined in func_odbc.conf, performing\n"
771 "substitution of the arguments into the query as specified by ${ARG1},\n"
772 "${ARG2}, ... ${ARGn}. When setting the function, the values are provided\n"
773 "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
774 "\nRead:\n%s\n\nWrite:\n%s\n",
776 (*query)->sql_write);
777 } else if (!ast_strlen_zero((*query)->sql_read)) {
778 asprintf((char **)&((*query)->acf->desc),
779 "Runs the following query, as defined in func_odbc.conf, performing\n"
780 "substitution of the arguments into the query as specified by ${ARG1},\n"
781 "${ARG2}, ... ${ARGn}. This function may only be read, not set.\n\nSQL:\n%s\n",
783 } else if (!ast_strlen_zero((*query)->sql_write)) {
784 asprintf((char **)&((*query)->acf->desc),
785 "Runs the following query, as defined in func_odbc.conf, performing\n"
786 "substitution of the arguments into the query as specified by ${ARG1},\n"
787 "${ARG2}, ... ${ARGn}. The values are provided either in whole as\n"
788 "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
789 "This function may only be set.\nSQL:\n%s\n",
790 (*query)->sql_write);
792 ast_free((char *)(*query)->acf->synopsis);
793 ast_free((char *)(*query)->acf->syntax);
794 ast_free((char *)(*query)->acf->name);
795 ast_free((*query)->acf);
797 ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute. Ignoring.\n", catg);
801 if (! ((*query)->acf->desc)) {
802 ast_free((char *)(*query)->acf->synopsis);
803 ast_free((char *)(*query)->acf->syntax);
804 ast_free((char *)(*query)->acf->name);
805 ast_free((*query)->acf);
811 if (ast_strlen_zero((*query)->sql_read)) {
812 (*query)->acf->read = NULL;
814 (*query)->acf->read = acf_odbc_read;
817 if (ast_strlen_zero((*query)->sql_write)) {
818 (*query)->acf->write = NULL;
820 (*query)->acf->write = acf_odbc_write;
826 static int free_acf_query(struct acf_odbc_query *query)
830 if (query->acf->name)
831 ast_free((char *)query->acf->name);
832 if (query->acf->syntax)
833 ast_free((char *)query->acf->syntax);
834 if (query->acf->synopsis)
835 ast_free((char *)query->acf->synopsis);
836 if (query->acf->desc)
837 ast_free((char *)query->acf->desc);
838 ast_free(query->acf);
845 static int load_module(void)
848 struct ast_config *cfg;
850 struct ast_flags config_flags = { 0 };
852 res |= ast_custom_function_register(&fetch_function);
853 res |= ast_register_application(app_odbcfinish, exec_odbcfinish, syn_odbcfinish, desc_odbcfinish);
854 AST_RWLIST_WRLOCK(&queries);
856 cfg = ast_config_load(config, config_flags);
857 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
858 ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
859 AST_RWLIST_UNLOCK(&queries);
860 return AST_MODULE_LOAD_DECLINE;
863 for (catg = ast_category_browse(cfg, NULL);
865 catg = ast_category_browse(cfg, catg)) {
866 struct acf_odbc_query *query = NULL;
869 if ((err = init_acf_query(cfg, catg, &query))) {
871 ast_log(LOG_ERROR, "Out of memory\n");
872 else if (err == EINVAL)
873 ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
875 ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
877 AST_RWLIST_INSERT_HEAD(&queries, query, list);
878 ast_custom_function_register(query->acf);
882 ast_config_destroy(cfg);
883 res |= ast_custom_function_register(&escape_function);
885 AST_RWLIST_UNLOCK(&queries);
889 static int unload_module(void)
891 struct acf_odbc_query *query;
894 AST_RWLIST_WRLOCK(&queries);
895 while (!AST_RWLIST_EMPTY(&queries)) {
896 query = AST_RWLIST_REMOVE_HEAD(&queries, list);
897 ast_custom_function_unregister(query->acf);
898 free_acf_query(query);
901 res |= ast_custom_function_unregister(&escape_function);
902 res |= ast_custom_function_unregister(&fetch_function);
903 res |= ast_unregister_application(app_odbcfinish);
905 /* Allow any threads waiting for this lock to pass (avoids a race) */
906 AST_RWLIST_UNLOCK(&queries);
908 AST_RWLIST_WRLOCK(&queries);
910 AST_RWLIST_UNLOCK(&queries);
914 static int reload(void)
917 struct ast_config *cfg;
918 struct acf_odbc_query *oldquery;
920 struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
922 cfg = ast_config_load(config, config_flags);
923 if (cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID)
926 AST_RWLIST_WRLOCK(&queries);
928 while (!AST_RWLIST_EMPTY(&queries)) {
929 oldquery = AST_RWLIST_REMOVE_HEAD(&queries, list);
930 ast_custom_function_unregister(oldquery->acf);
931 free_acf_query(oldquery);
935 ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
939 for (catg = ast_category_browse(cfg, NULL);
941 catg = ast_category_browse(cfg, catg)) {
942 struct acf_odbc_query *query = NULL;
944 if (init_acf_query(cfg, catg, &query)) {
945 ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
947 AST_RWLIST_INSERT_HEAD(&queries, query, list);
948 ast_custom_function_register(query->acf);
952 ast_config_destroy(cfg);
954 AST_RWLIST_UNLOCK(&queries);
958 /* XXX need to revise usecount - set if query_lock is set */
960 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",
962 .unload = unload_module,