update res_odbc to support pooled connections
authorRussell Bryant <russell@russellbryant.com>
Tue, 18 Apr 2006 18:16:32 +0000 (18:16 +0000)
committerRussell Bryant <russell@russellbryant.com>
Tue, 18 Apr 2006 18:16:32 +0000 (18:16 +0000)
(from tilghman's developer branch, res_odbc_rewrite)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@21181 65c4cc65-6c06-0410-ace0-fbb531ad65f3

funcs/func_odbc.c
include/asterisk/res_odbc.h
res/res_config_odbc.c
res/res_odbc.c

index 63c58df..b9751ed 100644 (file)
@@ -77,7 +77,7 @@ static void acf_odbc_error(SQLHSTMT stmt, int res)
  */
 static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const char *value)
 {
-       odbc_obj *obj;
+       struct odbc_obj *obj;
        struct acf_odbc_query *query;
        char *t, *arg, buf[2048]="", varname[15];
        int res, argcount=0, valcount=0, i, retry=0;
@@ -104,10 +104,10 @@ static int acf_odbc_write(struct ast_channel *chan, char *cmd, char *s, const ch
                return -1;
        }
 
-       obj = fetch_odbc_obj(query->dsn, 0);
+       obj = odbc_request_obj(query->dsn, 0);
 
        if (!obj) {
-               ast_log(LOG_ERROR, "No such DSN registered: %s (check res_odbc.conf)\n", query->dsn);
+               ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn);
                ast_mutex_unlock(&query_lock);
                return -1;
        }
@@ -204,9 +204,9 @@ retry_write:
                        }
                }
                SQLFreeHandle(SQL_HANDLE_STMT, stmt);
-               odbc_obj_disconnect(obj);
+               odbc_release_obj(obj);
                /* All handles are now invalid (after a disconnect), so we gotta redo all handles */
-               odbc_obj_connect(obj);
+               obj = odbc_request_obj("asterisk", 1);
                if (!retry) {
                        retry = 1;
                        goto retry_write;
@@ -235,7 +235,7 @@ retry_write:
 
 static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf, size_t len)
 {
-       odbc_obj *obj;
+       struct odbc_obj *obj;
        struct acf_odbc_query *query;
        char *arg, sql[2048] = "", varname[15];
        int count=0, res, x;
@@ -260,10 +260,10 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf
                return -1;
        }
 
-       obj = fetch_odbc_obj(query->dsn, 0);
+       obj = odbc_request_obj(query->dsn, 0);
 
        if (!obj) {
-               ast_log(LOG_ERROR, "No such DSN registered: %s (check res_odbc.conf)\n", query->dsn);
+               ast_log(LOG_ERROR, "No such DSN registered (or out of connections): %s (check res_odbc.conf)\n", query->dsn);
                ast_mutex_unlock(&query_lock);
                return -1;
        }
@@ -331,7 +331,7 @@ static int acf_odbc_read(struct ast_channel *chan, char *cmd, char *s, char *buf
                goto acf_out;
        }
 
-       for (x=0; x<colcount; x++) {
+       for (x = 0; x < colcount; x++) {
                int buflen, coldatalen;
                char coldata[256];
 
index 9e4070b..bbf0996 100644 (file)
@@ -3,9 +3,11 @@
  *
  * Copyright (C) 1999 - 2005, Digium, Inc.
  * Copyright (C) 2004 - 2005, Anthony Minessale II
+ * Copyright (C) 2006, Tilghman Lesher
  *
  * Mark Spencer <markster@digium.com>
  * Anthony Minessale <anthmct@yahoo.com>
+ * Tilghman Lesher <res_odbc_200603@the-tilghman.com>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
 #include <sqlext.h>
 #include <sqltypes.h>
 
-typedef struct odbc_obj odbc_obj;
-
-typedef enum { ODBC_SUCCESS=0,ODBC_FAIL=-1} odbc_status;
+typedef enum { ODBC_SUCCESS=0, ODBC_FAIL=-1} odbc_status;
 
 struct odbc_obj {
-       char *name;
-       char *dsn;
-       char *username;
-       char *password;
-       SQLHENV  env;                   /* ODBC Environment */
-       SQLHDBC  con;                   /* ODBC Connection Handle */
-       SQLHSTMT stmt;                  /* ODBC Statement Handle */
        ast_mutex_t lock;
-       int up;
-
+       SQLHDBC  con;                   /* ODBC Connection Handle */
+       struct odbc_class *parent;      /* Information about the connection is protected */
+       unsigned int used:1;
+       unsigned int up:1;
+       AST_LIST_ENTRY(odbc_obj) list;
 };
 
 /* functions */
-odbc_obj *new_odbc_obj(char *name,char *dsn,char *username, char *password);
-odbc_status odbc_obj_connect(odbc_obj *obj);
-odbc_status odbc_obj_disconnect(odbc_obj *obj);
-void destroy_odbc_obj(odbc_obj **obj);
-int register_odbc_obj(char *name,odbc_obj *obj);
-odbc_obj *fetch_odbc_obj(const char *name, int check);
-int odbc_dump_fd(int fd,odbc_obj *obj);
-int odbc_sanity_check(odbc_obj *obj);
-SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj *obj, void *data), void *data);
-int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt);
-int odbc_smart_direct_execute(odbc_obj *obj, SQLHSTMT stmt, char *sql);
+
+/*! \brief Executes a prepared statement handle
+ * \param obj The non-NULL result of odbc_request_obj()
+ * \param stmt The prepared statement handle
+ * \return Returns 0 on success or -1 on failure
+ *
+ * This function was originally designed simply to execute a prepared
+ * statement handle and to retry if the initial execution failed.
+ * Unfortunately, it did this by disconnecting and reconnecting the database
+ * handle which on most databases causes the statement handle to become
+ * invalid.  Therefore, this method has been deprecated in favor of
+ * odbc_prepare_and_execute() which allows the statement to be prepared
+ * multiple times, if necessary, in case of a loss of connection.
+ *
+ * This function really only ever worked with MySQL, where the statement handle is
+ * not prepared on the server.  If you are not using MySQL, you should avoid it.
+ */
+int odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt); /* DEPRECATED */
+
+/*! \brief Retrieves a connected ODBC object
+ * \param name The name of the ODBC class for which a connection is needed.
+ * \param check Whether to ensure that a connection is valid before returning the handle.  Usually unnecessary.
+ * \return Returns an ODBC object or NULL if there is no connection available with the requested name.
+ *
+ * Connection classes may, in fact, contain multiple connection handles.  If
+ * the connection is pooled, then each connection will be dedicated to the
+ * thread which requests it.  Note that all connections should be released
+ * when the thread is done by calling odbc_release_obj(), below.
+ */
+struct odbc_obj *odbc_request_obj(const char *name, int check);
+
+/*! \brief Releases an ODBC object previously allocated by odbc_request_obj()
+ * \param obj The ODBC object
+ */
+void odbc_release_obj(struct odbc_obj *obj);
+
+/*! \brief Checks an ODBC object to ensure it is still connected
+ * \param obj The ODBC object
+ * \return Returns 0 if connected, -1 otherwise.
+ */
+int odbc_sanity_check(struct odbc_obj *obj);
+
+/*! \brief Prepares, executes, and returns the resulting statement handle.
+ * \param obj The ODBC object
+ * \param prepare_cb A function callback, which, when called, should return a statement handle prepared, with any necessary parameters or result columns bound.
+ * \param data A parameter to be passed to the prepare_cb parameter function, indicating which statement handle is to be prepared.
+ * \return Returns a statement handle or NULL on error.
+ */
+SQLHSTMT odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data);
 
 #endif /* _ASTERISK_RES_ODBC_H */
