Sat Apr 26 2014 22:01:40

Asterisk developer's documentation


res_config_odbc.c
Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2010, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
00009  *
00010  * See http://www.asterisk.org for more information about
00011  * the Asterisk project. Please do not directly contact
00012  * any of the maintainers of this project for assistance;
00013  * the project provides a web site, mailing lists and IRC
00014  * channels for your use.
00015  *
00016  * This program is free software, distributed under the terms of
00017  * the GNU General Public License Version 2. See the LICENSE file
00018  * at the top of the source tree.
00019  */
00020 
00021 /*! \file
00022  *
00023  * \brief odbc+odbc plugin for portable configuration engine
00024  *
00025  * \author Mark Spencer <markster@digium.com>
00026  * \author Anthony Minessale II <anthmct@yahoo.com>
00027  *
00028  * \arg http://www.unixodbc.org
00029  */
00030 
00031 /*** MODULEINFO
00032    <depend>res_odbc</depend>
00033    <support_level>core</support_level>
00034  ***/
00035 
00036 #include "asterisk.h"
00037 
00038 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 411408 $")
00039 
00040 #include "asterisk/file.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/pbx.h"
00043 #include "asterisk/config.h"
00044 #include "asterisk/module.h"
00045 #include "asterisk/lock.h"
00046 #include "asterisk/res_odbc.h"
00047 #include "asterisk/utils.h"
00048 #include "asterisk/stringfields.h"
00049 
00050 AST_THREADSTORAGE(sql_buf);
00051 
00052 struct custom_prepare_struct {
00053    const char *sql;
00054    const char *extra;
00055    AST_DECLARE_STRING_FIELDS(
00056       AST_STRING_FIELD(encoding)[256];
00057    );
00058    va_list ap;
00059    unsigned long long skip;
00060 };
00061 
00062 static void decode_chunk(char *chunk)
00063 {
00064    for (; *chunk; chunk++) {
00065       if (*chunk == '^' && strchr("0123456789ABCDEF", chunk[1]) && strchr("0123456789ABCDEF", chunk[2])) {
00066          sscanf(chunk + 1, "%02hhX", chunk);
00067          memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
00068       }
00069    }
00070 }
00071 
00072 static inline int is_text(const struct odbc_cache_columns *column)
00073 {
00074    return column->type == SQL_CHAR || column->type == SQL_VARCHAR || column->type == SQL_LONGVARCHAR
00075       || column->type == SQL_WCHAR || column->type == SQL_WVARCHAR || column->type == SQL_WLONGVARCHAR;
00076 }
00077 
00078 static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
00079 {
00080    int res, x = 1, count = 0;
00081    struct custom_prepare_struct *cps = data;
00082    const char *newparam, *newval;
00083    char encodebuf[1024];
00084    SQLHSTMT stmt;
00085    va_list ap;
00086 
00087    res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00088    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00089       ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
00090       return NULL;
00091    }
00092 
00093    ast_debug(1, "Skip: %lld; SQL: %s\n", cps->skip, cps->sql);
00094 
00095    res = SQLPrepare(stmt, (unsigned char *)cps->sql, SQL_NTS);
00096    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00097       ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", cps->sql);
00098       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00099       return NULL;
00100    }
00101 
00102    va_copy(ap, cps->ap);
00103    while ((newparam = va_arg(ap, const char *))) {
00104       newval = va_arg(ap, const char *);
00105       if ((1LL << count++) & cps->skip) {
00106          ast_debug(1, "Skipping field '%s'='%s' (%llo/%llo)\n", newparam, newval, 1LL << (count - 1), cps->skip);
00107          continue;
00108       }
00109       ast_debug(1, "Parameter %d ('%s') = '%s'\n", x, newparam, newval);
00110       if (strchr(newval, ';') || strchr(newval, '^')) {
00111          char *eptr = encodebuf;
00112          const char *vptr = newval;
00113          for (; *vptr && eptr < encodebuf + sizeof(encodebuf); vptr++) {
00114             if (strchr("^;", *vptr)) {
00115                /* We use ^XX, instead of %XX because '%' is a special character in SQL */
00116                snprintf(eptr, encodebuf + sizeof(encodebuf) - eptr, "^%02hhX", *vptr);
00117                eptr += 3;
00118             } else {
00119                *eptr++ = *vptr;
00120             }
00121          }
00122          if (eptr < encodebuf + sizeof(encodebuf)) {
00123             *eptr = '\0';
00124          } else {
00125             encodebuf[sizeof(encodebuf) - 1] = '\0';
00126          }
00127          ast_string_field_set(cps, encoding[x], encodebuf);
00128          newval = cps->encoding[x];
00129       }
00130       SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
00131    }
00132    va_end(ap);
00133 
00134    if (!ast_strlen_zero(cps->extra))
00135       SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(cps->extra), 0, (void *)cps->extra, 0, NULL);
00136    return stmt;
00137 }
00138 
00139 /*!
00140  * \brief Excute an SQL query and return ast_variable list
00141  * \param database
00142  * \param table
00143  * \param ap list containing one or more field/operator/value set.
00144  *
00145  * Select database and preform query on table, prepare the sql statement
00146  * Sub-in the values to the prepared statement and execute it. Return results
00147  * as a ast_variable list.
00148  *
00149  * \retval var on success
00150  * \retval NULL on failure
00151 */
00152 static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap)
00153 {
00154    struct odbc_obj *obj;
00155    SQLHSTMT stmt;
00156    char sql[1024];
00157    char coltitle[256];
00158    char rowdata[2048];
00159    char *op;
00160    const char *newparam;
00161    char *stringp;
00162    char *chunk;
00163    SQLSMALLINT collen;
00164    int res;
00165    int x;
00166    struct ast_variable *var=NULL, *prev=NULL;
00167    SQLULEN colsize;
00168    SQLSMALLINT colcount=0;
00169    SQLSMALLINT datatype;
00170    SQLSMALLINT decimaldigits;
00171    SQLSMALLINT nullable;
00172    SQLLEN indicator;
00173    va_list aq;
00174    struct custom_prepare_struct cps = { .sql = sql };
00175    struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
00176 
00177    if (ast_string_field_init(&cps, 256)) {
00178       return NULL;
00179    }
00180 
00181    if (!table) {
00182       ast_string_field_free_memory(&cps);
00183       return NULL;
00184    }
00185 
00186    obj = ast_odbc_request_obj2(database, connected_flag);
00187 
00188    if (!obj) {
00189       ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", database);
00190       ast_string_field_free_memory(&cps);
00191       return NULL;
00192    }
00193 
00194    va_copy(aq, ap);
00195    newparam = va_arg(aq, const char *);
00196    if (!newparam) {
00197       va_end(aq);
00198       ast_odbc_release_obj(obj);
00199       ast_string_field_free_memory(&cps);
00200       return NULL;
00201    }
00202    va_arg(aq, const char *);
00203    op = !strchr(newparam, ' ') ? " =" : "";
00204    snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op,
00205       strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
00206    while((newparam = va_arg(aq, const char *))) {
00207       op = !strchr(newparam, ' ') ? " =" : "";
00208       snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op,
00209          strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
00210       va_arg(aq, const char *);
00211    }
00212    va_end(aq);
00213 
00214    va_copy(cps.ap, ap);
00215    stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
00216    va_end(cps.ap);
00217 
00218    if (!stmt) {
00219       ast_odbc_release_obj(obj);
00220       ast_string_field_free_memory(&cps);
00221       return NULL;
00222    }
00223 
00224    res = SQLNumResultCols(stmt, &colcount);
00225    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00226       ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
00227       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00228       ast_odbc_release_obj(obj);
00229       ast_string_field_free_memory(&cps);
00230       return NULL;
00231    }
00232 
00233    res = SQLFetch(stmt);
00234    if (res == SQL_NO_DATA) {
00235       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00236       ast_odbc_release_obj(obj);
00237       ast_string_field_free_memory(&cps);
00238       return NULL;
00239    }
00240    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00241       ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
00242       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00243       ast_odbc_release_obj(obj);
00244       ast_string_field_free_memory(&cps);
00245       return NULL;
00246    }
00247    for (x = 0; x < colcount; x++) {
00248       rowdata[0] = '\0';
00249       colsize = 0;
00250       collen = sizeof(coltitle);
00251       res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
00252                &datatype, &colsize, &decimaldigits, &nullable);
00253       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00254          ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
00255          if (var)
00256             ast_variables_destroy(var);
00257          ast_odbc_release_obj(obj);
00258          ast_string_field_free_memory(&cps);
00259          return NULL;
00260       }
00261 
00262       indicator = 0;
00263       res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator);
00264       if (indicator == SQL_NULL_DATA)
00265          rowdata[0] = '\0';
00266       else if (ast_strlen_zero(rowdata)) {
00267          /* Because we encode the empty string for a NULL, we will encode
00268           * actual empty strings as a string containing a single whitespace. */
00269          ast_copy_string(rowdata, " ", sizeof(rowdata));
00270       }
00271 
00272       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00273          ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
00274          if (var)
00275             ast_variables_destroy(var);
00276          ast_odbc_release_obj(obj);
00277          return NULL;
00278       }
00279       stringp = rowdata;
00280       while (stringp) {
00281          chunk = strsep(&stringp, ";");
00282          if (!ast_strlen_zero(ast_strip(chunk))) {
00283             if (strchr(chunk, '^')) {
00284                decode_chunk(chunk);
00285             }
00286             if (prev) {
00287                prev->next = ast_variable_new(coltitle, chunk, "");
00288                if (prev->next) {
00289                   prev = prev->next;
00290                }
00291             } else {
00292                prev = var = ast_variable_new(coltitle, chunk, "");
00293             }
00294          }
00295       }
00296    }
00297 
00298 
00299    SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00300    ast_odbc_release_obj(obj);
00301    ast_string_field_free_memory(&cps);
00302    return var;
00303 }
00304 
00305 /*!
00306  * \brief Excute an Select query and return ast_config list
00307  * \param database
00308  * \param table
00309  * \param ap list containing one or more field/operator/value set.
00310  *
00311  * Select database and preform query on table, prepare the sql statement
00312  * Sub-in the values to the prepared statement and execute it. 
00313  * Execute this prepared query against several ODBC connected databases.
00314  * Return results as an ast_config variable.
00315  *
00316  * \retval var on success
00317  * \retval NULL on failure
00318 */
00319 static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap)
00320 {
00321    struct odbc_obj *obj;
00322    SQLHSTMT stmt;
00323    char sql[1024];
00324    char coltitle[256];
00325    char rowdata[2048];
00326    const char *initfield;
00327    char *op;
00328    const char *newparam;
00329    char *stringp;
00330    char *chunk;
00331    SQLSMALLINT collen;
00332    int res;
00333    int x;
00334    struct ast_variable *var=NULL;
00335    struct ast_config *cfg=NULL;
00336    struct ast_category *cat=NULL;
00337    struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
00338    SQLULEN colsize;
00339    SQLSMALLINT colcount=0;
00340    SQLSMALLINT datatype;
00341    SQLSMALLINT decimaldigits;
00342    SQLSMALLINT nullable;
00343    SQLLEN indicator;
00344    struct custom_prepare_struct cps = { .sql = sql };
00345    va_list aq;
00346 
00347    if (!table || ast_string_field_init(&cps, 256)) {
00348       return NULL;
00349    }
00350 
00351 
00352    obj = ast_odbc_request_obj2(database, connected_flag);
00353    if (!obj) {
00354       ast_string_field_free_memory(&cps);
00355       return NULL;
00356    }
00357 
00358    va_copy(aq, ap);
00359    newparam = va_arg(aq, const char *);
00360    if (!newparam)  {
00361       va_end(aq);
00362       ast_odbc_release_obj(obj);
00363       ast_string_field_free_memory(&cps);
00364       return NULL;
00365    }
00366 
00367    initfield = ast_strdupa(newparam);
00368    if ((op = strchr(initfield, ' '))) {
00369       *op = '\0';
00370    }
00371 
00372    va_arg(aq, const char *);
00373    op = !strchr(newparam, ' ') ? " =" : "";
00374    snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op,
00375       strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
00376    while((newparam = va_arg(aq, const char *))) {
00377       op = !strchr(newparam, ' ') ? " =" : "";
00378       snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op,
00379          strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
00380       va_arg(aq, const char *);
00381    }
00382    va_end(aq);
00383 
00384    snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
00385 
00386    va_copy(cps.ap, ap);
00387    stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
00388    va_end(cps.ap);
00389 
00390    if (!stmt) {
00391       ast_odbc_release_obj(obj);
00392       ast_string_field_free_memory(&cps);
00393       return NULL;
00394    }
00395 
00396    res = SQLNumResultCols(stmt, &colcount);
00397    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00398       ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
00399       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00400       ast_odbc_release_obj(obj);
00401       ast_string_field_free_memory(&cps);
00402       return NULL;
00403    }
00404 
00405    cfg = ast_config_new();
00406    if (!cfg) {
00407       ast_log(LOG_WARNING, "Out of memory!\n");
00408       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00409       ast_odbc_release_obj(obj);
00410       ast_string_field_free_memory(&cps);
00411       return NULL;
00412    }
00413 
00414    while ((res=SQLFetch(stmt)) != SQL_NO_DATA) {
00415       var = NULL;
00416       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00417          ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
00418          continue;
00419       }
00420       cat = ast_category_new("","",99999);
00421       if (!cat) {
00422          ast_log(LOG_WARNING, "Out of memory!\n");
00423          continue;
00424       }
00425       for (x=0;x<colcount;x++) {
00426          rowdata[0] = '\0';
00427          colsize = 0;
00428          collen = sizeof(coltitle);
00429          res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
00430                   &datatype, &colsize, &decimaldigits, &nullable);
00431          if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00432             ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
00433             ast_category_destroy(cat);
00434             goto next_sql_fetch;
00435          }
00436 
00437          indicator = 0;
00438          res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator);
00439          if (indicator == SQL_NULL_DATA)
00440             continue;
00441 
00442          if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00443             ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
00444             ast_category_destroy(cat);
00445             goto next_sql_fetch;
00446          }
00447          stringp = rowdata;
00448          while (stringp) {
00449             chunk = strsep(&stringp, ";");
00450             if (!ast_strlen_zero(ast_strip(chunk))) {
00451                if (strchr(chunk, '^')) {
00452                   decode_chunk(chunk);
00453                }
00454                if (!strcmp(initfield, coltitle)) {
00455                   ast_category_rename(cat, chunk);
00456                }
00457                var = ast_variable_new(coltitle, chunk, "");
00458                ast_variable_append(cat, var);
00459             }
00460          }
00461       }
00462       ast_category_append(cfg, cat);
00463 next_sql_fetch:;
00464    }
00465 
00466    SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00467    ast_odbc_release_obj(obj);
00468    ast_string_field_free_memory(&cps);
00469    return cfg;
00470 }
00471 
00472 /*!
00473  * \brief Excute an UPDATE query
00474  * \param database
00475  * \param table
00476  * \param keyfield where clause field
00477  * \param lookup value of field for where clause
00478  * \param ap list containing one or more field/value set(s).
00479  *
00480  * Update a database table, prepare the sql statement using keyfield and lookup
00481  * control the number of records to change. All values to be changed are stored in ap list.
00482  * Sub-in the values to the prepared statement and execute it.
00483  *
00484  * \retval number of rows affected
00485  * \retval -1 on failure
00486 */
00487 static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
00488 {
00489    struct odbc_obj *obj;
00490    SQLHSTMT stmt;
00491    char sql[256];
00492    SQLLEN rowcount=0;
00493    const char *newparam, *newval;
00494    int res, count = 0, paramcount = 0;
00495    va_list aq;
00496    struct custom_prepare_struct cps = { .sql = sql, .extra = lookup };
00497    struct odbc_cache_tables *tableptr;
00498    struct odbc_cache_columns *column = NULL;
00499    struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
00500 
00501    if (!table || !keyfield) {
00502       return -1;
00503    }
00504 
00505    if (ast_string_field_init(&cps, 256)) {
00506       return -1;
00507    }
00508 
00509    tableptr = ast_odbc_find_table(database, table);
00510    if (!(obj = ast_odbc_request_obj2(database, connected_flag))) {
00511       ast_odbc_release_table(tableptr);
00512       ast_string_field_free_memory(&cps);
00513       return -1;
00514    }
00515 
00516    if (tableptr && !ast_odbc_find_column(tableptr, keyfield)) {
00517       ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'.  Update will fail\n", keyfield, table, database);
00518    }
00519 
00520    va_copy(aq, ap);
00521 
00522    snprintf(sql, sizeof(sql), "UPDATE %s SET ", table);
00523    while((newparam = va_arg(aq, const char *))) {
00524       newval = va_arg(aq, const char *);
00525       if ((tableptr && (column = ast_odbc_find_column(tableptr, newparam))) || count >= 64) {
00526          if (paramcount++) {
00527             snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", ");
00528          }
00529          /* NULL test for non-text columns */
00530          if (count < 64 && ast_strlen_zero(newval) && column->nullable && !is_text(column) && !ast_odbc_allow_empty_string_in_nontext(obj)) {
00531             snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=NULL", newparam);
00532             cps.skip |= (1LL << count);
00533          } else {
00534             /* Value is not an empty string, or column accepts empty strings, or we couldn't fit any more into cps.skip (count >= 64 ?!). */
00535             snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", newparam);
00536          }
00537       } else { /* the column does not exist in the table */
00538          cps.skip |= (1LL << count);
00539       }
00540       ++count;
00541    }
00542    va_end(aq);
00543    snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
00544    ast_odbc_release_table(tableptr);
00545 
00546    va_copy(cps.ap, ap);
00547    stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
00548    va_end(cps.ap);
00549 
00550    if (!stmt) {
00551       ast_odbc_release_obj(obj);
00552       ast_string_field_free_memory(&cps);
00553       return -1;
00554    }
00555 
00556    res = SQLRowCount(stmt, &rowcount);
00557    SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00558    ast_odbc_release_obj(obj);
00559    ast_string_field_free_memory(&cps);
00560 
00561    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00562       ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
00563       return -1;
00564    }
00565 
00566    if (rowcount >= 0) {
00567       return (int) rowcount;
00568    }
00569 
00570    return -1;
00571 }
00572 
00573 struct update2_prepare_struct {
00574    const char *database;
00575    const char *table;
00576    va_list ap;
00577 };
00578 
00579 static SQLHSTMT update2_prepare(struct odbc_obj *obj, void *data)
00580 {
00581    int res, x = 1, first = 1;
00582    struct update2_prepare_struct *ups = data;
00583    const char *newparam, *newval;
00584    struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
00585    SQLHSTMT stmt;
00586    va_list ap;
00587    struct odbc_cache_tables *tableptr = ast_odbc_find_table(ups->database, ups->table);
00588 
00589    if (!sql) {
00590       if (tableptr) {
00591          ast_odbc_release_table(tableptr);
00592       }
00593       return NULL;
00594    }
00595 
00596    if (!tableptr) {
00597       ast_log(LOG_ERROR, "Could not retrieve metadata for table '%s@%s'.  Update will fail!\n", ups->table, ups->database);
00598       return NULL;
00599    }
00600 
00601    res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00602    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00603       ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
00604       ast_odbc_release_table(tableptr);
00605       return NULL;
00606    }
00607 
00608    ast_str_set(&sql, 0, "UPDATE %s SET ", ups->table);
00609 
00610    /* Start by finding the second set of parameters */
00611    va_copy(ap, ups->ap);
00612 
00613    while ((newparam = va_arg(ap, const char *))) {
00614       newval = va_arg(ap, const char *);
00615    }
00616 
00617    while ((newparam = va_arg(ap, const char *))) {
00618       newval = va_arg(ap, const char *);
00619       if (ast_odbc_find_column(tableptr, newparam)) {
00620          ast_str_append(&sql, 0, "%s%s=? ", first ? "" : ", ", newparam);
00621          SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
00622          first = 0;
00623       } else {
00624          ast_log(LOG_NOTICE, "Not updating column '%s' in '%s@%s' because that column does not exist!\n", newparam, ups->table, ups->database);
00625       }
00626    }
00627    va_end(ap);
00628 
00629    ast_str_append(&sql, 0, "WHERE");
00630    first = 1;
00631 
00632    /* Restart search, because we need to add the search parameters */
00633    va_copy(ap, ups->ap);
00634 
00635    while ((newparam = va_arg(ap, const char *))) {
00636       newval = va_arg(ap, const char *);
00637       if (!ast_odbc_find_column(tableptr, newparam)) {
00638          va_end(ap);
00639          ast_log(LOG_ERROR, "One or more of the criteria columns '%s' on '%s@%s' for this update does not exist!\n", newparam, ups->table, ups->database);
00640          ast_odbc_release_table(tableptr);
00641          SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00642          return NULL;
00643       }
00644       ast_str_append(&sql, 0, "%s %s=?", first ? "" : " AND", newparam);
00645       SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
00646       first = 0;
00647    }
00648    va_end(ap);
00649 
00650    /* Done with the table metadata */
00651    ast_odbc_release_table(tableptr);
00652 
00653    res = SQLPrepare(stmt, (unsigned char *)ast_str_buffer(sql), SQL_NTS);
00654    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00655       ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", ast_str_buffer(sql));
00656       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00657       return NULL;
00658    }
00659 
00660    return stmt;
00661 }
00662 
00663 /*!
00664  * \brief Execute an UPDATE query
00665  * \param database
00666  * \param table
00667  * \param ap list containing one or more field/value set(s).
00668  *
00669  * Update a database table, preparing the sql statement from a list of
00670  * key/value pairs specified in ap.  The lookup pairs are specified first
00671  * and are separated from the update pairs by a sentinel value.
00672  * Sub-in the values to the prepared statement and execute it.
00673  *
00674  * \retval number of rows affected
00675  * \retval -1 on failure
00676 */
00677 static int update2_odbc(const char *database, const char *table, va_list ap)
00678 {
00679    struct odbc_obj *obj;
00680    SQLHSTMT stmt;
00681    struct update2_prepare_struct ups = { .database = database, .table = table, };
00682    struct ast_str *sql;
00683    int res;
00684    SQLLEN rowcount = 0;
00685 
00686    if (!(obj = ast_odbc_request_obj(database, 0))) {
00687       return -1;
00688    }
00689 
00690    va_copy(ups.ap, ap);
00691    if (!(stmt = ast_odbc_prepare_and_execute(obj, update2_prepare, &ups))) {
00692       va_end(ups.ap);
00693       ast_odbc_release_obj(obj);
00694       return -1;
00695    }
00696    va_end(ups.ap);
00697 
00698    res = SQLRowCount(stmt, &rowcount);
00699    SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00700    ast_odbc_release_obj(obj);
00701 
00702    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00703       /* Since only a single thread can access this memory, we can retrieve what would otherwise be lost. */
00704       sql = ast_str_thread_get(&sql_buf, 16);
00705       ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n", ast_str_buffer(sql));
00706       return -1;
00707    }
00708 
00709    if (rowcount >= 0) {
00710       return (int)rowcount;
00711    }
00712 
00713    return -1;
00714 }
00715 
00716 /*!
00717  * \brief Excute an INSERT query
00718  * \param database
00719  * \param table
00720  * \param ap list containing one or more field/value set(s)
00721  *
00722  * Insert a new record into database table, prepare the sql statement.
00723  * All values to be changed are stored in ap list.
00724  * Sub-in the values to the prepared statement and execute it.
00725  *
00726  * \retval number of rows affected
00727  * \retval -1 on failure
00728 */
00729 static int store_odbc(const char *database, const char *table, va_list ap)
00730 {
00731    struct odbc_obj *obj;
00732    SQLHSTMT stmt;
00733    char sql[256];
00734    char keys[256];
00735    char vals[256];
00736    SQLLEN rowcount=0;
00737    const char *newparam;
00738    int res;
00739    va_list aq;
00740    struct custom_prepare_struct cps = { .sql = sql, .extra = NULL };
00741    struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
00742 
00743    if (!table) {
00744       return -1;
00745    }
00746 
00747    obj = ast_odbc_request_obj2(database, connected_flag);
00748    if (!obj) {
00749       return -1;
00750    }
00751 
00752    va_copy(aq, ap);
00753 
00754    newparam = va_arg(aq, const char *);
00755    if (!newparam)  {
00756       va_end(aq);
00757       ast_odbc_release_obj(obj);
00758       return -1;
00759    }
00760    va_arg(aq, const char *);
00761    snprintf(keys, sizeof(keys), "%s", newparam);
00762    ast_copy_string(vals, "?", sizeof(vals));
00763    while ((newparam = va_arg(aq, const char *))) {
00764       snprintf(keys + strlen(keys), sizeof(keys) - strlen(keys), ", %s", newparam);
00765       snprintf(vals + strlen(vals), sizeof(vals) - strlen(vals), ", ?");
00766       va_arg(aq, const char *);
00767    }
00768    va_end(aq);
00769    snprintf(sql, sizeof(sql), "INSERT INTO %s (%s) VALUES (%s)", table, keys, vals);
00770 
00771 
00772    va_copy(cps.ap, ap);
00773    stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
00774    va_end(cps.ap);
00775 
00776    if (!stmt) {
00777       ast_odbc_release_obj(obj);
00778       return -1;
00779    }
00780 
00781    res = SQLRowCount(stmt, &rowcount);
00782    SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00783    ast_odbc_release_obj(obj);
00784 
00785    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00786       ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
00787       return -1;
00788    }
00789 
00790    if (rowcount >= 0)
00791       return (int)rowcount;
00792 
00793    return -1;
00794 }
00795 
00796 /*!
00797  * \brief Excute an DELETE query
00798  * \param database
00799  * \param table
00800  * \param keyfield where clause field
00801  * \param lookup value of field for where clause
00802  * \param ap list containing one or more field/value set(s)
00803  *
00804  * Delete a row from a database table, prepare the sql statement using keyfield and lookup
00805  * control the number of records to change. Additional params to match rows are stored in ap list.
00806  * Sub-in the values to the prepared statement and execute it.
00807  *
00808  * \retval number of rows affected
00809  * \retval -1 on failure
00810 */
00811 static int destroy_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
00812 {
00813    struct odbc_obj *obj;
00814    SQLHSTMT stmt;
00815    char sql[256];
00816    SQLLEN rowcount=0;
00817    const char *newparam;
00818    int res;
00819    va_list aq;
00820    struct custom_prepare_struct cps = { .sql = sql, .extra = lookup };
00821    struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
00822 
00823    if (!table) {
00824       return -1;
00825    }
00826 
00827    obj = ast_odbc_request_obj2(database, connected_flag);
00828    if (!obj) {
00829       return -1;
00830    }
00831 
00832    snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE ", table);
00833 
00834    va_copy(aq, ap);
00835    while((newparam = va_arg(aq, const char *))) {
00836       snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=? AND ", newparam);
00837       va_arg(aq, const char *);
00838    }
00839    va_end(aq);
00840    snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", keyfield);
00841 
00842    va_copy(cps.ap, ap);
00843    stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
00844    va_end(cps.ap);
00845 
00846    if (!stmt) {
00847       ast_odbc_release_obj(obj);
00848       return -1;
00849    }
00850 
00851    res = SQLRowCount(stmt, &rowcount);
00852    SQLFreeHandle (SQL_HANDLE_STMT, stmt);
00853    ast_odbc_release_obj(obj);
00854 
00855    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00856       ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
00857       return -1;
00858    }
00859 
00860    if (rowcount >= 0)
00861       return (int)rowcount;
00862 
00863    return -1;
00864 }
00865 
00866 
00867 struct config_odbc_obj {
00868    char *sql;
00869    unsigned long cat_metric;
00870    char category[128];
00871    char var_name[128];
00872    char var_val[1024]; /* changed from 128 to 1024 via bug 8251 */
00873    SQLLEN err;
00874 };
00875 
00876 static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
00877 {
00878    struct config_odbc_obj *q = data;
00879    SQLHSTMT sth;
00880    int res;
00881 
00882    res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
00883    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00884       ast_verb(4, "Failure in AllocStatement %d\n", res);
00885       return NULL;
00886    }
00887 
00888    res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS);
00889    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00890       ast_verb(4, "Error in PREPARE %d\n", res);
00891       SQLFreeHandle(SQL_HANDLE_STMT, sth);
00892       return NULL;
00893    }
00894 
00895    SQLBindCol(sth, 1, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err);
00896    SQLBindCol(sth, 2, SQL_C_CHAR, q->category, sizeof(q->category), &q->err);
00897    SQLBindCol(sth, 3, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err);
00898    SQLBindCol(sth, 4, SQL_C_CHAR, q->var_val, sizeof(q->var_val), &q->err);
00899 
00900    return sth;
00901 }
00902 
00903 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)
00904 {
00905    struct ast_variable *new_v;
00906    struct ast_category *cur_cat;
00907    int res = 0;
00908    struct odbc_obj *obj;
00909    char sqlbuf[1024] = "";
00910    char *sql = sqlbuf;
00911    size_t sqlleft = sizeof(sqlbuf);
00912    unsigned int last_cat_metric = 0;
00913    SQLSMALLINT rowcount = 0;
00914    SQLHSTMT stmt;
00915    char last[128] = "";
00916    struct config_odbc_obj q;
00917    struct ast_flags loader_flags = { 0 };
00918    struct ast_flags connected_flag = { RES_ODBC_CONNECTED };
00919 
00920    memset(&q, 0, sizeof(q));
00921 
00922    if (!file || !strcmp (file, "res_config_odbc.conf"))
00923       return NULL;      /* cant configure myself with myself ! */
00924 
00925    obj = ast_odbc_request_obj2(database, connected_flag);
00926    if (!obj)
00927       return NULL;
00928 
00929    ast_build_string(&sql, &sqlleft, "SELECT cat_metric, category, var_name, var_val FROM %s ", table);
00930    ast_build_string(&sql, &sqlleft, "WHERE filename='%s' AND commented=0 ", file);
00931    ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
00932    q.sql = sqlbuf;
00933 
00934    stmt = ast_odbc_prepare_and_execute(obj, config_odbc_prepare, &q);
00935 
00936    if (!stmt) {
00937       ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
00938       ast_odbc_release_obj(obj);
00939       return NULL;
00940    }
00941 
00942    res = SQLNumResultCols(stmt, &rowcount);
00943 
00944    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00945       ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
00946       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00947       ast_odbc_release_obj(obj);
00948       return NULL;
00949    }
00950 
00951    if (!rowcount) {
00952       ast_log(LOG_NOTICE, "found nothing\n");
00953       ast_odbc_release_obj(obj);
00954       return cfg;
00955    }
00956 
00957    cur_cat = ast_config_get_current_category(cfg);
00958 
00959    while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
00960       if (!strcmp (q.var_name, "#include")) {
00961          if (!ast_config_internal_load(q.var_val, cfg, loader_flags, "", who_asked)) {
00962             SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00963             ast_odbc_release_obj(obj);
00964             return NULL;
00965          }
00966          continue;
00967       } 
00968       if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) {
00969          cur_cat = ast_category_new(q.category, "", 99999);
00970          if (!cur_cat) {
00971             ast_log(LOG_WARNING, "Out of memory!\n");
00972             break;
00973          }
00974          strcpy(last, q.category);
00975          last_cat_metric   = q.cat_metric;
00976          ast_category_append(cfg, cur_cat);
00977       }
00978 
00979       new_v = ast_variable_new(q.var_name, q.var_val, "");
00980       ast_variable_append(cur_cat, new_v);
00981    }
00982 
00983    SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00984    ast_odbc_release_obj(obj);
00985    return cfg;
00986 }
00987 
00988 #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)
00989 #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)
00990 
00991 static int require_odbc(const char *database, const char *table, va_list ap)
00992 {
00993    struct odbc_cache_tables *tableptr = ast_odbc_find_table(database, table);
00994    struct odbc_cache_columns *col;
00995    char *elm;
00996    int type, size;
00997 
00998    if (!tableptr) {
00999       return -1;
01000    }
01001 
01002    while ((elm = va_arg(ap, char *))) {
01003       type = va_arg(ap, require_type);
01004       size = va_arg(ap, int);
01005       /* Check if the field matches the criteria */
01006       AST_RWLIST_TRAVERSE(&tableptr->columns, col, list) {
01007          if (strcmp(col->name, elm) == 0) {
01008             /* Type check, first.  Some fields are more particular than others */
01009             switch (col->type) {
01010             case SQL_CHAR:
01011             case SQL_VARCHAR:
01012             case SQL_LONGVARCHAR:
01013 #ifdef HAVE_ODBC_WCHAR
01014             case SQL_WCHAR:
01015             case SQL_WVARCHAR:
01016             case SQL_WLONGVARCHAR:
01017 #endif
01018             case SQL_BINARY:
01019             case SQL_VARBINARY:
01020             case SQL_LONGVARBINARY:
01021             case SQL_GUID:
01022 #define CHECK_SIZE(n) \
01023                   if (col->size < n) {      \
01024                      warn_length(col, n);  \
01025                   }                         \
01026                   break;
01027                switch (type) {
01028                case RQ_UINTEGER1: CHECK_SIZE(3)  /*         255 */
01029                case RQ_INTEGER1:  CHECK_SIZE(4)  /*        -128 */
01030                case RQ_UINTEGER2: CHECK_SIZE(5)  /*       65535 */
01031                case RQ_INTEGER2:  CHECK_SIZE(6)  /*      -32768 */
01032                case RQ_UINTEGER3:                /*    16777215 */
01033                case RQ_INTEGER3:  CHECK_SIZE(8)  /*    -8388608 */
01034                case RQ_DATE:                     /*  2008-06-09 */
01035                case RQ_UINTEGER4: CHECK_SIZE(10) /*  4200000000 */
01036                case RQ_INTEGER4:  CHECK_SIZE(11) /* -2100000000 */
01037                case RQ_DATETIME:                 /* 2008-06-09 16:03:47 */
01038                case RQ_UINTEGER8: CHECK_SIZE(19) /* trust me    */
01039                case RQ_INTEGER8:  CHECK_SIZE(20) /* ditto       */
01040                case RQ_FLOAT:
01041                case RQ_CHAR:      CHECK_SIZE(size)
01042                }
01043 #undef CHECK_SIZE
01044                break;
01045             case SQL_TYPE_DATE:
01046                if (type != RQ_DATE) {
01047                   warn_type(col, type);
01048                }
01049                break;
01050             case SQL_TYPE_TIMESTAMP:
01051             case SQL_TIMESTAMP:
01052                if (type != RQ_DATE && type != RQ_DATETIME) {
01053                   warn_type(col, type);
01054                }
01055                break;
01056             case SQL_BIT:
01057                warn_length(col, size);
01058                break;
01059 #define WARN_TYPE_OR_LENGTH(n)   \
01060                   if (!ast_rq_is_int(type)) {  \
01061                      warn_type(col, type);    \
01062                   } else {                     \
01063                      warn_length(col, n);  \
01064                   }
01065             case SQL_TINYINT:
01066                if (type != RQ_UINTEGER1) {
01067                   WARN_TYPE_OR_LENGTH(size)
01068                }
01069                break;
01070             case SQL_C_STINYINT:
01071                if (type != RQ_INTEGER1) {
01072                   WARN_TYPE_OR_LENGTH(size)
01073                }
01074                break;
01075             case SQL_C_USHORT:
01076                if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
01077                   WARN_TYPE_OR_LENGTH(size)
01078                }
01079                break;
01080             case SQL_SMALLINT:
01081             case SQL_C_SSHORT:
01082                if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
01083                   WARN_TYPE_OR_LENGTH(size)
01084                }
01085                break;
01086             case SQL_C_ULONG:
01087                if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
01088                   type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
01089                   type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
01090                   type != RQ_INTEGER4) {
01091                   WARN_TYPE_OR_LENGTH(size)
01092                }
01093                break;
01094             case SQL_INTEGER:
01095             case SQL_C_SLONG:
01096                if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
01097                   type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
01098                   type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
01099                   type != RQ_INTEGER4) {
01100                   WARN_TYPE_OR_LENGTH(size)
01101                }
01102                break;
01103             case SQL_C_UBIGINT:
01104                if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
01105                   type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
01106                   type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
01107                   type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
01108                   type != RQ_INTEGER8) {
01109                   WARN_TYPE_OR_LENGTH(size)
01110                }
01111                break;
01112             case SQL_BIGINT:
01113             case SQL_C_SBIGINT:
01114                if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
01115                   type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
01116                   type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
01117                   type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
01118                   type != RQ_INTEGER8) {
01119                   WARN_TYPE_OR_LENGTH(size)
01120                }
01121                break;
01122 #undef WARN_TYPE_OR_LENGTH
01123             case SQL_NUMERIC:
01124             case SQL_DECIMAL:
01125             case SQL_FLOAT:
01126             case SQL_REAL:
01127             case SQL_DOUBLE:
01128                if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
01129                   warn_type(col, type);
01130                }
01131                break;
01132             default:
01133                ast_log(LOG_WARNING, "Realtime table %s@%s: column type (%d) unrecognized for column '%s'\n", table, database, col->type, elm);
01134             }
01135             break;
01136          }
01137       }
01138       if (!col) {
01139          ast_log(LOG_WARNING, "Realtime table %s@%s requires column '%s', but that column does not exist!\n", table, database, elm);
01140       }
01141    }
01142    AST_RWLIST_UNLOCK(&tableptr->columns);
01143    return 0;
01144 }
01145 #undef warn_length
01146 #undef warn_type
01147 
01148 static int unload_odbc(const char *a, const char *b)
01149 {
01150    return ast_odbc_clear_cache(a, b);
01151 }
01152 
01153 static struct ast_config_engine odbc_engine = {
01154    .name = "odbc",
01155    .load_func = config_odbc,
01156    .realtime_func = realtime_odbc,
01157    .realtime_multi_func = realtime_multi_odbc,
01158    .store_func = store_odbc,
01159    .destroy_func = destroy_odbc,
01160    .update_func = update_odbc,
01161    .update2_func = update2_odbc,
01162    .require_func = require_odbc,
01163    .unload_func = unload_odbc,
01164 };
01165 
01166 static int unload_module (void)
01167 {
01168    ast_config_engine_deregister(&odbc_engine);
01169 
01170    ast_verb(1, "res_config_odbc unloaded.\n");
01171    return 0;
01172 }
01173 
01174 static int load_module (void)
01175 {
01176    ast_config_engine_register(&odbc_engine);
01177    ast_verb(1, "res_config_odbc loaded.\n");
01178    return 0;
01179 }
01180 
01181 static int reload_module(void)
01182 {
01183    return 0;
01184 }
01185 
01186 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Realtime ODBC configuration",
01187       .load = load_module,
01188       .unload = unload_module,
01189       .reload = reload_module,
01190       .load_pri = AST_MODPRI_REALTIME_DRIVER,
01191       );