Logger/CLI/etc.: Fix some aesthetic issues; reduce chatty verbose messages
[asterisk/asterisk.git] / res / res_config_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2010, 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 /*** MODULEINFO
32         <depend>res_odbc</depend>
33         <support_level>core</support_level>
34  ***/
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include "asterisk/file.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/config.h"
44 #include "asterisk/module.h"
45 #include "asterisk/lock.h"
46 #include "asterisk/res_odbc.h"
47 #include "asterisk/utils.h"
48 #include "asterisk/stringfields.h"
49
50 AST_THREADSTORAGE(sql_buf);
51 AST_THREADSTORAGE(rowdata_buf);
52
53 struct custom_prepare_struct {
54         const char *sql;
55         const char *extra;
56         AST_DECLARE_STRING_FIELDS(
57                 AST_STRING_FIELD(encoding)[256];
58         );
59         const struct ast_variable *fields;
60         unsigned long long skip;
61 };
62
63 #define ENCODE_CHUNK(buffer, s) \
64         do { \
65                 char *eptr = buffer; \
66                 const char *vptr = s; \
67                 for (; *vptr && eptr < buffer + sizeof(buffer); vptr++) { \
68                         if (strchr("^;", *vptr)) { \
69                                 /* We use ^XX, instead of %XX because '%' is a special character in SQL */ \
70                                 snprintf(eptr, buffer + sizeof(buffer) - eptr, "^%02hhX", *vptr); \
71                                 eptr += 3; \
72                         } else { \
73                                 *eptr++ = *vptr; \
74                         } \
75                 } \
76                 if (eptr < buffer + sizeof(buffer)) { \
77                         *eptr = '\0'; \
78                 } else { \
79                         buffer[sizeof(buffer) - 1] = '\0'; \
80                 } \
81         } while(0)
82
83 static void decode_chunk(char *chunk)
84 {
85         for (; *chunk; chunk++) {
86                 if (*chunk == '^' && strchr("0123456789ABCDEF", chunk[1]) && strchr("0123456789ABCDEF", chunk[2])) {
87                         sscanf(chunk + 1, "%02hhX", (unsigned char *)chunk);
88                         memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
89                 }
90         }
91 }
92
93 static inline int is_text(const struct odbc_cache_columns *column)
94 {
95         return column->type == SQL_CHAR || column->type == SQL_VARCHAR || column->type == SQL_LONGVARCHAR
96                 || column->type == SQL_WCHAR || column->type == SQL_WVARCHAR || column->type == SQL_WLONGVARCHAR;
97 }
98
99 static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
100 {
101         int res, x = 1, count = 0;
102         struct custom_prepare_struct *cps = data;
103         const struct ast_variable *field;
104         char encodebuf[1024];
105         SQLHSTMT stmt;
106
107         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
108         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
109                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
110                 return NULL;
111         }
112
113         ast_debug(1, "Skip: %llu; SQL: %s\n", cps->skip, cps->sql);
114
115         res = SQLPrepare(stmt, (unsigned char *)cps->sql, SQL_NTS);
116         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
117                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", cps->sql);
118                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
119                 return NULL;
120         }
121
122         for (field = cps->fields; field; field = field->next) {
123                 const char *newval = field->value;
124
125                 if ((1LL << count++) & cps->skip) {
126                         ast_debug(1, "Skipping field '%s'='%s' (%llo/%llo)\n", field->name, newval, 1ULL << (count - 1), cps->skip);
127                         continue;
128                 }
129                 ast_debug(1, "Parameter %d ('%s') = '%s'\n", x, field->name, newval);
130                 if (strchr(newval, ';') || strchr(newval, '^')) {
131                         ENCODE_CHUNK(encodebuf, newval);
132                         ast_string_field_set(cps, encoding[x], encodebuf);
133                         newval = cps->encoding[x];
134                 }
135                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
136         }
137
138         if (!ast_strlen_zero(cps->extra)) {
139                 const char *newval = cps->extra;
140                 if (strchr(newval, ';') || strchr(newval, '^')) {
141                         ENCODE_CHUNK(encodebuf, newval);
142                         ast_string_field_set(cps, encoding[x], encodebuf);
143                         newval = cps->encoding[x];
144                 } 
145                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
146         }
147
148         return stmt;
149 }
150
151 /*!
152  * \brief Excute an SQL query and return ast_variable list
153  * \param database
154  * \param table
155  * \param ap list containing one or more field/operator/value set.
156  *
157  * Select database and preform query on table, prepare the sql statement
158  * Sub-in the values to the prepared statement and execute it. Return results
159  * as a ast_variable list.
160  *
161  * \retval var on success
162  * \retval NULL on failure
163 */
164 static struct ast_variable *realtime_odbc(const char *database, const char *table, const struct ast_variable *fields)
165 {
166         struct odbc_obj *obj;
167         SQLHSTMT stmt;
168         char sql[1024];
169         char coltitle[256];
170         struct ast_str *rowdata = ast_str_thread_get(&rowdata_buf, 128);
171         char *op;
172         const struct ast_variable *field = fields;
173         char *stringp;
174         char *chunk;
175         SQLSMALLINT collen;
176         int res;
177         int x;
178         struct ast_variable *var=NULL, *prev=NULL;
179         SQLULEN colsize;
180         SQLSMALLINT colcount=0;
181         SQLSMALLINT datatype;
182         SQLSMALLINT decimaldigits;
183         SQLSMALLINT nullable;
184         SQLLEN indicator;
185         struct custom_prepare_struct cps = { .sql = sql, .fields = fields, };
186         struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
187
188         if (!table || !field) {
189                 return NULL;
190         }
191
192         obj = ast_odbc_request_obj2(database, connected_flag);
193
194         if (!obj) {
195                 ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", database);
196                 return NULL;
197         }
198
199         op = !strchr(field->name, ' ') ? " =" : "";
200         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, field->name, op,
201                 strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
202         while ((field = field->next)) {
203                 op = !strchr(field->name, ' ') ? " =" : "";
204                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", field->name, op,
205                         strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
206         }
207
208         if (ast_string_field_init(&cps, 256)) {
209                 ast_odbc_release_obj(obj);
210                 return NULL;
211         }
212         stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
213         ast_string_field_free_memory(&cps);
214
215         if (!stmt) {
216                 ast_odbc_release_obj(obj);
217                 return NULL;
218         }
219
220         res = SQLNumResultCols(stmt, &colcount);
221         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
222                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
223                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
224                 ast_odbc_release_obj(obj);
225                 return NULL;
226         }
227
228         res = SQLFetch(stmt);
229         if (res == SQL_NO_DATA) {
230                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
231                 ast_odbc_release_obj(obj);
232                 return NULL;
233         }
234         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
235                 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
236                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
237                 ast_odbc_release_obj(obj);
238                 return NULL;
239         }
240         for (x = 0; x < colcount; x++) {
241                 colsize = 0;
242                 collen = sizeof(coltitle);
243                 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
244                                         &datatype, &colsize, &decimaldigits, &nullable);
245                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
246                         ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
247                         if (var)
248                                 ast_variables_destroy(var);
249                         ast_odbc_release_obj(obj);
250                         return NULL;
251                 }
252
253                 ast_str_reset(rowdata);
254                 indicator = 0;
255
256                 res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata), ast_str_size(rowdata), &indicator);
257                 ast_str_update(rowdata);
258                 if (indicator == SQL_NULL_DATA) {
259                         ast_str_reset(rowdata);
260                 } else if (!ast_str_strlen(rowdata)) {
261                         /* Because we encode the empty string for a NULL, we will encode
262                          * actual empty strings as a string containing a single whitespace. */
263                         ast_str_set(&rowdata, -1, "%s", " ");
264                 } else if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) {
265                         if (indicator != ast_str_strlen(rowdata)) {
266                                 /* If the available space was not enough to contain the row data enlarge and read in the rest */
267                                 ast_str_make_space(&rowdata, indicator + 1);
268                                 res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata) + ast_str_strlen(rowdata),
269                                         ast_str_size(rowdata) - ast_str_strlen(rowdata), &indicator);
270                                 ast_str_update(rowdata);
271                         }
272                 }
273
274                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
275                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
276                         if (var)
277                                 ast_variables_destroy(var);
278                         ast_odbc_release_obj(obj);
279                         return NULL;
280                 }
281
282                 stringp = ast_str_buffer(rowdata);
283                 while (stringp) {
284                         chunk = strsep(&stringp, ";");
285                         if (!ast_strlen_zero(ast_strip(chunk))) {
286                                 if (strchr(chunk, '^')) {
287                                         decode_chunk(chunk);
288                                 }
289                                 if (prev) {
290                                         prev->next = ast_variable_new(coltitle, chunk, "");
291                                         if (prev->next) {
292                                                 prev = prev->next;
293                                         }
294                                 } else {
295                                         prev = var = ast_variable_new(coltitle, chunk, "");
296                                 }
297                         }
298                 }
299         }
300
301         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
302         ast_odbc_release_obj(obj);
303         return var;
304 }
305
306 /*!
307  * \brief Excute an Select query and return ast_config list
308  * \param database
309  * \param table
310  * \param ap list containing one or more field/operator/value set.
311  *
312  * Select database and preform query on table, prepare the sql statement
313  * Sub-in the values to the prepared statement and execute it. 
314  * Execute this prepared query against several ODBC connected databases.
315  * Return results as an ast_config variable.
316  *
317  * \retval var on success
318  * \retval NULL on failure
319 */
320 static struct ast_config *realtime_multi_odbc(const char *database, const char *table, const struct ast_variable *fields)
321 {
322         struct odbc_obj *obj;
323         SQLHSTMT stmt;
324         char sql[1024];
325         char coltitle[256];
326         struct ast_str *rowdata = ast_str_thread_get(&rowdata_buf, 128);
327         const char *initfield;
328         char *op;
329         const struct ast_variable *field = fields;
330         char *stringp;
331         char *chunk;
332         SQLSMALLINT collen;
333         int res;
334         int x;
335         struct ast_variable *var=NULL;
336         struct ast_config *cfg=NULL;
337         struct ast_category *cat=NULL;
338         struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
339         SQLULEN colsize;
340         SQLSMALLINT colcount=0;
341         SQLSMALLINT datatype;
342         SQLSMALLINT decimaldigits;
343         SQLSMALLINT nullable;
344         SQLLEN indicator;
345         struct custom_prepare_struct cps = { .sql = sql, .fields = fields, };
346
347         if (!table || !field) {
348                 return NULL;
349         }
350
351         obj = ast_odbc_request_obj2(database, connected_flag);
352         if (!obj) {
353                 return NULL;
354         }
355
356         initfield = ast_strdupa(field->name);
357         if ((op = strchr(initfield, ' '))) {
358                 *op = '\0';
359         }
360
361         op = !strchr(field->name, ' ') ? " =" : "";
362         snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, field->name, op,
363                 strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
364         while ((field = field->next)) {
365                 op = !strchr(field->name, ' ') ? " =" : "";
366                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", field->name, op,
367                         strcasestr(field->name, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
368         }
369
370         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
371
372         if (ast_string_field_init(&cps, 256)) {
373                 ast_odbc_release_obj(obj);
374                 return NULL;
375         }
376         stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
377         ast_string_field_free_memory(&cps);
378
379         if (!stmt) {
380                 ast_odbc_release_obj(obj);
381                 return NULL;
382         }
383
384         res = SQLNumResultCols(stmt, &colcount);
385         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
386                 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
387                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
388                 ast_odbc_release_obj(obj);
389                 return NULL;
390         }
391
392         cfg = ast_config_new();
393         if (!cfg) {
394                 ast_log(LOG_WARNING, "Out of memory!\n");
395                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
396                 ast_odbc_release_obj(obj);
397                 return NULL;
398         }
399
400         while ((res=SQLFetch(stmt)) != SQL_NO_DATA) {
401                 var = NULL;
402                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
403                         ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
404                         continue;
405                 }
406                 cat = ast_category_new("","",99999);
407                 if (!cat) {
408                         ast_log(LOG_WARNING, "Out of memory!\n");
409                         continue;
410                 }
411                 for (x=0;x<colcount;x++) {
412                         colsize = 0;
413                         collen = sizeof(coltitle);
414                         res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
415                                                 &datatype, &colsize, &decimaldigits, &nullable);
416                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
417                                 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
418                                 ast_category_destroy(cat);
419                                 goto next_sql_fetch;
420                         }
421
422                         ast_str_reset(rowdata);
423                         indicator = 0;
424
425                         res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata), ast_str_size(rowdata), &indicator);
426                         ast_str_update(rowdata);
427                         if (indicator == SQL_NULL_DATA) {
428                                 continue;
429                         }
430
431                         if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) {
432                                 if (indicator != ast_str_strlen(rowdata)) {
433                                         /* If the available space was not enough to contain the row data enlarge and read in the rest */
434                                         ast_str_make_space(&rowdata, indicator + 1);
435                                         res = SQLGetData(stmt, x + 1, SQL_CHAR, ast_str_buffer(rowdata) + ast_str_strlen(rowdata),
436                                                 ast_str_size(rowdata) - ast_str_strlen(rowdata), &indicator);
437                                         ast_str_update(rowdata);
438                                 }
439                         }
440
441                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
442                                 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
443                                 ast_category_destroy(cat);
444                                 goto next_sql_fetch;
445                         }
446                         stringp = ast_str_buffer(rowdata);
447                         while (stringp) {
448                                 chunk = strsep(&stringp, ";");
449                                 if (!ast_strlen_zero(ast_strip(chunk))) {
450                                         if (strchr(chunk, '^')) {
451                                                 decode_chunk(chunk);
452                                         }
453                                         if (!strcmp(initfield, coltitle)) {
454                                                 ast_category_rename(cat, chunk);
455                                         }
456                                         var = ast_variable_new(coltitle, chunk, "");
457                                         ast_variable_append(cat, var);
458                                 }
459                         }
460                 }
461                 ast_category_append(cfg, cat);
462 next_sql_fetch:;
463         }
464
465         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
466         ast_odbc_release_obj(obj);
467         return cfg;
468 }
469
470 /*!
471  * \brief Excute an UPDATE query
472  * \param database
473  * \param table
474  * \param keyfield where clause field
475  * \param lookup value of field for where clause
476  * \param ap list containing one or more field/value set(s).
477  *
478  * Update a database table, prepare the sql statement using keyfield and lookup
479  * control the number of records to change. All values to be changed are stored in ap list.
480  * Sub-in the values to the prepared statement and execute it.
481  *
482  * \retval number of rows affected
483  * \retval -1 on failure
484 */
485 static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields)
486 {
487         struct odbc_obj *obj;
488         SQLHSTMT stmt;
489         char sql[256];
490         SQLLEN rowcount=0;
491         const struct ast_variable *field = fields;
492         int res, count = 0, paramcount = 0;
493         struct custom_prepare_struct cps = { .sql = sql, .extra = lookup, .fields = fields, };
494         struct odbc_cache_tables *tableptr;
495         struct odbc_cache_columns *column = NULL;
496         struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
497
498         if (!table || !field || !keyfield) {
499                 return -1;
500         }
501
502         tableptr = ast_odbc_find_table(database, table);
503         if (!(obj = ast_odbc_request_obj2(database, connected_flag))) {
504                 ast_odbc_release_table(tableptr);
505                 return -1;
506         }
507
508         if (tableptr && !ast_odbc_find_column(tableptr, keyfield)) {
509                 ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'.  Update will fail\n", keyfield, table, database);
510         }
511
512         snprintf(sql, sizeof(sql), "UPDATE %s SET ", table);
513         while (field) {
514                 if ((tableptr && (column = ast_odbc_find_column(tableptr, field->name))) || count >= 64) {
515                         if (paramcount++) {
516                                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", ");
517                         }
518                         /* NULL test for non-text columns */
519                         if (count < 64 && ast_strlen_zero(field->value) && column->nullable && !is_text(column)) {
520                                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=NULL", field->name);
521                                 cps.skip |= (1LL << count);
522                         } else {
523                                 /* Value is not an empty string, or column is of text type, or we couldn't fit any more into cps.skip (count >= 64 ?!). */
524                                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", field->name);
525                         }
526                 } else { /* the column does not exist in the table */
527                         cps.skip |= (1LL << count);
528                 }
529                 ++count;
530                 field = field->next;
531         }
532         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
533         ast_odbc_release_table(tableptr);
534
535         if (ast_string_field_init(&cps, 256)) {
536                 ast_odbc_release_obj(obj);
537                 return -1;
538         }
539         stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
540         ast_string_field_free_memory(&cps);
541
542         if (!stmt) {
543                 ast_odbc_release_obj(obj);
544                 return -1;
545         }
546
547         res = SQLRowCount(stmt, &rowcount);
548         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
549         ast_odbc_release_obj(obj);
550
551         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
552                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
553                 return -1;
554         }
555
556         if (rowcount >= 0) {
557                 return (int) rowcount;
558         }
559
560         return -1;
561 }
562
563 struct update2_prepare_struct {
564         const char *database;
565         const char *table;
566         const struct ast_variable *lookup_fields;
567         const struct ast_variable *update_fields;
568 };
569
570 static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data)
571 {
572         int res, x = 1, first = 1;
573         struct update2_prepare_struct *ups = data;
574         const struct ast_variable *field;
575         struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
576         SQLHSTMT stmt;
577         struct odbc_cache_tables *tableptr = ast_odbc_find_table(ups->database, ups->table);
578
579         if (!sql) {
580                 if (tableptr) {
581                         ast_odbc_release_table(tableptr);
582                 }
583                 return NULL;
584         }
585
586         if (!tableptr) {
587                 ast_log(LOG_ERROR, "Could not retrieve metadata for table '%s@%s'.  Update will fail!\n", ups->table, ups->database);
588                 return NULL;
589         }
590
591         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
592         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
593                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
594                 ast_odbc_release_table(tableptr);
595                 return NULL;
596         }
597
598         ast_str_set(&sql, 0, "UPDATE %s SET ", ups->table);
599
600         for (field = ups->update_fields; field; field = field->next) {
601                 if (ast_odbc_find_column(tableptr, field->name)) {
602                         ast_str_append(&sql, 0, "%s%s=? ", first ? "" : ", ", field->name);
603                         SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(field->name), 0, (void *)field->value, 0, NULL);
604                         first = 0;
605                 } else {
606                         ast_log(LOG_NOTICE, "Not updating column '%s' in '%s@%s' because that column does not exist!\n", field->name, ups->table, ups->database);
607                 }
608         }
609
610         ast_str_append(&sql, 0, "WHERE");
611         first = 1;
612
613         for (field = ups->lookup_fields; field; field = field->next) {
614                 if (!ast_odbc_find_column(tableptr, field->name)) {
615                         ast_log(LOG_ERROR, "One or more of the criteria columns '%s' on '%s@%s' for this update does not exist!\n", field->name, ups->table, ups->database);
616                         ast_odbc_release_table(tableptr);
617                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
618                         return NULL;
619                 }
620                 ast_str_append(&sql, 0, "%s %s=?", first ? "" : " AND", field->name);
621                 SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(field->value), 0, (void *)field->value, 0, NULL);
622                 first = 0;
623         }
624
625         /* Done with the table metadata */
626         ast_odbc_release_table(tableptr);
627
628         res = SQLPrepare(stmt, (unsigned char *)ast_str_buffer(sql), SQL_NTS);
629         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
630                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", ast_str_buffer(sql));
631                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
632                 return NULL;
633         }
634
635         return stmt;
636 }
637
638 /*!
639  * \brief Execute an UPDATE query
640  * \param database
641  * \param table
642  * \param ap list containing one or more field/value set(s).
643  *
644  * Update a database table, preparing the sql statement from a list of
645  * key/value pairs specified in ap.  The lookup pairs are specified first
646  * and are separated from the update pairs by a sentinel value.
647  * Sub-in the values to the prepared statement and execute it.
648  *
649  * \retval number of rows affected
650  * \retval -1 on failure
651 */
652 static int update2_odbc(const char *database, const char *table, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
653 {
654         struct odbc_obj *obj;
655         SQLHSTMT stmt;
656         struct update2_prepare_struct ups = { .database = database, .table = table, .lookup_fields = lookup_fields, .update_fields = update_fields, };
657         struct ast_str *sql;
658         int res;
659         SQLLEN rowcount = 0;
660
661         if (!(obj = ast_odbc_request_obj(database, 0))) {
662                 return -1;
663         }
664
665         if (!(stmt = ast_odbc_prepare_and_execute(obj, update2_prepare, &ups))) {
666                 ast_odbc_release_obj(obj);
667                 return -1;
668         }
669
670         res = SQLRowCount(stmt, &rowcount);
671         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
672         ast_odbc_release_obj(obj);
673
674         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
675                 /* Since only a single thread can access this memory, we can retrieve what would otherwise be lost. */
676                 sql = ast_str_thread_get(&sql_buf, 16);
677                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n", ast_str_buffer(sql));
678                 return -1;
679         }
680
681         if (rowcount >= 0) {
682                 return (int)rowcount;
683         }
684
685         return -1;
686 }
687
688 /*!
689  * \brief Excute an INSERT query
690  * \param database
691  * \param table
692  * \param ap list containing one or more field/value set(s)
693  *
694  * Insert a new record into database table, prepare the sql statement.
695  * All values to be changed are stored in ap list.
696  * Sub-in the values to the prepared statement and execute it.
697  *
698  * \retval number of rows affected
699  * \retval -1 on failure
700 */
701 static int store_odbc(const char *database, const char *table, const struct ast_variable *fields)
702 {
703         struct odbc_obj *obj;
704         SQLHSTMT stmt;
705         char sql[256];
706         char keys[256];
707         char vals[256];
708         SQLLEN rowcount=0;
709         const struct ast_variable *field = fields;
710         int res;
711         struct custom_prepare_struct cps = { .sql = sql, .extra = NULL, .fields = fields, };
712         struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
713
714         if (!table || !field) {
715                 return -1;
716         }
717
718         obj = ast_odbc_request_obj2(database, connected_flag);
719         if (!obj) {
720                 return -1;
721         }
722
723         snprintf(keys, sizeof(keys), "%s", field->name);
724         ast_copy_string(vals, "?", sizeof(vals));
725         while ((field = field->next)) {
726                 snprintf(keys + strlen(keys), sizeof(keys) - strlen(keys), ", %s", field->name);
727                 snprintf(vals + strlen(vals), sizeof(vals) - strlen(vals), ", ?");
728         }
729         snprintf(sql, sizeof(sql), "INSERT INTO %s (%s) VALUES (%s)", table, keys, vals);
730
731         if (ast_string_field_init(&cps, 256)) {
732                 ast_odbc_release_obj(obj);
733                 return -1;
734         }
735         stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
736         ast_string_field_free_memory(&cps);
737
738         if (!stmt) {
739                 ast_odbc_release_obj(obj);
740                 return -1;
741         }
742
743         res = SQLRowCount(stmt, &rowcount);
744         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
745         ast_odbc_release_obj(obj);
746
747         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
748                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
749                 return -1;
750         }
751
752         if (rowcount >= 0)
753                 return (int)rowcount;
754
755         return -1;
756 }
757
758 /*!
759  * \brief Excute an DELETE query
760  * \param database
761  * \param table
762  * \param keyfield where clause field
763  * \param lookup value of field for where clause
764  * \param ap list containing one or more field/value set(s)
765  *
766  * Delete a row from a database table, prepare the sql statement using keyfield and lookup
767  * control the number of records to change. Additional params to match rows are stored in ap list.
768  * Sub-in the values to the prepared statement and execute it.
769  *
770  * \retval number of rows affected
771  * \retval -1 on failure
772 */
773 static int destroy_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields)
774 {
775         struct odbc_obj *obj;
776         SQLHSTMT stmt;
777         char sql[256];
778         SQLLEN rowcount=0;
779         const struct ast_variable *field;
780         int res;
781         struct custom_prepare_struct cps = { .sql = sql, .extra = lookup, .fields = fields, };
782         struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
783
784         if (!table) {
785                 return -1;
786         }
787
788         obj = ast_odbc_request_obj2(database, connected_flag);
789         if (!obj) {
790                 return -1;
791         }
792
793         snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE ", table);
794
795         for (field = fields; field; field = field->next) {
796                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=? AND ", field->name);
797         }
798         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", keyfield);
799
800         if (ast_string_field_init(&cps, 256)) {
801                 ast_odbc_release_obj(obj);
802                 return -1;
803         }
804         stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
805         ast_string_field_free_memory(&cps);
806
807         if (!stmt) {
808                 ast_odbc_release_obj(obj);
809                 return -1;
810         }
811
812         res = SQLRowCount(stmt, &rowcount);
813         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
814         ast_odbc_release_obj(obj);
815
816         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
817                 ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
818                 return -1;
819         }
820
821         if (rowcount >= 0)
822                 return (int)rowcount;
823
824         return -1;
825 }
826
827 struct config_odbc_obj {
828         char *sql;
829         unsigned long cat_metric;
830         char category[128];
831         char var_name[128];
832         char *var_val;
833         unsigned long var_val_size;
834         SQLLEN err;
835 };
836
837
838 static SQLHSTMT length_determination_odbc_prepare(struct odbc_obj *obj, void *data)
839 {
840         struct config_odbc_obj *q = data;
841         SQLHSTMT sth;
842         int res;
843
844         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
845         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
846                 ast_verb(4, "Failure in AllocStatement %d\n", res);
847                 return NULL;
848         }
849
850         res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS);
851         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
852                 ast_verb(4, "Error in PREPARE %d\n", res);
853                 SQLFreeHandle(SQL_HANDLE_STMT, sth);
854                 return NULL;
855         }
856
857         SQLBindCol(sth, 1, SQL_C_ULONG, &q->var_val_size, sizeof(q->var_val_size), &q->err);
858
859         return sth;
860 }
861
862 static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
863 {
864         struct config_odbc_obj *q = data;
865         SQLHSTMT sth;
866         int res;
867
868         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
869         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
870                 ast_verb(4, "Failure in AllocStatement %d\n", res);
871                 return NULL;
872         }
873
874         res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS);
875         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
876                 ast_verb(4, "Error in PREPARE %d\n", res);
877                 SQLFreeHandle(SQL_HANDLE_STMT, sth);
878                 return NULL;
879         }
880
881         SQLBindCol(sth, 1, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err);
882         SQLBindCol(sth, 2, SQL_C_CHAR, q->category, sizeof(q->category), &q->err);
883         SQLBindCol(sth, 3, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err);
884         SQLBindCol(sth, 4, SQL_C_CHAR, q->var_val, q->var_val_size, &q->err);
885
886         return sth;
887 }
888
889 static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl, const char *who_asked)
890 {
891         struct ast_variable *new_v;
892         struct ast_category *cur_cat;
893         int res = 0;
894         struct odbc_obj *obj;
895         char sqlbuf[1024] = "";
896         char *sql = sqlbuf;
897         size_t sqlleft = sizeof(sqlbuf);
898         unsigned int last_cat_metric = 0;
899         SQLSMALLINT rowcount = 0;
900         SQLHSTMT stmt;
901         char last[128] = "";
902         struct config_odbc_obj q;
903         struct ast_flags loader_flags = { 0 };
904         struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
905
906         memset(&q, 0, sizeof(q));
907
908         if (!file || !strcmp (file, "res_config_odbc.conf"))
909                 return NULL;            /* cant configure myself with myself ! */
910
911         obj = ast_odbc_request_obj2(database, connected_flag);
912         if (!obj)
913                 return NULL;
914
915         q.sql = sqlbuf;
916
917         ast_build_string(&sql, &sqlleft, "SELECT MAX(LENGTH(var_val)) FROM %s WHERE filename='%s'", table, file);
918
919         stmt = ast_odbc_prepare_and_execute(obj, length_determination_odbc_prepare, &q);
920
921         if (!stmt) {
922                 ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
923                 ast_odbc_release_obj(obj);
924                 return NULL;
925         }
926
927         res = SQLNumResultCols(stmt, &rowcount);
928
929         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
930                 ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
931                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
932                 ast_odbc_release_obj(obj);
933                 return NULL;
934         }
935
936         if (!rowcount) {
937                 ast_log(LOG_NOTICE, "found nothing\n");
938                 ast_odbc_release_obj(obj);
939                 return cfg;
940         }
941
942         /* There will be only one result for this, the maximum length of a variable value */
943         if (SQLFetch(stmt) == SQL_NO_DATA) {
944                 ast_log(LOG_NOTICE, "Failed to determine maximum length of a configuration value\n");
945                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
946                 ast_odbc_release_obj(obj);
947                 return NULL;
948         }
949
950         /* Reset stuff to a fresh state for the actual query which will retrieve all configuration */
951         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
952         sql = sqlbuf;
953         sqlleft = sizeof(sqlbuf);
954
955         ast_build_string(&sql, &sqlleft, "SELECT cat_metric, category, var_name, var_val FROM %s ", table);
956         ast_build_string(&sql, &sqlleft, "WHERE filename='%s' AND commented=0 ", file);
957         ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
958
959         q.var_val_size += 1;
960         q.var_val = ast_malloc(q.var_val_size);
961         if (!q.var_val) {
962                 ast_log(LOG_WARNING, "Could not create buffer for reading in configuration values for '%s'\n", file);
963                 ast_odbc_release_obj(obj);
964                 return NULL;
965         }
966
967         stmt = ast_odbc_prepare_and_execute(obj, config_odbc_prepare, &q);
968
969         if (!stmt) {
970                 ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
971                 ast_odbc_release_obj(obj);
972                 ast_free(q.var_val);
973                 return NULL;
974         }
975
976         res = SQLNumResultCols(stmt, &rowcount);
977
978         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
979                 ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
980                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
981                 ast_odbc_release_obj(obj);
982                 ast_free(q.var_val);
983                 return NULL;
984         }
985
986         if (!rowcount) {
987                 ast_log(LOG_NOTICE, "found nothing\n");
988                 ast_odbc_release_obj(obj);
989                 ast_free(q.var_val);
990                 return cfg;
991         }
992
993         cur_cat = ast_config_get_current_category(cfg);
994
995         while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
996                 if (!strcmp (q.var_name, "#include")) {
997                         if (!ast_config_internal_load(q.var_val, cfg, loader_flags, "", who_asked)) {
998                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
999                                 ast_odbc_release_obj(obj);
1000                                 ast_free(q.var_val);
1001                                 return NULL;
1002                         }
1003                         continue;
1004                 } 
1005                 if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) {
1006                         cur_cat = ast_category_new(q.category, "", 99999);
1007                         if (!cur_cat) {
1008                                 ast_log(LOG_WARNING, "Out of memory!\n");
1009                                 break;
1010                         }
1011                         strcpy(last, q.category);
1012                         last_cat_metric = q.cat_metric;
1013                         ast_category_append(cfg, cur_cat);
1014                 }
1015
1016                 new_v = ast_variable_new(q.var_name, q.var_val, "");
1017                 ast_variable_append(cur_cat, new_v);
1018         }
1019
1020         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1021         ast_odbc_release_obj(obj);
1022         ast_free(q.var_val);
1023         return cfg;
1024 }
1025
1026 #define warn_length(col, size)  ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' is not long enough to contain realtime data (needs %d)\n", table, database, col->name, size)
1027 #define warn_type(col, type)    ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' is of the incorrect type (%d) to contain the required realtime data\n", table, database, col->name, col->type)
1028
1029 static int require_odbc(const char *database, const char *table, va_list ap)
1030 {
1031         struct odbc_cache_tables *tableptr = ast_odbc_find_table(database, table);
1032         struct odbc_cache_columns *col;
1033         char *elm;
1034         int type, size;
1035
1036         if (!tableptr) {
1037                 return -1;
1038         }
1039
1040         while ((elm = va_arg(ap, char *))) {
1041                 type = va_arg(ap, require_type);
1042                 size = va_arg(ap, int);
1043                 /* Check if the field matches the criteria */
1044                 AST_RWLIST_TRAVERSE(&tableptr->columns, col, list) {
1045                         if (strcmp(col->name, elm) == 0) {
1046                                 /* Type check, first.  Some fields are more particular than others */
1047                                 switch (col->type) {
1048                                 case SQL_CHAR:
1049                                 case SQL_VARCHAR:
1050                                 case SQL_LONGVARCHAR:
1051 #ifdef HAVE_ODBC_WCHAR
1052                                 case SQL_WCHAR:
1053                                 case SQL_WVARCHAR:
1054                                 case SQL_WLONGVARCHAR:
1055 #endif
1056                                 case SQL_BINARY:
1057                                 case SQL_VARBINARY:
1058                                 case SQL_LONGVARBINARY:
1059                                 case SQL_GUID:
1060 #define CHECK_SIZE(n) \
1061                                                 if (col->size < n) {      \
1062                                                         warn_length(col, n);  \
1063                                                 }                         \
1064                                                 break;
1065                                         switch (type) {
1066                                         case RQ_UINTEGER1: CHECK_SIZE(3)  /*         255 */
1067                                         case RQ_INTEGER1:  CHECK_SIZE(4)  /*        -128 */
1068                                         case RQ_UINTEGER2: CHECK_SIZE(5)  /*       65535 */
1069                                         case RQ_INTEGER2:  CHECK_SIZE(6)  /*      -32768 */
1070                                         case RQ_UINTEGER3:                /*    16777215 */
1071                                         case RQ_INTEGER3:  CHECK_SIZE(8)  /*    -8388608 */
1072                                         case RQ_DATE:                     /*  2008-06-09 */
1073                                         case RQ_UINTEGER4: CHECK_SIZE(10) /*  4200000000 */
1074                                         case RQ_INTEGER4:  CHECK_SIZE(11) /* -2100000000 */
1075                                         case RQ_DATETIME:                 /* 2008-06-09 16:03:47 */
1076                                         case RQ_UINTEGER8: CHECK_SIZE(19) /* trust me    */
1077                                         case RQ_INTEGER8:  CHECK_SIZE(20) /* ditto       */
1078                                         case RQ_FLOAT:
1079                                         case RQ_CHAR:      CHECK_SIZE(size)
1080                                         }
1081 #undef CHECK_SIZE
1082                                         break;
1083                                 case SQL_TYPE_DATE:
1084                                         if (type != RQ_DATE) {
1085                                                 warn_type(col, type);
1086                                         }
1087                                         break;
1088                                 case SQL_TYPE_TIMESTAMP:
1089                                 case SQL_TIMESTAMP:
1090                                         if (type != RQ_DATE && type != RQ_DATETIME) {
1091                                                 warn_type(col, type);
1092                                         }
1093                                         break;
1094                                 case SQL_BIT:
1095                                         warn_length(col, size);
1096                                         break;
1097 #define WARN_TYPE_OR_LENGTH(n)  \
1098                                                 if (!ast_rq_is_int(type)) {  \
1099                                                         warn_type(col, type);    \
1100                                                 } else {                     \
1101                                                         warn_length(col, n);  \
1102                                                 }
1103                                 case SQL_TINYINT:
1104                                         if (type != RQ_UINTEGER1) {
1105                                                 WARN_TYPE_OR_LENGTH(size)
1106                                         }
1107                                         break;
1108                                 case SQL_C_STINYINT:
1109                                         if (type != RQ_INTEGER1) {
1110                                                 WARN_TYPE_OR_LENGTH(size)
1111                                         }
1112                                         break;
1113                                 case SQL_C_USHORT:
1114                                         if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
1115                                                 WARN_TYPE_OR_LENGTH(size)
1116                                         }
1117                                         break;
1118                                 case SQL_SMALLINT:
1119                                 case SQL_C_SSHORT:
1120                                         if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
1121                                                 WARN_TYPE_OR_LENGTH(size)
1122                                         }
1123                                         break;
1124                                 case SQL_C_ULONG:
1125                                         if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1126                                                 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1127                                                 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1128                                                 type != RQ_INTEGER4) {
1129                                                 WARN_TYPE_OR_LENGTH(size)
1130                                         }
1131                                         break;
1132                                 case SQL_INTEGER:
1133                                 case SQL_C_SLONG:
1134                                         if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1135                                                 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1136                                                 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1137                                                 type != RQ_INTEGER4) {
1138                                                 WARN_TYPE_OR_LENGTH(size)
1139                                         }
1140                                         break;
1141                                 case SQL_C_UBIGINT:
1142                                         if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1143                                                 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1144                                                 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1145                                                 type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1146                                                 type != RQ_INTEGER8) {
1147                                                 WARN_TYPE_OR_LENGTH(size)
1148                                         }
1149                                         break;
1150                                 case SQL_BIGINT:
1151                                 case SQL_C_SBIGINT:
1152                                         if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
1153                                                 type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
1154                                                 type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
1155                                                 type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
1156                                                 type != RQ_INTEGER8) {
1157                                                 WARN_TYPE_OR_LENGTH(size)
1158                                         }
1159                                         break;
1160 #undef WARN_TYPE_OR_LENGTH
1161                                 case SQL_NUMERIC:
1162                                 case SQL_DECIMAL:
1163                                 case SQL_FLOAT:
1164                                 case SQL_REAL:
1165                                 case SQL_DOUBLE:
1166                                         if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
1167                                                 warn_type(col, type);
1168                                         }
1169                                         break;
1170                                 default:
1171                                         ast_log(LOG_WARNING, "Realtime table %s@%s: column type (%d) unrecognized for column '%s'\n", table, database, col->type, elm);
1172                                 }
1173                                 break;
1174                         }
1175                 }
1176                 if (!col) {
1177                         ast_log(LOG_WARNING, "Realtime table %s@%s requires column '%s', but that column does not exist!\n", table, database, elm);
1178                 }
1179         }
1180         AST_RWLIST_UNLOCK(&tableptr->columns);
1181         return 0;
1182 }
1183 #undef warn_length
1184 #undef warn_type
1185
1186 static int unload_odbc(const char *a, const char *b)
1187 {
1188         return ast_odbc_clear_cache(a, b);
1189 }
1190
1191 static struct ast_config_engine odbc_engine = {
1192         .name = "odbc",
1193         .load_func = config_odbc,
1194         .realtime_func = realtime_odbc,
1195         .realtime_multi_func = realtime_multi_odbc,
1196         .store_func = store_odbc,
1197         .destroy_func = destroy_odbc,
1198         .update_func = update_odbc,
1199         .update2_func = update2_odbc,
1200         .require_func = require_odbc,
1201         .unload_func = unload_odbc,
1202 };
1203
1204 static int unload_module (void)
1205 {
1206         ast_config_engine_deregister(&odbc_engine);
1207
1208         return 0;
1209 }
1210
1211 static int load_module (void)
1212 {
1213         ast_config_engine_register(&odbc_engine);
1214
1215         return 0;
1216 }
1217
1218 static int reload_module(void)
1219 {
1220         return 0;
1221 }
1222
1223 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Realtime ODBC configuration",
1224                 .load = load_module,
1225                 .unload = unload_module,
1226                 .reload = reload_module,
1227                 .load_pri = AST_MODPRI_REALTIME_DRIVER,
1228                 );