index f2634a9..e48b01c 100644 (file)
@@ -52,7 +52,7 @@ LOCAL_USER_DECL;
 
 static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap)
 {
-       odbc_obj *obj;
+       struct odbc_obj *obj;
        SQLHSTMT stmt;
        char sql[1024];
        char coltitle[256];
@@ -79,19 +79,21 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
        if (!table)
                return NULL;
 
-       obj = fetch_odbc_obj(database, 0);
+       obj = odbc_request_obj(database, 0);
        if (!obj)
                return NULL;
 
        res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
+               odbc_release_obj(obj);
                return NULL;
        }
 
        newparam = va_arg(aq, const char *);
        if (!newparam)  {
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
        newval = va_arg(aq, const char *);
@@ -107,6 +109,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
        
@@ -123,6 +126,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
 
@@ -130,20 +134,23 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
 
        res = SQLFetch(stmt);
        if (res == SQL_NO_DATA) {
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                 return NULL;
        }
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
-       for (x=0;x<colcount;x++) {
+       for (x = 0; x < colcount; x++) {
                rowdata[0] = '\0';
                collen = sizeof(coltitle);
                res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, 
@@ -152,6 +159,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
                        ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
                        if (var)
                                ast_variables_destroy(var);
+                       odbc_release_obj(obj);
                        return NULL;
                }
 
@@ -164,6 +172,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
                        ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
                        if (var)
                                ast_variables_destroy(var);
+                       odbc_release_obj(obj);
                        return NULL;
                }
                stringp = rowdata;
@@ -183,12 +192,13 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
 
 
        SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+       odbc_release_obj(obj);
        return var;
 }
 
 static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap)
 {
-       odbc_obj *obj;
+       struct odbc_obj *obj;
        SQLHSTMT stmt;
        char sql[1024];
        char coltitle[256];
@@ -220,19 +230,21 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char *
                return NULL;
        memset(&ra, 0, sizeof(ra));
 
-       obj = fetch_odbc_obj(database, 0);
+       obj = odbc_request_obj(database, 0);
        if (!obj)
                return NULL;
 
        res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
+               odbc_release_obj(obj);
                return NULL;
        }
 
        newparam = va_arg(aq, const char *);
        if (!newparam)  {
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
        initfield = ast_strdupa(newparam);
@@ -252,7 +264,8 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char *
        res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
        
@@ -268,21 +281,24 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char *
 
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
 
        res = SQLNumResultCols(stmt, &colcount);
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
 
        cfg = ast_config_new();
        if (!cfg) {
                ast_log(LOG_WARNING, "Out of memory!\n");
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
 
@@ -332,13 +348,14 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char *
                ast_category_append(cfg, cat);
        }
 
-       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+       odbc_release_obj(obj);
        return cfg;
 }
 
 static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
 {
-       odbc_obj *obj;
+       struct odbc_obj *obj;
        SQLHSTMT stmt;
        char sql[256];
        SQLLEN rowcount=0;
@@ -352,19 +369,21 @@ static int update_odbc(const char *database, const char *table, const char *keyf
        if (!table)
                return -1;
 
-       obj = fetch_odbc_obj (database, 0);
+       obj = odbc_request_obj(database, 0);
        if (!obj)
                return -1;
 
        res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
+               odbc_release_obj(obj);
                return -1;
        }
 
        newparam = va_arg(aq, const char *);
        if (!newparam)  {
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return -1;
        }
        newval = va_arg(aq, const char *);
@@ -380,6 +399,7 @@ static int update_odbc(const char *database, const char *table, const char *keyf
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return -1;
        }
        
@@ -398,103 +418,147 @@ static int update_odbc(const char *database, const char *table, const char *keyf
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return -1;
        }
 
        res = SQLRowCount(stmt, &rowcount);
        SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+       odbc_release_obj(obj);
 
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
                return -1;
        }
 
-       if (rowcount >= 0)
-               return (int)rowcount;
+       if (rowcount >= 0)
+               return (int)rowcount;
 
        return -1;
 }
 
