update res_odbc to support pooled connections
[asterisk/asterisk.git] / res / res_config_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief odbc+odbc plugin for portable configuration engine
24  *
25  * \author Mark Spencer <markster@digium.com>
26  * \author Anthony Minessale II <anthmct@yahoo.com>
27  *
28  * \arg http://www.unixodbc.org
29  */
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include "asterisk/file.h"
41 #include "asterisk/logger.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/config.h"
45 #include "asterisk/module.h"
46 #include "asterisk/lock.h"
47 #include "asterisk/options.h"
48 #include "asterisk/res_odbc.h"
49 #include "asterisk/utils.h"
50
51 LOCAL_USER_DECL;
52
53 static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap)
54 {
55         struct odbc_obj *obj;
56         SQLHSTMT stmt;
57         char sql[1024];
58         char coltitle[256];
59         char rowdata[2048];
60         char *op;
61         const char *newparam, *newval;
62         char *stringp;
63         char *chunk;
64         SQLSMALLINT collen;
65         int res;
66         int x;
67         struct ast_variable *var=NULL, *prev=NULL;
68         SQLULEN colsize;
69         SQLSMALLINT colcount=0;
70         SQLSMALLINT datatype;
71         SQLSMALLINT decimaldigits;
72         SQLSMALLINT nullable;
73         SQLINTEGER indicator;
74         va_list aq;
75         
76         va_copy(aq, ap);
77         
78         
79         if (!table)
80                 return NULL;
81
82         obj = odbc_request_obj(database, 0);
83         if (!obj)
84                 return NULL;
85
86         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
87         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
88                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
89                 odbc_release_obj(obj);
90                 return NULL;
91         }
92
93         newparam = va_arg(aq, const char *);
94         if (!newparam)  {
95                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
96                 odbc_release_obj(obj);
97                 return NULL;
98         }
99         newval = va_arg(aq, const char *);
100         if (!strchr(newparam, ' ')) op = " ="; else op = "";
101         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?", table, newparam, op);
102         while((newparam = va_arg(aq, const char *))) {
103                 if (!strchr(newparam, ' ')) op = " ="; else op = "";
104                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?", newparam, op);
105                 newval = va_arg(aq, const char *);
106         }
107         va_end(aq);
108         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
109         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
110                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
111                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
112                 odbc_release_obj(obj);
113                 return NULL;
114         }
115         
116         /* Now bind the parameters */
117         x = 1;
118
119         while((newparam = va_arg(ap, const char *))) {
120                 newval = va_arg(ap, const char *);
121                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
122         }
123         
124         res = odbc_smart_execute(obj, stmt);
125
126         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
127                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
128                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
129                 odbc_release_obj(obj);
130                 return NULL;
131         }
132
133         res = SQLNumResultCols(stmt, &colcount);
134         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
135                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
136                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
137                 odbc_release_obj(obj);
138                 return NULL;
139         }
140
141         res = SQLFetch(stmt);
142         if (res == SQL_NO_DATA) {
143                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
144                 odbc_release_obj(obj);
145                 return NULL;
146         }
147         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
148                 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
149                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
150                 odbc_release_obj(obj);
151                 return NULL;
152         }
153         for (x = 0; x < colcount; x++) {
154                 rowdata[0] = '\0';
155                 collen = sizeof(coltitle);
156                 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
157                                         &datatype, &colsize, &decimaldigits, &nullable);
158                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
159                         ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
160                         if (var)
161                                 ast_variables_destroy(var);
162                         odbc_release_obj(obj);
163                         return NULL;
164                 }
165
166                 indicator = 0;
167                 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator);
168                 if (indicator == SQL_NULL_DATA)
169                         continue;
170
171                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
172                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
173                         if (var)
174                                 ast_variables_destroy(var);
175                         odbc_release_obj(obj);
176                         return NULL;
177                 }
178                 stringp = rowdata;
179                 while(stringp) {
180                         chunk = strsep(&stringp, ";");
181                         if (!ast_strlen_zero(ast_strip(chunk))) {
182                                 if (prev) {
183                                         prev->next = ast_variable_new(coltitle, chunk);
184                                         if (prev->next)
185                                                 prev = prev->next;
186                                         } else 
187                                                 prev = var = ast_variable_new(coltitle, chunk);
188                                         
189                         }
190                 }
191         }
192
193
194         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
195         odbc_release_obj(obj);
196         return var;
197 }
198
199 static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap)
200 {
201         struct odbc_obj *obj;
202         SQLHSTMT stmt;
203         char sql[1024];
204         char coltitle[256];
205         char rowdata[2048];
206         const char *initfield=NULL;
207         char *op;
208         const char *newparam, *newval;
209         char *stringp;
210         char *chunk;
211         SQLSMALLINT collen;
212         int res;
213         int x;
214         struct ast_variable *var=NULL;
215         struct ast_config *cfg=NULL;
216         struct ast_category *cat=NULL;
217         struct ast_realloca ra;
218         SQLULEN colsize;
219         SQLSMALLINT colcount=0;
220         SQLSMALLINT datatype;
221         SQLSMALLINT decimaldigits;
222         SQLSMALLINT nullable;
223         SQLINTEGER indicator;
224
225         va_list aq;
226         va_copy(aq, ap);
227         
228         
229         if (!table)
230                 return NULL;
231         memset(&ra, 0, sizeof(ra));
232
233         obj = odbc_request_obj(database, 0);
234         if (!obj)
235                 return NULL;
236
237         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
238         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
239                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
240                 odbc_release_obj(obj);
241                 return NULL;
242         }
243
244         newparam = va_arg(aq, const char *);
245         if (!newparam)  {
246                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
247                 odbc_release_obj(obj);
248                 return NULL;
249         }
250         initfield = ast_strdupa(newparam);
251         if (initfield && (op = strchr(initfield, ' '))) 
252                 *op = '\0';
253         newval = va_arg(aq, const char *);
254         if (!strchr(newparam, ' ')) op = " ="; else op = "";
255         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?", table, newparam, op);
256         while((newparam = va_arg(aq, const char *))) {
257                 if (!strchr(newparam, ' ')) op = " ="; else op = "";
258                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?", newparam, op);
259                 newval = va_arg(aq, const char *);
260         }
261         if (initfield)
262                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
263         va_end(aq);
264         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
265         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
266                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
267                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
268                 odbc_release_obj(obj);
269                 return NULL;
270         }
271         
272         /* Now bind the parameters */
273         x = 1;
274
275         while((newparam = va_arg(ap, const char *))) {
276                 newval = va_arg(ap, const char *);
277                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
278         }
279                 
280         res = odbc_smart_execute(obj, stmt);
281
282         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
283                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
284                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
285                 odbc_release_obj(obj);
286                 return NULL;
287         }
288
289         res = SQLNumResultCols(stmt, &colcount);
290         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
291                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
292                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
293                 odbc_release_obj(obj);
294                 return NULL;
295         }
296
297         cfg = ast_config_new();
298         if (!cfg) {
299                 ast_log(LOG_WARNING, "Out of memory!\n");
300                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
301                 odbc_release_obj(obj);
302                 return NULL;
303         }
304
305         while ((res=SQLFetch(stmt)) != SQL_NO_DATA) {
306                 var = NULL;
307                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
308                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
309                         continue;
310                 }
311                 cat = ast_category_new("");
312                 if (!cat) {
313                         ast_log(LOG_WARNING, "Out of memory!\n");
314                         continue;
315                 }
316                 for (x=0;x<colcount;x++) {
317                         rowdata[0] = '\0';
318                         collen = sizeof(coltitle);
319                         res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
320                                                 &datatype, &colsize, &decimaldigits, &nullable);
321                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
322                                 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
323                                 ast_category_destroy(cat);
324                                 continue;
325                         }
326
327                         indicator = 0;
328                         res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator);
329                         if (indicator == SQL_NULL_DATA)
330                                 continue;
331
332                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
333                                 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
334                                 ast_category_destroy(cat);
335                                 continue;
336                         }
337                         stringp = rowdata;
338                         while(stringp) {
339                                 chunk = strsep(&stringp, ";");
340                                 if (!ast_strlen_zero(ast_strip(chunk))) {
341                                         if (initfield && !strcmp(initfield, coltitle))
342                                                 ast_category_rename(cat, chunk);
343                                         var = ast_variable_new(coltitle, chunk);
344                                         ast_variable_append(cat, var);
345                                 }
346                         }
347                 }
348                 ast_category_append(cfg, cat);
349         }
350
351         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
352         odbc_release_obj(obj);
353         return cfg;
354 }
355
356 static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
357 {
358         struct odbc_obj *obj;
359         SQLHSTMT stmt;
360         char sql[256];
361         SQLLEN rowcount=0;
362         const char *newparam, *newval;
363         int res;
364         int x;
365         va_list aq;
366         
367         va_copy(aq, ap);
368         
369         if (!table)
370                 return -1;
371
372         obj = odbc_request_obj(database, 0);
373         if (!obj)
374                 return -1;
375
376         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
377         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
378                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
379                 odbc_release_obj(obj);
380                 return -1;
381         }
382
383         newparam = va_arg(aq, const char *);
384         if (!newparam)  {
385                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
386                 odbc_release_obj(obj);
387                 return -1;
388         }
389         newval = va_arg(aq, const char *);
390         snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam);
391         while((newparam = va_arg(aq, const char *))) {
392                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam);
393                 newval = va_arg(aq, const char *);
394         }
395         va_end(aq);
396         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
397         
398         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
399         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
400                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
401                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
402                 odbc_release_obj(obj);
403                 return -1;
404         }
405         
406         /* Now bind the parameters */
407         x = 1;
408
409         while((newparam = va_arg(ap, const char *))) {
410                 newval = va_arg(ap, const char *);
411                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
412         }
413                 
414         SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(lookup), 0, (void *)lookup, 0, NULL);
415
416         res = odbc_smart_execute(obj, stmt);
417
418         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
419                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
420                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
421                 odbc_release_obj(obj);
422                 return -1;
423         }
424
425         res = SQLRowCount(stmt, &rowcount);
426         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
427         odbc_release_obj(obj);
428
429         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
430                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
431                 return -1;
432         }
433
434         if (rowcount >= 0)
435                 return (int)rowcount;
436
437         return -1;
438 }
439
440 struct config_odbc_obj {
441         char *sql;
442         unsigned long id;
443         unsigned long cat_metric;
444         unsigned long var_metric;
445         unsigned long commented;
446         char filename[128];
447         char category[128];
448         char var_name[128];
449         char var_val[128];
450         SQLINTEGER err;
451 };
452
453 static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
454 {
455         struct config_odbc_obj *q = data;
456         SQLHSTMT sth;
457         int res;
458
459         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
460         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
461                 if (option_verbose > 3)
462                         ast_verbose( VERBOSE_PREFIX_4 "Failure in AllocStatement %d\n", res);
463                 return NULL;
464         }
465
466         res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS);
467         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
468                 if (option_verbose > 3)
469                         ast_verbose( VERBOSE_PREFIX_4 "Error in PREPARE %d\n", res);
470                 SQLFreeHandle(SQL_HANDLE_STMT, sth);
471                 return NULL;
472         }
473
474         SQLBindCol(sth, 1, SQL_C_ULONG, &q->id, sizeof(q->id), &q->err);
475         SQLBindCol(sth, 2, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err);
476         SQLBindCol(sth, 3, SQL_C_ULONG, &q->var_metric, sizeof(q->var_metric), &q->err);
477         SQLBindCol(sth, 4, SQL_C_ULONG, &q->commented, sizeof(q->commented), &q->err);
478         SQLBindCol(sth, 5, SQL_C_CHAR, q->filename, sizeof(q->filename), &q->err);
479         SQLBindCol(sth, 6, SQL_C_CHAR, q->category, sizeof(q->category), &q->err);
480         SQLBindCol(sth, 7, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err);
481         SQLBindCol(sth, 8, SQL_C_CHAR, q->var_val, sizeof(q->var_val), &q->err);
482
483         return sth;
484 }
485
486 static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg)
487 {
488         struct ast_variable *new_v;
489         struct ast_category *cur_cat;
490         int res = 0;
491         struct odbc_obj *obj;
492         char sql[255] = "";
493         unsigned int last_cat_metric = 0;
494         SQLSMALLINT rowcount=0;
495         SQLHSTMT stmt;
496         char last[128] = "";
497         struct config_odbc_obj q;
498
499         memset(&q, 0, sizeof(q));
500
501         if (!file || !strcmp (file, "res_config_odbc.conf"))
502                 return NULL;            /* cant configure myself with myself ! */
503
504         obj = odbc_request_obj(database, 0);
505         if (!obj)
506                 return NULL;
507
508         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE filename='%s' and commented=0 ORDER BY filename,cat_metric desc,var_metric asc,category,var_name,var_val,id", table, file);
509         q.sql = sql;
510
511         stmt = odbc_prepare_and_execute(obj, config_odbc_prepare, &q);
512
513         if (!stmt) {
514                 ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
515                 odbc_release_obj(obj);
516                 return NULL;
517         }
518
519         res = SQLNumResultCols(stmt, &rowcount);
520
521         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
522                 ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
523                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
524                 odbc_release_obj(obj);
525                 return NULL;
526         }
527
528         if (!rowcount) {
529                 ast_log(LOG_NOTICE, "found nothing\n");
530                 odbc_release_obj(obj);
531                 return cfg;
532         }
533
534         cur_cat = ast_config_get_current_category(cfg);
535
536         while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
537                 if (!strcmp (q.var_name, "#include")) {
538                         if (!ast_config_internal_load(q.var_val, cfg)) {
539                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
540                                 odbc_release_obj(obj);
541                                 return NULL;
542                         }
543                         continue;
544                 } 
545                 if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) {
546                         cur_cat = ast_category_new(q.category);
547                         if (!cur_cat) {
548                                 ast_log(LOG_WARNING, "Out of memory!\n");
549                                 break;
550                         }
551                         strcpy(last, q.category);
552                         last_cat_metric = q.cat_metric;
553                         ast_category_append(cfg, cur_cat);
554                 }
555
556                 new_v = ast_variable_new(q.var_name, q.var_val);
557                 ast_variable_append(cur_cat, new_v);
558         }
559
560         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
561         odbc_release_obj(obj);
562         return cfg;
563 }
564
565 static struct ast_config_engine odbc_engine = {
566         .name = "odbc",
567         .load_func = config_odbc,
568         .realtime_func = realtime_odbc,
569         .realtime_multi_func = realtime_multi_odbc,
570         .update_func = update_odbc
571 };
572
573 static int unload_module (void *mod)
574 {
575         ast_hangup_localusers(mod);
576         ast_config_engine_deregister(&odbc_engine);
577         if (option_verbose)
578                 ast_verbose("res_config_odbc unloaded.\n");
579         return 0;
580 }
581
582 static int load_module (void *mod)
583 {
584         ast_config_engine_register(&odbc_engine);
585         if (option_verbose)
586                 ast_verbose("res_config_odbc loaded.\n");
587         return 0;
588 }
589
590 static const char *description(void)
591 {
592         return "ODBC Configuration";
593 }
594
595 static const char *key(void)
596 {
597         return ASTERISK_GPL_KEY;
598 }
599
600 STD_MOD(MOD_0 | NO_USECOUNT | NO_UNLOAD, NULL, NULL, NULL);