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