+struct config_odbc_obj {
+       char *sql;
+       unsigned long id;
+       unsigned long cat_metric;
+       unsigned long var_metric;
+       unsigned long commented;
+       char filename[128];
+       char category[128];
+       char var_name[128];
+       char var_val[128];
+       SQLINTEGER err;
+};
+
+static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
+{
+       struct config_odbc_obj *q = data;
+       SQLHSTMT sth;
+       int res;
+
+       res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
+       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+               if (option_verbose > 3)
+                       ast_verbose( VERBOSE_PREFIX_4 "Failure in AllocStatement %d\n", res);
+               return NULL;
+       }
+
+       res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS);
+       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+               if (option_verbose > 3)
+                       ast_verbose( VERBOSE_PREFIX_4 "Error in PREPARE %d\n", res);
+               SQLFreeHandle(SQL_HANDLE_STMT, sth);
+               return NULL;
+       }
+
+       SQLBindCol(sth, 1, SQL_C_ULONG, &q->id, sizeof(q->id), &q->err);
+       SQLBindCol(sth, 2, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err);
+       SQLBindCol(sth, 3, SQL_C_ULONG, &q->var_metric, sizeof(q->var_metric), &q->err);
+       SQLBindCol(sth, 4, SQL_C_ULONG, &q->commented, sizeof(q->commented), &q->err);
+       SQLBindCol(sth, 5, SQL_C_CHAR, q->filename, sizeof(q->filename), &q->err);
+       SQLBindCol(sth, 6, SQL_C_CHAR, q->category, sizeof(q->category), &q->err);
+       SQLBindCol(sth, 7, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err);
+       SQLBindCol(sth, 8, SQL_C_CHAR, q->var_val, sizeof(q->var_val), &q->err);
+
+       return sth;
+}
+
 static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg)
 {
        struct ast_variable *new_v;
        struct ast_category *cur_cat;
        int res = 0;
-       odbc_obj *obj;
-       SQLINTEGER err=0, commented=0, cat_metric=0, var_metric=0, last_cat_metric=0;
-       SQLBIGINT id;
-       char sql[255] = "", filename[128], category[128], var_name[128], var_val[512];
+       struct odbc_obj *obj;
+       char sql[255] = "";
+       unsigned int last_cat_metric = 0;
        SQLSMALLINT rowcount=0;
        SQLHSTMT stmt;
        char last[128] = "";
+       struct config_odbc_obj q;
+
+       memset(&q, 0, sizeof(q));
 
        if (!file || !strcmp (file, "res_config_odbc.conf"))
                return NULL;            /* cant configure myself with myself ! */
 
-       obj = fetch_odbc_obj(database, 0);
+       obj = odbc_request_obj(database, 0);
        if (!obj)
                return NULL;
 
-       res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
-
-       SQLBindCol (stmt, 1, SQL_C_ULONG, &id, sizeof (id), &err);
-       SQLBindCol (stmt, 2, SQL_C_ULONG, &cat_metric, sizeof (cat_metric), &err);
-       SQLBindCol (stmt, 3, SQL_C_ULONG, &var_metric, sizeof (var_metric), &err);
-       SQLBindCol (stmt, 4, SQL_C_ULONG, &commented, sizeof (commented), &err);
-       SQLBindCol (stmt, 5, SQL_C_CHAR, &filename, sizeof (filename), &err);
-       SQLBindCol (stmt, 6, SQL_C_CHAR, &category, sizeof (category), &err);
-       SQLBindCol (stmt, 7, SQL_C_CHAR, &var_name, sizeof (var_name), &err);
-       SQLBindCol (stmt, 8, SQL_C_CHAR, &var_val, sizeof (var_val), &err);
-       
        snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE filename='%s' and commented=0 ORDER BY filename,cat_metric desc,var_metric asc,category,var_name,var_val,id", table, file);
+       q.sql = sql;
 
-       res = odbc_smart_direct_execute(obj, stmt, sql);
-       
-       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-               ast_log (LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+       stmt = odbc_prepare_and_execute(obj, config_odbc_prepare, &q);
+
+       if (!stmt) {
+               ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
+               odbc_release_obj(obj);
                return NULL;
        }
 
-       res = SQLNumResultCols (stmt, &rowcount);
+       res = SQLNumResultCols(stmt, &rowcount);
 
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-               ast_log (LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
-               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
+               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+               odbc_release_obj(obj);
                return NULL;
        }
 
        if (!rowcount) {
-               ast_log (LOG_NOTICE, "found nothing\n");
+               ast_log(LOG_NOTICE, "found nothing\n");
+               odbc_release_obj(obj);
                return cfg;
        }
 
        cur_cat = ast_config_get_current_category(cfg);
 
        while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
-               if (!strcmp (var_name, "#include")) {
-                       if (!ast_config_internal_load(var_val, cfg)) {
-                               SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+               if (!strcmp (q.var_name, "#include")) {
+                       if (!ast_config_internal_load(q.var_val, cfg)) {
+                               SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+                               odbc_release_obj(obj);
                                return NULL;
                        }
                        continue;
                } 
-               if (strcmp(last, category) || last_cat_metric != cat_metric) {
-                       cur_cat = ast_category_new(category);
+               if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) {
+                       cur_cat = ast_category_new(q.category);
                        if (!cur_cat) {
                                ast_log(LOG_WARNING, "Out of memory!\n");
                                break;
                        }
-                       strcpy(last, category);
-                       last_cat_metric = cat_metric;
+                       strcpy(last, q.category);
+                       last_cat_metric = q.cat_metric;
                        ast_category_append(cfg, cur_cat);
                }
 
-               new_v = ast_variable_new(var_name, var_val);
+               new_v = ast_variable_new(q.var_name, q.var_val);
                ast_variable_append(cur_cat, new_v);
        }
 
-       SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+       odbc_release_obj(obj);
        return cfg;
 }
 
index cc25584..76fd217 100644 (file)
@@ -49,66 +49,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/lock.h"
 #include "asterisk/res_odbc.h"
-#define MAX_ODBC_HANDLES 25
 
-struct odbc_list
+struct odbc_class
 {
+       AST_LIST_ENTRY(odbc_class) list;
        char name[80];
-       odbc_obj *obj;
-       int used;
+       char dsn[80];
+       char username[80];
+       char password[80];
+       SQLHENV env;
+       unsigned int haspool:1;         /* Boolean - TDS databases need this */
+       unsigned int limit:10;          /* Gives a limit of 1023 maximum */
+       unsigned int count:10;          /* Running count of pooled connections */
+       unsigned int delme:1;                   /* Purge the class */
+       AST_LIST_HEAD(, odbc_obj) odbc_obj;
 };
 
-static struct odbc_list ODBC_REGISTRY[MAX_ODBC_HANDLES];
+AST_LIST_HEAD_STATIC(odbc_list, odbc_class);
 
+static odbc_status odbc_obj_connect(struct odbc_obj *obj);
+static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
+static int odbc_register_class(struct odbc_class *class, int connect);
 
-static void odbc_destroy(void)
-{
-       int x = 0;
-
-       for (x = 0; x < MAX_ODBC_HANDLES; x++) {
-               if (ODBC_REGISTRY[x].obj) {
-                       destroy_odbc_obj(&ODBC_REGISTRY[x].obj);
-                       ODBC_REGISTRY[x].obj = NULL;
-               }
-       }
-}
-
-static odbc_obj *odbc_read(struct odbc_list *registry, const char *name)
-{
-       int x = 0;
-       for (x = 0; x < MAX_ODBC_HANDLES; x++) {
-               if (registry[x].used && !strcmp(registry[x].name, name)) {
-                       return registry[x].obj;
-               }
-       }
-       return NULL;
-}
-
-static int odbc_write(struct odbc_list *registry, char *name, odbc_obj *obj)
-{
-       int x = 0;
-       for (x = 0; x < MAX_ODBC_HANDLES; x++) {
-               if (!registry[x].used) {
-                       ast_copy_string(registry[x].name, name, sizeof(registry[x].name));
-                       registry[x].obj = obj;
-                       registry[x].used = 1;
-                       return 1;
-               }
-       }
-       return 0;
-}
-
-static void odbc_init(void)
-{
-       int x = 0;
-       for (x = 0; x < MAX_ODBC_HANDLES; x++) {
-               memset(&ODBC_REGISTRY[x], 0, sizeof(struct odbc_list));
-       }
-}
-
-/* internal stuff */
 
-SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj *obj, void *data), void *data)
+SQLHSTMT odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
 {
        int res = 0, i, attempt;
        SQLINTEGER nativeerror=0, numfields=0;
@@ -142,9 +106,12 @@ SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj
                                ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
                                SQLFreeHandle(SQL_HANDLE_STMT, stmt);
 
-                               ast_mutex_lock(&obj->lock);
                                obj->up = 0;
-                               ast_mutex_unlock(&obj->lock);
+                               /*
+                                * While this isn't the best way to try to correct an error, this won't automatically
+                                * fail when the statement handle invalidates.
+                                */
+                               /* XXX Actually, it might, if we're using a non-pooled connection. Possible race here. XXX */
                                odbc_obj_disconnect(obj);
                                odbc_obj_connect(obj);
                                continue;
@@ -156,7 +123,7 @@ SQLHSTMT odbc_prepare_and_execute(odbc_obj *obj, SQLHSTMT (*prepare_cb)(odbc_obj
        return stmt;
 }
 
-int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt) 
+int odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) 
 {
        int res = 0, i;
        SQLINTEGER nativeerror=0, numfields=0;
@@ -176,7 +143,14 @@ int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt)
                                }
                        }
                }
