Mon Oct 8 12:39:04 2012

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

Generated on Mon Oct 8 12:39:04 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7