minor simplification of a conditional statement
[asterisk/asterisk.git] / cdr / cdr_adaptive_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Tilghman Lesher
5  *
6  * Tilghman Lesher <cdr_adaptive_odbc__v1@the-tilghman.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Adaptive ODBC CDR backend
22  * 
23  * \author Tilghman Lesher <cdr_adaptive_odbc__v1@the-tilghman.com>
24  * \ingroup cdr_drivers
25  */
26
27 /*** MODULEINFO
28         <depend>unixodbc</depend>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <time.h>
41
42 #include <sql.h>
43 #include <sqlext.h>
44 #include <sqltypes.h>
45
46 #include "asterisk/config.h"
47 #include "asterisk/options.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/lock.h"
50 #include "asterisk/linkedlists.h"
51 #include "asterisk/res_odbc.h"
52 #include "asterisk/cdr.h"
53 #include "asterisk/module.h"
54 #include "asterisk/logger.h"
55
56 #define CONFIG  "cdr_adaptive_odbc.conf"
57
58 static char *name = "Adaptive ODBC";
59 /* Optimization to reduce number of memory allocations */
60 static int maxsize = 512, maxsize2 = 512;
61
62 struct columns {
63         char *name;
64         char *cdrname;
65         SQLSMALLINT type;
66         SQLINTEGER size;
67         SQLSMALLINT decimals;
68         SQLSMALLINT radix;
69         SQLSMALLINT nullable;
70         SQLINTEGER octetlen;
71         AST_LIST_ENTRY(columns) list;
72 };
73
74 struct tables {
75         char *connection;
76         char *table;
77         AST_LIST_HEAD_NOLOCK(odbc_columns, columns) columns;
78         AST_RWLIST_ENTRY(tables) list;
79 };
80
81 static AST_RWLIST_HEAD_STATIC(odbc_tables, tables);
82
83 static int load_config(void)
84 {
85         struct ast_config *cfg;
86         struct ast_variable *var;
87         const char *tmp, *catg;
88         struct tables *tableptr;
89         struct columns *entry;
90         struct odbc_obj *obj;
91         char columnname[80];
92         char connection[40];
93         char table[40];
94         int lenconnection, lentable;
95         SQLLEN sqlptr;
96         int res = 0;
97         SQLHSTMT stmt = NULL;
98
99         cfg = ast_config_load(CONFIG);
100         if (!cfg) {
101                 ast_log(LOG_WARNING, "Unable to load " CONFIG ".  No adaptive ODBC CDRs.\n");
102                 return -1;
103         }
104
105         for (catg = ast_category_browse(cfg, NULL); catg; catg = ast_category_browse(cfg, catg)) {
106                 var = ast_variable_browse(cfg, catg);
107                 if (!var)
108                         continue;
109
110                 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "connection"))) {
111                         ast_log(LOG_WARNING, "No connection parameter found in '%s'.  Skipping.\n", catg);
112                         continue;
113                 }
114                 ast_copy_string(connection, tmp, sizeof(connection));
115                 lenconnection = strlen(connection);
116
117                 /* When loading, we want to be sure we can connect. */
118                 obj = ast_odbc_request_obj(connection, 1);
119                 if (!obj) {
120                         ast_log(LOG_WARNING, "No such connection '%s' in the '%s' section of " CONFIG ".  Check res_odbc.conf.\n", connection, catg);
121                         continue;
122                 }
123
124                 if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "table"))) {
125                         ast_log(LOG_NOTICE, "No table name found.  Assuming 'cdr'.\n");
126                         tmp = "cdr";
127                 }
128                 ast_copy_string(table, tmp, sizeof(table));
129                 lentable = strlen(table);
130
131                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
132                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
133                         ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", connection);
134                         ast_odbc_release_obj(obj);
135                         continue;
136                 }
137
138                 res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)table, SQL_NTS, (unsigned char *)"%", SQL_NTS);
139                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
140                         ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'.  Skipping.\n", connection);
141                         ast_odbc_release_obj(obj);
142                         continue;
143                 }
144
145                 tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + lenconnection + 1 + lentable + 1);
146                 if (!tableptr) {
147                         ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", table, connection);
148                         ast_odbc_release_obj(obj);
149                         res = -1;
150                         break;
151                 }
152
153                 tableptr->connection = (char *)tableptr + sizeof(*tableptr);
154                 tableptr->table = (char *)tableptr + sizeof(*tableptr) + lenconnection + 1;
155                 ast_copy_string(tableptr->connection, connection, lenconnection + 1);
156                 ast_copy_string(tableptr->table, table, lentable + 1);
157
158                 ast_verb(3, "Found adaptive CDR table %s@%s.\n", tableptr->table, tableptr->connection);
159
160                 while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
161                         char *cdrvar = "";
162
163                         SQLGetData(stmt,  4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
164
165                         /* Is there an alias for this column? */
166
167                         /* NOTE: This seems like a non-optimal parse method, but I'm going
168                          * for user configuration readability, rather than fast parsing. We
169                          * really don't parse this file all that often, anyway.
170                          */
171                         for (var = ast_variable_browse(cfg, catg); var; var = var->next) {
172                                 if (strcasecmp(var->value, columnname) == 0) {
173                                         char *tmp = ast_strdupa(var->name + 5);
174                                         cdrvar = ast_strip(tmp);
175                                         ast_verb(3, "Found alias %s for column %s in %s@%s\n", cdrvar, columnname, tableptr->table, tableptr->connection);
176                                         break;
177                                 }
178                         }
179                         entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1 + strlen(cdrvar) + 1);
180                         if (!entry) {
181                                 ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, table, connection);
182                                 res = -1;
183                                 break;
184                         }
185                         entry->name = (char *)entry + sizeof(*entry);
186                         strcpy(entry->name, columnname);
187
188                         if (!ast_strlen_zero(cdrvar)) {
189                                 entry->cdrname = entry->name + strlen(columnname) + 1;
190                                 strcpy(entry->cdrname, cdrvar);
191                         } else /* Point to same place as the column name */
192                                 entry->cdrname = (char *)entry + sizeof(*entry);
193
194                         SQLGetData(stmt,  5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
195                         SQLGetData(stmt,  7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
196                         SQLGetData(stmt,  9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
197                         SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
198                         SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
199                         SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
200
201                         /* Specification states that the octenlen should be the maximum number of bytes
202                          * returned in a char or binary column, but it seems that some drivers just set
203                          * it to NULL. (Bad Postgres! No biscuit!) */
204                         if (entry->octetlen == 0)
205                                 entry->octetlen = entry->size;
206
207                         ast_verb(10, "Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, entry->size, entry->octetlen, entry->decimals, entry->radix);
208                         /* Insert column info into column list */
209                         AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
210                         res = 0;
211                 }
212
213                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
214                 ast_odbc_release_obj(obj);
215
216                 if (AST_LIST_FIRST(&(tableptr->columns)))
217                         AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
218                 else
219                         ast_free(tableptr);
220         }
221         return res;
222 }
223
224 static int free_config(void)
225 {
226         struct tables *table;
227         struct columns *entry;
228         while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
229                 while ((entry = AST_LIST_REMOVE_HEAD(&(table->columns), list))) {
230                         ast_free(entry);
231                 }
232                 ast_free(table);
233         }
234         return 0;
235 }
236
237 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
238 {
239         int res, i;
240         char *sql = data;
241         SQLHSTMT stmt;
242         SQLINTEGER nativeerror = 0, numfields = 0;
243         SQLSMALLINT diagbytes = 0;
244         unsigned char state[10], diagnostic[256];
245
246         res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
247         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
248                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
249                 return NULL;
250         }
251
252         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
253         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
254                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
255                 SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
256                 for (i = 0; i < numfields; i++) {
257                         SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
258                         ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
259                         if (i > 10) {
260                                 ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
261                                 break;
262                         }
263                 }
264                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
265                 return NULL;
266         }
267
268         return stmt;
269 }
270
271 #define LENGTHEN_BUF1(size)                                                                                                             \
272                         do {                                                                                                                            \
273                                 /* Lengthen buffer, if necessary */                                                             \
274                                 if ((newsize = lensql + (size) + 3) > sizesql) {        \
275                                         if ((tmp = ast_realloc(sql, (newsize / 512 + 1) * 512))) {      \
276                                                 sql = tmp;                                                                                              \
277                                                 sizesql = (newsize / 512 + 1) * 512;                                    \
278                                         } else {                                                                                                        \
279                                                 ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
280                                                 ast_free(sql);                                                                                  \
281                                                 ast_free(sql2);                                                                                 \
282                                                 AST_RWLIST_UNLOCK(&odbc_tables);                                                \
283                                                 return -1;                                                                                              \
284                                         }                                                                                                                       \
285                                 }                                                                                                                               \
286                         } while (0)
287
288 #define LENGTHEN_BUF2(size)                                                                                                             \
289                         do {                                                                                                                            \
290                                 if ((newsize = lensql2 + (size) + 3) > sizesql2) {                              \
291                                         if ((tmp = ast_realloc(sql2, (newsize / 512 + 1) * 512))) {     \
292                                                 sql2 = tmp;                                                                                             \
293                                                 sizesql2 = (newsize / 512 + 1) * 512;                                   \
294                                         } else {                                                                                                        \
295                                                 ast_log(LOG_ERROR, "Unable to allocate sufficient memory.  Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table);       \
296                                                 ast_free(sql);                                                                                  \
297                                                 ast_free(sql2);                                                                                 \
298                                                 AST_RWLIST_UNLOCK(&odbc_tables);                                                \
299                                                 return -1;                                                                                              \
300                                         }                                                                                                                       \
301                                 }                                                                                                                               \
302                         } while (0)
303
304 static int odbc_log(struct ast_cdr *cdr)
305 {
306         struct tables *tableptr;
307         struct columns *entry;
308         struct odbc_obj *obj;
309         int lensql, lensql2, sizesql = maxsize, sizesql2 = maxsize2, newsize;
310         /* Allocated, so we can realloc() */
311         char *sql = ast_calloc(sizeof(char), sizesql), *sql2 = ast_calloc(sizeof(char), sizesql2), *tmp;
312         char colbuf[1024], *colptr;
313         SQLHSTMT stmt = NULL;
314         SQLLEN rows = 0;
315
316         if (AST_RWLIST_RDLOCK(&odbc_tables)) {
317                 ast_log(LOG_ERROR, "Unable to lock table list.  Insert CDR(s) failed.\n");
318                 return -1;
319         }
320
321         AST_LIST_TRAVERSE(&odbc_tables, tableptr, list) {
322                 lensql = snprintf(sql, sizesql, "INSERT INTO %s (", tableptr->table);
323                 lensql2 = snprintf(sql2, sizesql2, " VALUES (");
324
325                 AST_LIST_TRAVERSE(&(tableptr->columns), entry, list) {
326                         /* Check if we have a similarly named variable */
327                         ast_cdr_getvar(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), 0,
328                                 (strcasecmp(entry->cdrname, "start") == 0 ||
329                                  strcasecmp(entry->cdrname, "answer") == 0 ||
330                                  strcasecmp(entry->cdrname, "end") == 0) ? 0 : 1);
331
332                         if (colptr) {
333                                 LENGTHEN_BUF1(strlen(entry->name));
334
335                                 switch (entry->type) {
336                                 case SQL_CHAR:
337                                 case SQL_VARCHAR:
338                                 case SQL_LONGVARCHAR:
339                                 case SQL_BINARY:
340                                 case SQL_VARBINARY:
341                                 case SQL_LONGVARBINARY:
342                                 case SQL_GUID:
343                                         /* For these two field names, get the rendered form, instead of the raw
344                                          * form (but only when we're dealing with a character-based field).
345                                          */
346                                         if (strcasecmp(entry->name, "disposition") == 0)
347                                                 ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
348                                         else if (strcasecmp(entry->name, "amaflags") == 0)
349                                                 ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
350
351                                         /* Truncate too-long fields */
352                                         if (entry->type != SQL_GUID) {
353                                                 if (strlen(colptr) > entry->octetlen)
354                                                         colptr[entry->octetlen] = '\0';
355                                         }
356
357                                         lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
358                                         LENGTHEN_BUF2(strlen(colptr));
359
360                                         /* Encode value, with escaping */
361                                         strcpy(sql2 + lensql2, "'");
362                                         lensql2++;
363                                         for (tmp = colptr; *tmp; tmp++) {
364                                                 if (*tmp == '\'') {
365                                                         strcpy(sql2 + lensql2, "''");
366                                                         lensql2 += 2;
367                                                 } else if (*tmp == '\\') {
368                                                         strcpy(sql2 + lensql2, "\\\\");
369                                                         lensql2 += 2;
370                                                 } else {
371                                                         sql2[lensql2++] = *tmp;
372                                                         sql2[lensql2] = '\0';
373                                                 }
374                                         }
375                                         strcpy(sql2 + lensql2, "',");
376                                         lensql2 += 2;
377                                         break;
378                                 case SQL_TYPE_DATE:
379                                         {
380                                                 int year = 0, month = 0, day = 0;
381                                                 if (sscanf(colptr, "%d-%d-%d", &year, &month, &day) != 3 || year <= 0 ||
382                                                         month <= 0 || month > 12 || day < 0 || day > 31 ||
383                                                         ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
384                                                         (month == 2 && year % 400 == 0 && day > 29) ||
385                                                         (month == 2 && year % 100 == 0 && day > 28) ||
386                                                         (month == 2 && year % 4 == 0 && day > 29) ||
387                                                         (month == 2 && year % 4 != 0 && day > 28)) {
388                                                         ast_log(LOG_WARNING, "CDR variable %s is not a valid date ('%s').\n", entry->name, colptr);
389                                                         break;
390                                                 }
391
392                                                 if (year > 0 && year < 100)
393                                                         year += 2000;
394
395                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
396                                                 LENGTHEN_BUF2(10);
397                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "'%04d-%02d-%02d',", year, month, day);
398                                         }
399                                         break;
400                                 case SQL_TYPE_TIME:
401                                         {
402                                                 int hour = 0, minute = 0, second = 0;
403                                                 int count = sscanf(colptr, "%d:%d:%d", &hour, &minute, &second);
404
405                                                 if ((count != 2 && count != 3) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
406                                                         ast_log(LOG_WARNING, "CDR variable %s is not a valid time ('%s').\n", entry->name, colptr);
407                                                         break;
408                                                 }
409
410                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
411                                                 LENGTHEN_BUF2(8);
412                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "'%02d:%02d:%02d',", hour, minute, second);
413                                         }
414                                         break;
415                                 case SQL_TYPE_TIMESTAMP:
416                                 case SQL_TIMESTAMP:
417                                         {
418                                                 int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
419                                                 int count = sscanf(colptr, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);
420
421                                                 if ((count != 3 && count != 5 && count != 6) || year <= 0 ||
422                                                         month <= 0 || month > 12 || day < 0 || day > 31 ||
423                                                         ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31) ||
424                                                         (month == 2 && year % 400 == 0 && day > 29) ||
425                                                         (month == 2 && year % 100 == 0 && day > 28) ||
426                                                         (month == 2 && year % 4 == 0 && day > 29) ||
427                                                         (month == 2 && year % 4 != 0 && day > 28) ||
428                                                         hour > 23 || minute > 59 || second > 59 || hour < 0 || minute < 0 || second < 0) {
429                                                         ast_log(LOG_WARNING, "CDR variable %s is not a valid timestamp ('%s').\n", entry->name, colptr);
430                                                         break;
431                                                 }
432
433                                                 if (year > 0 && year < 100)
434                                                         year += 2000;
435
436                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
437                                                 LENGTHEN_BUF2(19);
438                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "'%04d-%02d-%02d %02d:%02d:%02d',", year, month, day, hour, minute, second);
439                                         }
440                                         break;
441                                 case SQL_INTEGER:
442                                         {
443                                                 int integer = 0;
444                                                 if (sscanf(colptr, "%d", &integer) != 1) {
445                                                         ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
446                                                         break;
447                                                 }
448
449                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
450                                                 LENGTHEN_BUF2(12);
451                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
452                                         }
453                                         break;
454                                 case SQL_BIGINT:
455                                         {
456                                                 long long integer = 0;
457                                                 if (sscanf(colptr, "%lld", &integer) != 1) {
458                                                         ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
459                                                         break;
460                                                 }
461
462                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
463                                                 LENGTHEN_BUF2(24);
464                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%lld,", integer);
465                                         }
466                                         break;
467                                 case SQL_SMALLINT:
468                                         {
469                                                 short integer = 0;
470                                                 if (sscanf(colptr, "%hd", &integer) != 1) {
471                                                         ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
472                                                         break;
473                                                 }
474
475                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
476                                                 LENGTHEN_BUF2(6);
477                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
478                                         }
479                                         break;
480                                 case SQL_TINYINT:
481                                         {
482                                                 char integer = 0;
483                                                 if (sscanf(colptr, "%hhd", &integer) != 1) {
484                                                         ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
485                                                         break;
486                                                 }
487
488                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
489                                                 LENGTHEN_BUF2(4);
490                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
491                                         }
492                                         break;
493                                 case SQL_BIT:
494                                         {
495                                                 char integer = 0;
496                                                 if (sscanf(colptr, "%hhd", &integer) != 1) {
497                                                         ast_log(LOG_WARNING, "CDR variable %s is not an integer.\n", entry->name);
498                                                         break;
499                                                 }
500                                                 if (integer != 0)
501                                                         integer = 1;
502
503                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
504                                                 LENGTHEN_BUF2(2);
505                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%d,", integer);
506                                         }
507                                         break;
508                                 case SQL_NUMERIC:
509                                 case SQL_DECIMAL:
510                                         {
511                                                 double number = 0.0;
512                                                 if (sscanf(colptr, "%lf", &number) != 1) {
513                                                         ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
514                                                         break;
515                                                 }
516
517                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
518                                                 LENGTHEN_BUF2(entry->decimals);
519                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%*.*lf,", entry->decimals, entry->radix, number);
520                                         }
521                                         break;
522                                 case SQL_FLOAT:
523                                 case SQL_REAL:
524                                 case SQL_DOUBLE:
525                                         {
526                                                 double number = 0.0;
527                                                 if (sscanf(colptr, "%lf", &number) != 1) {
528                                                         ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name);
529                                                         break;
530                                                 }
531
532                                                 lensql += snprintf(sql + lensql, sizesql - lensql, "%s,", entry->name);
533                                                 LENGTHEN_BUF2(entry->decimals);
534                                                 lensql2 += snprintf(sql2 + lensql2, sizesql2 - lensql2, "%lf,", number);
535                                         }
536                                         break;
537                                 default:
538                                         ast_log(LOG_WARNING, "Column type %d (field '%s:%s:%s') is unsupported at this time.\n", entry->type, tableptr->connection, tableptr->table, entry->name);
539                                 }
540                         }
541                 }
542
543                 /* Concatenate the two constructed buffers */
544                 LENGTHEN_BUF1(lensql2);
545                 sql[lensql - 1] = ')';
546                 sql2[lensql2 - 1] = ')';
547                 strcat(sql + lensql, sql2);
548
549                 ast_verb(11, "[%s]\n", sql);
550                 /* No need to check the connection now; we'll handle any failure in prepare_and_execute */
551                 obj = ast_odbc_request_obj(tableptr->connection, 0);
552                 if (obj) {
553                         stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, sql);
554                         if (stmt) {
555                                 SQLRowCount(stmt, &rows);
556                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
557                         }
558                         if (rows == 0) {
559                                 ast_log(LOG_WARNING, "cdr_adaptive_odbc: Insert failed on '%s:%s'.  CDR failed: %s\n", tableptr->connection, tableptr->table, sql);
560                         }
561                         ast_odbc_release_obj(obj);
562                 } else {
563                         ast_log(LOG_WARNING, "cdr_adaptive_odbc: Unable to retrieve database handle for '%s:%s'.  CDR failed: %s\n", tableptr->connection, tableptr->table, sql);
564                 }
565         }
566         AST_RWLIST_UNLOCK(&odbc_tables);
567
568         /* Next time, just allocate buffers that are that big to start with. */
569         if (sizesql > maxsize)
570                 maxsize = sizesql;
571         if (sizesql2 > maxsize2)
572                 maxsize2 = sizesql2;
573
574         ast_free(sql);
575         ast_free(sql2);
576         return 0;
577 }
578
579 static int unload_module(void)
580 {
581         ast_cdr_unregister(name);
582         usleep(1);
583         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
584                 ast_cdr_register(name, ast_module_info->description, odbc_log);
585                 ast_log(LOG_ERROR, "Unable to lock column list.  Unload failed.\n");
586                 return -1;
587         }
588
589         free_config();
590         AST_RWLIST_UNLOCK(&odbc_tables);
591         return 0;
592 }
593
594 static int load_module(void)
595 {
596         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
597                 ast_log(LOG_ERROR, "Unable to lock column list.  Load failed.\n");
598                 return 0;
599         }
600
601         load_config();
602         AST_RWLIST_UNLOCK(&odbc_tables);
603         ast_cdr_register(name, ast_module_info->description, odbc_log);
604         return 0;
605 }
606
607 static int reload(void)
608 {
609         if (AST_RWLIST_WRLOCK(&odbc_tables)) {
610                 ast_log(LOG_ERROR, "Unable to lock column list.  Reload failed.\n");
611                 return -1;
612         }
613
614         free_config();
615         load_config();
616         AST_RWLIST_UNLOCK(&odbc_tables);
617         return 0;
618 }
619
620 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Adaptive ODBC CDR backend",
621         .load = load_module,
622         .unload = unload_module,
623         .reload = reload,
624 );
625