-/*
+#if 0
+               /* This is a really bad method of trying to correct a dead connection.  It
+                * only ever really worked with MySQL.  It will not work with any other
+                * database, since most databases prepare their statements on the server,
+                * and if you disconnect, you invalidate the statement handle.  Hence, if
+                * you disconnect, you're going to fail anyway, whether you try to execute
+                * a second time or not.
+                */
                ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
                ast_mutex_lock(&obj->lock);
                obj->up = 0;
@@ -184,58 +158,38 @@ int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt)
                odbc_obj_disconnect(obj);
                odbc_obj_connect(obj);
                res = SQLExecute(stmt);
-*/
+#endif
        }
        
        return res;
 }
 
 
-int odbc_smart_direct_execute(odbc_obj *obj, SQLHSTMT stmt, char *sql) 
-{
-       int res = 0;
-
-       res = SQLExecDirect (stmt, (unsigned char *)sql, SQL_NTS);
-       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-               ast_log(LOG_WARNING, "SQL Execute error! Attempting a reconnect...\n");
-               ast_mutex_lock(&obj->lock);
-               obj->up = 0;
-               ast_mutex_unlock(&obj->lock);
-               odbc_obj_disconnect(obj);
-               odbc_obj_connect(obj);
-               res = SQLExecDirect (stmt, (unsigned char *)sql, SQL_NTS);
-       }
-       
-       return res;
-}
-
-int odbc_sanity_check(odbc_obj *obj) 
+int odbc_sanity_check(struct odbc_obj *obj) 
 {
        char *test_sql = "select 1";
        SQLHSTMT stmt;
        int res = 0;
 
-       ast_mutex_lock(&obj->lock);
-       if(obj->up) { /* so you say... let's make sure */
-               res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
+       if (obj->up) {
+               res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
                if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-                       obj->up = 0; /* Liar!*/
+                       obj->up = 0;
                } else {
                        res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS);
                        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-                               obj->up = 0; /* Liar!*/
+                               obj->up = 0;
                        } else {
                                res = SQLExecute(stmt);
                                if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-                                       obj->up = 0; /* Liar!*/
+                                       obj->up = 0;
                                }
                        }
                }
                SQLFreeHandle (SQL_HANDLE_STMT, stmt);
        }
-       ast_mutex_unlock(&obj->lock);
 
