simplify autoconfig include mechanism (make tholo happy he can use lint again :-)
[asterisk/asterisk.git] / funcs / func_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (c) 2005, 2006 Tilghman Lesher
5  *
6  * Tilghman Lesher <func_odbc__200508@the-tilghman.com>
7  *
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.
13  *
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.
17  */
18
19 /*!
20  * \file
21  *
22  * \brief ODBC lookups
23  *
24  * \author Tilghman Lesher <func_odbc__200508@the-tilghman.com>
25  */
26
27 /*** MODULEINFO
28         <depend>unixodbc</depend>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <string.h>
40
41 #include "asterisk/module.h"
42 #include "asterisk/file.h"
43 #include "asterisk/logger.h"
44 #include "asterisk/options.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/module.h"
48 #include "asterisk/config.h"
49 #include "asterisk/res_odbc.h"
50 #include "asterisk/app.h"
51
52 static char *tdesc = "ODBC lookups";
53
54 static char *config = "func_odbc.conf";
55
56 enum {
57         OPT_ESCAPECOMMAS =      (1 << 0),
58 } odbc_option_flags;
59
60 struct acf_odbc_query {
61         AST_LIST_ENTRY(acf_odbc_query) list;
62         char dsn[30];
63         char sql_read[2048];
64         char sql_write[2048];
65         unsigned int flags;
66         struct ast_custom_function *acf;
67 };
68
69 AST_LIST_HEAD_STATIC(queries, acf_odbc_query);
70
71 #ifdef NEEDTRACE
72 static void acf_odbc_error(SQLHSTMT stmt, int res)
73 {
74         char state[10] = "", diagnostic[256] = "";
75         SQLINTEGER nativeerror = 0;
76         SQLSMALLINT diagbytes = 0;
77         SQLGetDiagRec(SQL_HANDLE_STMT, stmt, 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
78         ast_log(LOG_WARNING, "SQL return value %d: error %s: %s (len %d)\n", res, state, diagnostic, diagbytes);
79 }
80 #endif
81
82 /*
83  * Master control routine
84  */
85 static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const char *value)
86 {
87         struct odbc_obj *obj;
88         struct acf_odbc_query *query;
89         char *t, buf[2048]="", varname[15];
90         int res, i, retry=0;
91         AST_DECLARE_APP_ARGS(values,
92                 AST_APP_ARG(field)[100];
93         );
94         AST_DECLARE_APP_ARGS(args,
95                 AST_APP_ARG(field)[100];
96         );
97         SQLHSTMT stmt;
98         SQLINTEGER nativeerror=0, numfields=0, rows=0;
99         SQLSMALLINT diagbytes=0;
100         unsigned char state[10], diagnostic[256];
101 #ifdef NEEDTRACE
102         SQLINTEGER enable = 1;
103         char *tracefile = "/tmp/odbc.trace";
104 #endif
105
106         AST_LIST_LOCK(&queries);
107         AST_LIST_TRAVERSE(&queries, query, list) {
108                 if (!strcmp(query->acf->name, cmd)) {
109                         break;
110                 }
111         }
112
113         if (!query) {
114                 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
115                 AST_LIST_UNLOCK(&queries);
116                 return -1;
117         }
118
119         obj = odbc_request_obj(query->dsn, 0);
120
121         if (!obj) {
122                 ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn);
123                 AST_LIST_UNLOCK(&queries);
124                 return -1;
125         }
126
127         /* Parse our arguments */
128         t = value ? ast_strdupa(value) : "";
129
130         if (!s || !t) {
131                 ast_log(LOG_ERROR, "Out of memory\n");
132                 AST_LIST_UNLOCK(&queries);
133                 return -1;
134         }
135
136         AST_STANDARD_APP_ARGS(args, s);
137         for (i = 0; i < args.argc; i++) {
138                 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
139                 pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
140         }
141
142         /* Parse values, just like arguments */
143         /* Can't use the pipe, because app Set removes them */
144         AST_NONSTANDARD_APP_ARGS(values, t, ',');
145         for (i = 0; i < values.argc; i++) {
146                 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
147                 pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
148         }
149
150         /* Additionally set the value as a whole (but push an empty string if value is NULL) */
151         pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : "");
152
153         pbx_substitute_variables_helper(chan, query->sql_write, buf, sizeof(buf) - 1);
154
155         /* Restore prior values */
156         for (i = 0; i < args.argc; i++) {
157                 snprintf(varname, sizeof(varname), "ARG%d", i + 1);
158                 pbx_builtin_setvar_helper(chan, varname, NULL);
159         }
160
161         for (i = 0; i < values.argc; i++) {
162                 snprintf(varname, sizeof(varname), "VAL%d", i + 1);
163                 pbx_builtin_setvar_helper(chan, varname, NULL);
164         }
165         pbx_builtin_setvar_helper(chan, "VALUE", NULL);
166
167         AST_LIST_UNLOCK(&queries);
168
169 retry_write:
170 #ifdef NEEDTRACE
171         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
172         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
173 #endif
174
175         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
176         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
177                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
178                 pbx_builtin_setvar_helper(chan, "ODBCROWS", "-1");
179                 return -1;
180         }
181
182         res = SQLPrepare(stmt, (unsigned char *)buf, SQL_NTS);
183         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
184                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", buf);
185                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
186                 pbx_builtin_setvar_helper(chan, "ODBCROWS", "-1");
187                 return -1;
188         }
189
190         res = SQLExecute(stmt);
191         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
192                 if (res == SQL_ERROR) {
193                         SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
194                         for (i = 0; i <= numfields; i++) {
195                                 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
196                                 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
197                                 if (i > 10) {
198                                         ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
199                                         break;
200                                 }
201                         }
202                 }
203                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
204                 odbc_release_obj(obj);
205                 /* All handles are now invalid (after a disconnect), so we gotta redo all handles */
206                 obj = odbc_request_obj(query->dsn, 1);
207                 if (!retry) {
208                         retry = 1;
209                         goto retry_write;
210                 }
211                 rows = -1;
212         } else {
213                 /* Rows affected */
214                 SQLRowCount(stmt, &rows);
215         }
216
217         /* Output the affected rows, for all cases.  In the event of failure, we
218          * flag this as -1 rows.  Note that this is different from 0 affected rows
219          * which would be the case if we succeeded in our query, but the values did
220          * not change. */
221         snprintf(varname, sizeof(varname), "%d", (int)rows);
222         pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
223
224         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
225                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", buf);
226         }
227
228         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
229
230         return 0;
231 }
232
233 static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf, size_t len)
234 {
235         struct odbc_obj *obj;
236         struct acf_odbc_query *query;
237         char sql[2048] = "", varname[15];
238         int res, x, buflen = 0, escapecommas;
239         AST_DECLARE_APP_ARGS(args,
240                 AST_APP_ARG(field)[100];
241         );
242         SQLHSTMT stmt;
243         SQLSMALLINT colcount=0;
244         SQLINTEGER indicator;
245 #ifdef NEEDTRACE
246         SQLINTEGER enable = 1;
247         char *tracefile = "/tmp/odbc.trace";
248 #endif
249
250         AST_LIST_LOCK(&queries);
251         AST_LIST_TRAVERSE(&queries, query, list) {
252                 if (!strcmp(query->acf->name, cmd)) {
253                         break;
254                 }
255         }
256
257         if (!query) {
258                 ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
259                 AST_LIST_UNLOCK(&queries);
260                 return -1;
261         }
262
263         obj = odbc_request_obj(query->dsn, 0);
264
265         if (!obj) {
266                 ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn);
267                 AST_LIST_UNLOCK(&queries);
268                 return -1;
269         }
270
271 #ifdef NEEDTRACE
272         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
273         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
274 #endif
275
276         AST_STANDARD_APP_ARGS(args, s);
277         for (x = 0; x < args.argc; x++) {
278                 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
279                 pbx_builtin_pushvar_helper(chan, varname, args.field[x]);
280         }
281
282         pbx_substitute_variables_helper(chan, query->sql_read, sql, sizeof(sql) - 1);
283
284         /* Restore prior values */
285         for (x = 0; x < args.argc; x++) {
286                 snprintf(varname, sizeof(varname), "ARG%d", x + 1);
287                 pbx_builtin_setvar_helper(chan, varname, NULL);
288         }
289
290         /* Save this flag, so we can release the lock */
291         escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS);
292
293         AST_LIST_UNLOCK(&queries);
294
295         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
296         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
297                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
298                 return -1;
299         }
300
301         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
302         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
303                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
304                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
305                 return -1;
306         }
307
308         res = odbc_smart_execute(obj, stmt);
309         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
310                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
311                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
312                 return -1;
313         }
314
315         res = SQLNumResultCols(stmt, &colcount);
316         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
317                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
318                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
319                 return -1;
320         }
321
322         *buf = '\0';
323
324         res = SQLFetch(stmt);
325         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
326                 if (res == SQL_NO_DATA) {
327                         if (option_verbose > 3) {
328                                 ast_verbose(VERBOSE_PREFIX_4 "Found no rows [%s]\n", sql);
329                         }
330                 } else if (option_verbose > 3) {
331                         ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql);
332                 }
333                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
334                 return 0;
335         }
336
337         for (x = 0; x < colcount; x++) {
338                 int i;
339                 char coldata[256];
340
341                 buflen = strlen(buf);
342                 res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator);
343                 if (indicator == SQL_NULL_DATA) {
344                         coldata[0] = '\0';
345                         res = SQL_SUCCESS;
346                 }
347
348                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
349                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
350                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
351                         return -1;
352                 }
353
354                 /* Copy data, encoding '\' and ',' for the argument parser */
355                 for (i = 0; i < sizeof(coldata); i++) {
356                         if (escapecommas && (coldata[i] == '\\' || coldata[i] == ',')) {
357                                 buf[buflen++] = '\\';
358                         }
359                         buf[buflen++] = coldata[i];
360
361                         if (buflen >= len - 2)
362                                 break;
363
364                         if (coldata[i] == '\0')
365                                 break;
366                 }
367
368                 buf[buflen - 1] = ',';
369         }
370         /* Trim trailing comma */
371         buf[buflen - 1] = '\0';
372
373         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
374         return 0;
375 }
376
377 static int acf_escape(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
378 {
379         char *out = buf;
380
381         for (; *data && out - buf < len; data++) {
382                 if (*data == '\'') {
383                         *out = '\'';
384                         out++;
385                 }
386                 *out++ = *data;
387         }
388         *out = '\0';
389
390         return 0;
391 }
392
393 static struct ast_custom_function escape_function = {
394         .name = "SQL_ESC",
395         .synopsis = "Escapes single ticks for use in SQL statements",
396         .syntax = "SQL_ESC(<string>)",
397         .desc =
398 "Used in SQL templates to escape data which may contain single ticks (') which\n"
399 "are otherwise used to delimit data.  For example:\n"
400 "SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n",
401         .read = acf_escape,
402         .write = NULL,
403 };
404
405 static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
406 {
407         char *tmp;
408
409         if (!cfg || !catg) {
410                 return -1;
411         }
412
413         *query = ast_calloc(1, sizeof(struct acf_odbc_query));
414         if (! (*query))
415                 return -1;
416
417         if ((tmp = ast_variable_retrieve(cfg, catg, "dsn"))) {
418                 ast_copy_string((*query)->dsn, tmp, sizeof((*query)->dsn));
419         } else {
420                 return -1;
421         }
422
423         if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) {
424                 ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read));
425         }
426
427         if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) {
428                 ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write));
429         }
430
431         /* Allow escaping of embedded commas in fields to be turned off */
432         ast_set_flag((*query), OPT_ESCAPECOMMAS);
433         if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
434                 if (ast_false(tmp))
435                         ast_clear_flag((*query), OPT_ESCAPECOMMAS);
436         }
437
438         (*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
439         if (! (*query)->acf) {
440                 free(*query);
441                 return -1;
442         }
443
444         if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
445                 asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg);
446         } else {
447                 asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg);
448         }
449
450         if (!((*query)->acf->name)) {
451                 free((*query)->acf);
452                 free(*query);
453                 return -1;
454         }
455
456         asprintf((char **)&((*query)->acf->syntax), "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
457
458         if (!((*query)->acf->syntax)) {
459                 free((char *)(*query)->acf->name);
460                 free((*query)->acf);
461                 free(*query);
462                 return -1;
463         }
464
465         (*query)->acf->synopsis = "Runs the referenced query with the specified arguments";
466         if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) {
467                 asprintf((char **)&((*query)->acf->desc),
468                                         "Runs the following query, as defined in func_odbc.conf, performing\n"
469                                         "substitution of the arguments into the query as specified by ${ARG1},\n"
470                                         "${ARG2}, ... ${ARGn}.  When setting the function, the values are provided\n"
471                                         "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
472                                         "\nRead:\n%s\n\nWrite:\n%s\n",
473                                         (*query)->sql_read,
474                                         (*query)->sql_write);
475         } else if (!ast_strlen_zero((*query)->sql_read)) {
476                 asprintf((char **)&((*query)->acf->desc),
477                                         "Runs the following query, as defined in func_odbc.conf, performing\n"
478                                         "substitution of the arguments into the query as specified by ${ARG1},\n"
479                                         "${ARG2}, ... ${ARGn}.  This function may only be read, not set.\n\nSQL:\n%s\n",
480                                         (*query)->sql_read);
481         } else if (!ast_strlen_zero((*query)->sql_write)) {
482                 asprintf((char **)&((*query)->acf->desc),
483                                         "Runs the following query, as defined in func_odbc.conf, performing\n"
484                                         "substitution of the arguments into the query as specified by ${ARG1},\n"
485                                         "${ARG2}, ... ${ARGn}.  The values are provided either in whole as\n"
486                                         "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
487                                         "This function may only be set.\nSQL:\n%s\n",
488                                         (*query)->sql_write);
489         }
490
491         /* Could be out of memory, or could be we have neither sql_read nor sql_write */
492         if (! ((*query)->acf->desc)) {
493                 free((char *)(*query)->acf->syntax);
494                 free((char *)(*query)->acf->name);
495                 free((*query)->acf);
496                 free(*query);
497                 return -1;
498         }
499
500         if (ast_strlen_zero((*query)->sql_read)) {
501                 (*query)->acf->read = NULL;
502         } else {
503                 (*query)->acf->read = acf_odbc_read;
504         }
505
506         if (ast_strlen_zero((*query)->sql_write)) {
507                 (*query)->acf->write = NULL;
508         } else {
509                 (*query)->acf->write = acf_odbc_write;
510         }
511
512         return 0;
513 }
514
515 static int free_acf_query(struct acf_odbc_query *query)
516 {
517         if (query) {
518                 if (query->acf) {
519                         if (query->acf->name)
520                                 free((char *)query->acf->name);
521                         if (query->acf->syntax)
522                                 free((char *)query->acf->syntax);
523                         if (query->acf->desc)
524                                 free((char *)query->acf->desc);
525                         free(query->acf);
526                 }
527                 free(query);
528         }
529         return 0;
530 }
531
532 static int odbc_load_module(void)
533 {
534         int res = 0;
535         struct ast_config *cfg;
536         char *catg;
537
538         AST_LIST_LOCK(&queries);
539
540         cfg = ast_config_load(config);
541         if (!cfg) {
542                 ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
543                 AST_LIST_UNLOCK(&queries);
544                 return 0;
545         }
546
547         for (catg = ast_category_browse(cfg, NULL);
548              catg;
549              catg = ast_category_browse(cfg, catg)) {
550                 struct acf_odbc_query *query = NULL;
551
552                 if (init_acf_query(cfg, catg, &query)) {
553                         ast_log(LOG_ERROR, "Out of memory\n");
554                         free_acf_query(query);
555                 } else {
556                         AST_LIST_INSERT_HEAD(&queries, query, list);
557                         ast_custom_function_register(query->acf);
558                 }
559         }
560
561         ast_config_destroy(cfg);
562         ast_custom_function_register(&escape_function);
563
564         AST_LIST_UNLOCK(&queries);
565         return res;
566 }
567
568 static int odbc_unload_module(void)
569 {
570         struct acf_odbc_query *query;
571
572         AST_LIST_LOCK(&queries);
573         while (!AST_LIST_EMPTY(&queries)) {
574                 query = AST_LIST_REMOVE_HEAD(&queries, list);
575                 ast_custom_function_unregister(query->acf);
576                 free_acf_query(query);
577         }
578
579         ast_custom_function_unregister(&escape_function);
580
581         /* Allow any threads waiting for this lock to pass (avoids a race) */
582         AST_LIST_UNLOCK(&queries);
583         AST_LIST_LOCK(&queries);
584
585         AST_LIST_UNLOCK(&queries);
586         return 0;
587 }
588
589 static int reload(void *mod)
590 {
591         int res = 0;
592         struct ast_config *cfg;
593         struct acf_odbc_query *oldquery;
594         char *catg;
595
596         AST_LIST_LOCK(&queries);
597
598         while (!AST_LIST_EMPTY(&queries)) {
599                 oldquery = AST_LIST_REMOVE_HEAD(&queries, list);
600                 ast_custom_function_unregister(oldquery->acf);
601                 free_acf_query(oldquery);
602         }
603
604         cfg = ast_config_load(config);
605         if (!cfg) {
606                 ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
607                 goto reload_out;
608         }
609
610         for (catg = ast_category_browse(cfg, NULL);
611              catg;
612              catg = ast_category_browse(cfg, catg)) {
613                 struct acf_odbc_query *query = NULL;
614
615                 if (init_acf_query(cfg, catg, &query)) {
616                         ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
617                 } else {
618                         AST_LIST_INSERT_HEAD(&queries, query, list);
619                         ast_custom_function_register(query->acf);
620                 }
621         }
622
623         ast_config_destroy(cfg);
624 reload_out:
625         AST_LIST_UNLOCK(&queries);
626         return res;
627 }
628
629 static int unload_module(void *mod)
630 {
631         return odbc_unload_module();
632 }
633
634 static int load_module(void *mod)
635 {
636         return odbc_load_module();
637 }
638
639 static const char *description(void)
640 {
641         return tdesc;
642 }
643
644 /* XXX need to revise usecount - set if query_lock is set */
645
646 static const char *key(void)
647 {
648         return ASTERISK_GPL_KEY;
649 }
650
651 STD_MOD(MOD_1, reload, NULL, NULL);
652