-       if(!obj->up) { /* Try to reconnect! */
+       if (!obj->up) { /* Try to reconnect! */
                ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
                odbc_obj_disconnect(obj);
                odbc_obj_connect(obj);
@@ -249,325 +203,305 @@ static int load_odbc_config(void)
        struct ast_config *config;
        struct ast_variable *v;
        char *cat, *dsn, *username, *password;
-       int enabled;
-       int connect = 0;
-       char *env_var;
+       int enabled, pooling, limit;
+       int connect = 0, res = 0;
 
-       odbc_obj *obj;
+       struct odbc_class *new;
 
        config = ast_config_load(cfg);
        if (config) {
                for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
-                       if (!strcmp(cat, "ENV")) {
+                       if (!strcasecmp(cat, "ENV")) {
+                               for (v = ast_variable_browse(config, cat); v; v = v->next) {
+                                       setenv(v->name, v->value, 1);
+                                       ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
+                               }
+                       } else {
+                               /* Reset all to defaults for each class of odbc connections */
+                               dsn = username = password = NULL;
+                               enabled = 1;
+                               connect = 0;
+                               pooling = 0;
+                               limit = 0;
                                for (v = ast_variable_browse(config, cat); v; v = v->next) {
-                                       env_var = malloc(strlen(v->name) + strlen(v->value) + 2);
-                                       if (env_var) {
-                                               sprintf(env_var, "%s=%s", v->name, v->value);
-                                               ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
-                                               putenv(env_var);
-                                               free(env_var);
+                                       if (!strcasecmp(v->name, "pooling")) {
+                                               pooling = 1;
+                                       } else if (!strcasecmp(v->name, "limit")) {
+                                               sscanf(v->value, "%d", &limit);
+                                               if (ast_true(v->value) && !limit) {
+                                                       ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
+                                                       limit = 1023;
+                                               } else if (ast_false(v->value)) {
+                                                       ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Disabling ODBC class '%s'.\n", v->value, cat);
+                                                       enabled = 0;
+                                                       break;
+                                               }
+                                       } else if (!strcasecmp(v->name, "enabled")) {
+                                               enabled = ast_true(v->value);
+                                       } else if (!strcasecmp(v->name, "pre-connect")) {
+                                               connect = ast_true(v->value);
+                                       } else if (!strcasecmp(v->name, "dsn")) {
+                                               dsn = v->value;
+                                       } else if (!strcasecmp(v->name, "username")) {
+                                               username = v->value;
+                                       } else if (!strcasecmp(v->name, "password")) {
+                                               password = v->value;
                                        }
                                }
 
-                       cat = ast_category_browse(config, cat);
-                       }
+                               if (enabled && !ast_strlen_zero(dsn)) {
+                                       new = ast_calloc(1, sizeof(*new));
 
-                       dsn = username = password = NULL;
-                       enabled = 1;
-                       connect = 0;
-                       for (v = ast_variable_browse(config, cat); v; v = v->next) {
-                               if (!strcmp(v->name, "enabled"))
-                                       enabled = ast_true(v->value);
-                               if (!strcmp(v->name, "pre-connect"))
-                                       connect = ast_true(v->value);
-                               if (!strcmp(v->name, "dsn"))
-                                       dsn = v->value;
-                               if (!strcmp(v->name, "username"))
-                                       username = v->value;
-                               if (!strcmp(v->name, "password"))
-                                       password = v->value;
-                       }
+                                       if (!new) {
+                                               ast_log(LOG_ERROR, "Memory error while loading configuration.\n");
+                                               res = -1;
+                                               break;
+                                       }
 
-                       if (enabled && dsn) {
-                               obj = new_odbc_obj(cat, dsn, username, password);
-                               if (obj) {
-                                       register_odbc_obj(cat, obj);
-                                       ast_log(LOG_NOTICE, "registered database handle '%s' dsn->[%s]\n", cat, obj->dsn);
-                                       if (connect) {
-                                               odbc_obj_connect(obj);
+                                       if (cat)
+                                               ast_copy_string(new->name, cat, sizeof(new->name));
+                                       if (dsn)
+                                               ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
+                                       if (username)
+                                               ast_copy_string(new->username, username, sizeof(new->username));
+                                       if (password)
+                                               ast_copy_string(new->password, password, sizeof(new->password));
+
+                                       SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
+                                       res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
+
+                                       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+                                               ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
+                                               SQLFreeHandle(SQL_HANDLE_ENV, new->env);
+                                               return res;
+                                       }
+
+                                       if (pooling) {
+                                               new->haspool = pooling;
+                                               if (limit) {
+                                                       new->limit = limit;
+                                               } else {
+                                                       ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless.  Changing limit from 0 to 5.\n");
+                                                       new->limit = 5;
+                                               }
                                        }
-                               } else {
-                                       ast_log(LOG_WARNING, "Addition of obj %s failed.\n", cat);
-                               }
 
+                                       odbc_register_class(new, connect);
+                                       ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
+                               }
                        }
                }
                ast_config_destroy(config);
        }
-       return 0;
-}
-
-int odbc_dump_fd(int fd, odbc_obj *obj)
-{
-       /* make sure the connection is up before we lie to our master.*/
-       odbc_sanity_check(obj);
-       ast_cli(fd, "Name: %s\nDSN: %s\nConnected: %s\n\n", obj->name, obj->dsn, obj->up ? "yes" : "no");
-       return 0;
-}
-
-static int odbc_connect_usage(int fd)
-{
-       ast_cli(fd, "usage odbc connect <DSN>\n");
-       return 0;
-}
-
-static int odbc_disconnect_usage(int fd)
-{
-       ast_cli(fd, "usage odbc disconnect <DSN>\n");
-       return 0;
+       return res;
 }
 
 static int odbc_show_command(int fd, int argc, char **argv)
 {
-       odbc_obj *obj;
-       int x = 0;
-       
-       if (!strcmp(argv[1], "show")) {
-               if (!argv[2] || (argv[2] && !strcmp(argv[2], "all"))) {
-                       for (x = 0; x < MAX_ODBC_HANDLES; x++) {
-                               if (!ODBC_REGISTRY[x].used)
-                                       break;
-                               if (ODBC_REGISTRY[x].obj)
-                                       odbc_dump_fd(fd, ODBC_REGISTRY[x].obj);
-                       }
-               } else {
-                       obj = odbc_read(ODBC_REGISTRY, argv[2]);
-                       if (obj)
-                               odbc_dump_fd(fd, obj);
-               }
-       }
-       return 0;
-}
+       struct odbc_class *class;
+       struct odbc_obj *current;
 
-static int odbc_disconnect_command(int fd, int argc, char **argv)
-{
-       odbc_obj *obj;
-       if (!strcmp(argv[1], "disconnect")) {
-               if (!argv[2])
-                       return odbc_disconnect_usage(fd);
-
-               obj = odbc_read(ODBC_REGISTRY, argv[2]);
-               if (obj) {
-                       odbc_obj_disconnect(obj);
-               }
-       } 
-       return 0;
-}
+       if (!strcmp(argv[1], "show")) {
+               AST_LIST_LOCK(&odbc_list);
+               AST_LIST_TRAVERSE(&odbc_list, class, list) {
+                       if ((argc == 2) || (argc == 3 && !strcmp(argv[2], "all")) || (!strcmp(argv[2], class->name))) {
+                               int count = 0;
+                               ast_cli(fd, "Name: %s\nDSN: %s\n", class->name, class->dsn);
 
-static int odbc_connect_command(int fd, int argc, char **argv)
-{
-       odbc_obj *obj;
-       if (!argv[1])
-               return odbc_connect_usage(fd);
+                               if (class->haspool) {
+                                       ast_cli(fd, "Pooled: yes\nLimit: %d\nConnections in use: %d\n", class->limit, class->count);
 
-       if (!strcmp(argv[1], "connect") || !strcmp(argv[1], "disconnect")) {
-               if (!argv[2])
-                       return odbc_connect_usage(fd);
+                                       AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
+                                               ast_cli(fd, "  Connection %d: %s", ++count, current->up && odbc_sanity_check(current) ? "connected" : "disconnected");
+                                       }
+                               } else {
+                                       /* Should only ever be one of these */
+                                       AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
+                                               ast_cli(fd, "Pooled: no\nConnected: %s\n", current->up && odbc_sanity_check(current) ? "yes" : "no");
+                                       }
+                               }
 
-               obj = odbc_read(ODBC_REGISTRY, argv[2]);
-               if (obj) {
-                       odbc_obj_connect(obj);
+                               ast_cli(fd, "\n");
+                       }
                }
+               AST_LIST_UNLOCK(&odbc_list);
        }
        return 0;
 }
 
-
-static char connect_usage[] =
-"Usage: odbc connect <DSN>\n"
-"       Connect to ODBC DSN\n";
-
-static char disconnect_usage[] =
-"Usage: odbc connect <DSN>\n"
-"       Disconnect from ODBC DSN\n";
-
 static char show_usage[] =
-"Usage: odbc show {DSN}\n"
-"       Show ODBC {DSN}\n"
-"       Specifying DSN will show that DSN else, all DSNs are shown\n";
-
-static struct ast_cli_entry odbc_connect_struct =
-        { { "odbc", "connect", NULL }, odbc_connect_command, "Connect to ODBC DSN", connect_usage };
-
-
-static struct ast_cli_entry odbc_disconnect_struct =
-        { { "odbc", "disconnect", NULL }, odbc_disconnect_command, "Disconnect from ODBC DSN", disconnect_usage };
+"Usage: odbc show [<class>]\n"
+"       List settings of a particular ODBC class.\n"
+"       or, if not specified, all classes.\n";
 
 static struct ast_cli_entry odbc_show_struct =
         { { "odbc", "show", NULL }, odbc_show_command, "Show ODBC DSN(s)", show_usage };
 
-/* api calls */
-
-int register_odbc_obj(char *name, odbc_obj *obj)
+static int odbc_register_class(struct odbc_class *class, int connect)
 {
-       if (obj != NULL)
-               return odbc_write(ODBC_REGISTRY, name, obj);
-       return 0;
-}
+       struct odbc_obj *obj;
+       if (class) {
+               AST_LIST_LOCK(&odbc_list);
+               AST_LIST_INSERT_HEAD(&odbc_list, class, list);
+               AST_LIST_UNLOCK(&odbc_list);
+
+               if (connect) {
+                       /* Request and release builds a connection */
+                       obj = odbc_request_obj(class->name, 0);
+                       odbc_release_obj(obj);
+               }
 
-odbc_obj *fetch_odbc_obj(const char *name, int check)
-{
-       odbc_obj *obj = NULL;
-       if((obj = (odbc_obj *) odbc_read(ODBC_REGISTRY, name))) {
-               if(check)
-                       odbc_sanity_check(obj);
+               return 0;
+       } else {
+               ast_log(LOG_WARNING, "Attempted to register a NULL class?\n");
+               return -1;
        }
-       return obj;
 }
 
-odbc_obj *new_odbc_obj(char *name, char *dsn, char *username, char *password)
+void odbc_release_obj(struct odbc_obj *obj)
 {
-       static odbc_obj *new;
+       /* For pooled connections, this frees the connection to be
+        * reused.  For non-pooled connections, it does nothing. */
+       obj->used = 0;
+}
 
-       if (!(new = calloc(1, sizeof(*new))) || 
-           !(new->name = malloc(strlen(name) + 1)) || 
-           !(new->dsn = malloc(strlen(dsn) + 1)))
-               goto cleanup;
+struct odbc_obj *odbc_request_obj(const char *name, int check)
+{
+       struct odbc_obj *obj = NULL;
+       struct odbc_class *class;
 
-       if (username) {
-               if (!(new->username = malloc(strlen(username) + 1)))
-                       goto cleanup;
-               strcpy(new->username, username);
+       AST_LIST_LOCK(&odbc_list);
+       AST_LIST_TRAVERSE(&odbc_list, class, list) {
+               if (!strcmp(class->name, name))
+                       break;
        }
+       AST_LIST_UNLOCK(&odbc_list);
+
+       if (!class)
+               return NULL;
+
+       AST_LIST_LOCK(&class->odbc_obj);
+       if (class->haspool) {
+               /* Recycle connections before building another */
+               AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
+                       if (! obj->used) {
+                               obj->used = 1;
+                               break;
+                       }
+               }
 
-       if (password) {
-               if (!(new->password = malloc(strlen(password) + 1)))
-                       goto cleanup;
-               strcpy(new->password, password);
-       }
+               if (!obj && (class->count < class->limit)) {
+                       class->count++;
+                       obj = ast_calloc(1, sizeof(*obj));
+                       if (!obj) {
+                               ast_log(LOG_ERROR, "Out of memory\n");
+                               AST_LIST_UNLOCK(&class->odbc_obj);
+                               return NULL;
+                       }
+                       ast_mutex_init(&obj->lock);
+                       obj->parent = class;
+                       odbc_obj_connect(obj);
+                       AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list);
+               }
+       } else {
+               /* Non-pooled connection: multiple modules can use the same connection. */
+               AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
+                       /* Non-pooled connection: if there is an entry, return it */
+                       break;
+               }
 
-       strcpy(new->name, name);
-       strcpy(new->dsn, dsn);
-       new->env = SQL_NULL_HANDLE;
-       new->up = 0;
-       ast_mutex_init(&new->lock);
-       return new;
-
-cleanup:
-       if (new) {
-               free(new->name);
-               free(new->dsn);
-               free(new->username);
-               free(new->password);
-
-               free(new);      
+               if (!obj) {
+                       /* No entry: build one */
+                       obj = ast_calloc(1, sizeof(*obj));
+                       if (!obj) {
+                               ast_log(LOG_ERROR, "Out of memory\n");
+                               AST_LIST_UNLOCK(&class->odbc_obj);
+                               return NULL;
+                       }
+                       ast_mutex_init(&obj->lock);
+                       obj->parent = class;
+                       if (odbc_obj_connect(obj) == ODBC_FAIL) {
+                               ast_log(LOG_WARNING, "Failed to connect\n");
+                               ast_mutex_destroy(&obj->lock);
+                               free(obj);
+                       } else {
+                               AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list);
+                       }
+               }
        }
+       AST_LIST_UNLOCK(&class->odbc_obj);
 
-       return NULL;
-}
-
-void destroy_odbc_obj(odbc_obj **obj)
-{
-       odbc_obj_disconnect(*obj);
-
-       ast_mutex_lock(&(*obj)->lock);
-       SQLFreeHandle(SQL_HANDLE_STMT, (*obj)->stmt);
-       SQLFreeHandle(SQL_HANDLE_DBC, (*obj)->con);
-       SQLFreeHandle(SQL_HANDLE_ENV, (*obj)->env);
-
-       free((*obj)->name);
-       free((*obj)->dsn);
-       if ((*obj)->username)
-               free((*obj)->username);
-       if ((*obj)->password)
-               free((*obj)->password);
-       ast_mutex_unlock(&(*obj)->lock);
-       ast_mutex_destroy(&(*obj)->lock);
-       free(*obj);
+       if (obj && check) {
+               odbc_sanity_check(obj);
+       }
+       return obj;
 }
 
-odbc_status odbc_obj_disconnect(odbc_obj *obj)
+static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
 {
        int res;
        ast_mutex_lock(&obj->lock);
 
        res = SQLDisconnect(obj->con);
 
-
        if (res == ODBC_SUCCESS) {
-               ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->name, obj->dsn);
+               ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
        } else {
                ast_log(LOG_WARNING, "res_odbc: %s [%s] already disconnected\n",
-               obj->name, obj->dsn);
+               obj->parent->name, obj->parent->dsn);
        }
        obj->up = 0;
        ast_mutex_unlock(&obj->lock);
        return ODBC_SUCCESS;
 }
 
-odbc_status odbc_obj_connect(odbc_obj *obj)
+static odbc_status odbc_obj_connect(struct odbc_obj *obj)
 {
        int res;
        SQLINTEGER err;
        short int mlen;
        unsigned char msg[200], stat[10];
-
+#ifdef NEEDTRACE
+       SQLINTEGER enable = 1;
+       char *tracefile = "/tmp/odbc.trace";
+#endif
        ast_mutex_lock(&obj->lock);
 
-       if (obj->env == SQL_NULL_HANDLE) {
-               res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &obj->env);
-
-               if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-                       if (option_verbose > 3)
-                               ast_log(LOG_WARNING, "res_odbc: Error AllocHandle\n");
-                       ast_mutex_unlock(&obj->lock);
-                       return ODBC_FAIL;
-               }
-
-               res = SQLSetEnvAttr(obj->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
-
-               if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-                       if (option_verbose > 3)
-                               ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
-                       SQLFreeHandle(SQL_HANDLE_ENV, obj->env);
-                       ast_mutex_unlock(&obj->lock);
-                       return ODBC_FAIL;
-               }
-
-               res = SQLAllocHandle(SQL_HANDLE_DBC, obj->env, &obj->con);
+       res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
 
-               if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+       if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
 
-                       if (option_verbose > 3)
-                               ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
-                       SQLFreeHandle(SQL_HANDLE_ENV, obj->env);
+               ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
+               SQLFreeHandle(SQL_HANDLE_ENV, obj->parent->env);
 
-                       ast_mutex_unlock(&obj->lock);
-                       return ODBC_FAIL;
-               }
-               SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0);
+               ast_mutex_unlock(&obj->lock);
+               return ODBC_FAIL;
        }
-       if(obj->up) {
+       SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0);
+#ifdef NEEDTRACE
+       SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
+       SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
+#endif
+
+       if (obj->up) {
                odbc_obj_disconnect(obj);
-               ast_log(LOG_NOTICE,"Re-connecting %s\n", obj->name);
+               ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
+       } else {
+               ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
        }
 
-       ast_log(LOG_NOTICE, "Connecting %s\n", obj->name);
-
        res = SQLConnect(obj->con,
-                  (SQLCHAR *) obj->dsn, SQL_NTS,
-                  (SQLCHAR *) obj->username, SQL_NTS,
-                  (SQLCHAR *) obj->password, SQL_NTS);
+                  (SQLCHAR *) obj->parent->dsn, SQL_NTS,
+                  (SQLCHAR *) obj->parent->username, SQL_NTS,
+                  (SQLCHAR *) obj->parent->password, SQL_NTS);
 
        if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen);
-               SQLFreeHandle(SQL_HANDLE_ENV, obj->env);
                ast_mutex_unlock(&obj->lock);
                ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
                return ODBC_FAIL;
        } else {
-
-               ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->name, obj->dsn);
+               ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
                obj->up = 1;
        }
 
@@ -577,23 +511,159 @@ odbc_status odbc_obj_connect(odbc_obj *obj)
 
 LOCAL_USER_DECL;
 
-static int unload_module(void *mod)
+static int reload(void *mod)
 {
-       STANDARD_HANGUP_LOCALUSERS;
-       odbc_destroy();
-       ast_cli_unregister(&odbc_disconnect_struct);
-       ast_cli_unregister(&odbc_connect_struct);
-       ast_cli_unregister(&odbc_show_struct);
-       ast_log(LOG_NOTICE, "res_odbc unloaded.\n");
+       static char *cfg = "res_odbc.conf";
+       struct ast_config *config;
+       struct ast_variable *v;
+       char *cat, *dsn, *username, *password;
+       int enabled, pooling, limit;
+       int connect = 0, res = 0;
+
+       struct odbc_class *new, *class;
+       struct odbc_obj *current;
+
+       /* First, mark all to be purged */
+       AST_LIST_LOCK(&odbc_list);
+       AST_LIST_TRAVERSE(&odbc_list, class, list) {
+               class->delme = 1;
+       }
+
+       config = ast_config_load(cfg);
+       if (config) {
+               for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
+                       if (!strcasecmp(cat, "ENV")) {
+                               for (v = ast_variable_browse(config, cat); v; v = v->next) {
+                                       setenv(v->name, v->value, 1);
+                                       ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
+                               }
+                       } else {
+                               /* Reset all to defaults for each class of odbc connections */
+                               dsn = username = password = NULL;
+                               enabled = 1;
+                               connect = 0;
+                               pooling = 0;
+                               limit = 0;
+                               for (v = ast_variable_browse(config, cat); v; v = v->next) {
+                                       if (!strcasecmp(v->name, "pooling")) {
+                                               pooling = 1;
+                                       } else if (!strcasecmp(v->name, "limit")) {
+                                               sscanf(v->value, "%d", &limit);
+                                               if (ast_true(v->value) && !limit) {
+                                                       ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
+                                                       limit = 1023;
+                                               } else if (ast_false(v->value)) {
+                                                       ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Disabling ODBC class '%s'.\n", v->value, cat);
+                                                       enabled = 0;
+                                                       break;
+                                               }
+                                       } else if (!strcasecmp(v->name, "enabled")) {
+                                               enabled = ast_true(v->value);
+                                       } else if (!strcasecmp(v->name, "pre-connect")) {
+                                               connect = ast_true(v->value);
+                                       } else if (!strcasecmp(v->name, "dsn")) {
+                                               dsn = v->value;
+                                       } else if (!strcasecmp(v->name, "username")) {
+                                               username = v->value;
+                                       } else if (!strcasecmp(v->name, "password")) {
+                                               password = v->value;
+                                       }
+                               }
+
+                               if (enabled && !ast_strlen_zero(dsn)) {
+                                       /* First, check the list to see if it already exists */
+                                       AST_LIST_TRAVERSE(&odbc_list, class, list) {
+                                               if (!strcmp(class->name, cat)) {
+                                                       class->delme = 0;
+                                                       break;
+                                               }
+                                       }
+
+                                       if (class) {
+                                               new = class;
+                                       } else {
+                                               new = ast_calloc(1, sizeof(*new));
+                                       }
+
+                                       if (!new) {
+                                               ast_log(LOG_ERROR, "Memory error while loading configuration.\n");
+                                               res = -1;
+                                               break;
+                                       }
+
+                                       if (cat)
+                                               ast_copy_string(new->name, cat, sizeof(new->name));
+                                       if (dsn)
+                                               ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
+                                       if (username)
+                                               ast_copy_string(new->username, username, sizeof(new->username));
+                                       if (password)
+                                               ast_copy_string(new->password, password, sizeof(new->password));
+
+                                       if (!class) {
+                                               SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
+                                               res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
+
+                                               if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+                                                       ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
+                                                       SQLFreeHandle(SQL_HANDLE_ENV, new->env);
+                                                       AST_LIST_UNLOCK(&odbc_list);
+                                                       return res;
+                                               }
+                                       }
+
+                                       if (pooling) {
+                                               new->haspool = pooling;
+                                               if (limit) {
+                                                       new->limit = limit;
+                                               } else {
+                                                       ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless.  Changing limit from 0 to 5.\n");
+                                                       new->limit = 5;
+                                               }
+                                       }
+
+                                       if (class) {
+                                               ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn);
+                                       } else {
+                                               odbc_register_class(new, connect);
+                                               ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
+                                       }
+                               }
+                       }
+               }
+               ast_config_destroy(config);
+       }
+
+       /* Purge classes that we know can go away (pooled with 0, only) */
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) {
+               if (class->delme && class->haspool && class->count == 0) {
+                       AST_LIST_TRAVERSE_SAFE_BEGIN(&(class->odbc_obj), current, list) {
+                               AST_LIST_REMOVE_CURRENT(&(class->odbc_obj), list);
+                               odbc_obj_disconnect(current);
+                               ast_mutex_destroy(&current->lock);
+                               free(current);
+                       }
+                       AST_LIST_TRAVERSE_SAFE_END;
+
+                       AST_LIST_REMOVE_CURRENT(&odbc_list, list);
+                       free(class);
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+       AST_LIST_UNLOCK(&odbc_list);
+
        return 0;
 }
 
+static int unload_module(void *mod)
+{
+       /* Prohibit unloading */
+       return -1;
+}
+
 static int load_module(void *mod)
 {
-       odbc_init();
        load_odbc_config();
-       ast_cli_register(&odbc_disconnect_struct);
-       ast_cli_register(&odbc_connect_struct);
        ast_cli_register(&odbc_show_struct);
        ast_log(LOG_NOTICE, "res_odbc loaded.\n");
        return 0;
@@ -609,4 +679,4 @@ static const char *key(void)
        return ASTERISK_GPL_KEY;
 }
 
-STD_MOD(MOD_0, NULL, NULL, NULL);
+STD_MOD(MOD_0, reload, NULL, NULL);