summaryrefslogtreecommitdiff
path: root/res
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2015-12-23 15:07:05 -0600
committerMark Michelson <mmichelson@digium.com>2016-01-22 11:59:06 -0600
commit9714da7aa4021ba495d5f4ad02fb4128e54689ab (patch)
tree586842cbcba3826f44b8f81986450584d42921c2 /res
parent60a15fe4365d1bd47c19836fc9addde15c1f0167 (diff)
res_odbc: Remove connection management
Asterisk by default will create a single database connection and share it among all threads that attempt to access the database. In previous versions of Asterisk, this was tolerable, because the most used channel driver, chan_sip, mostly accessed the database from a single thread. With PJSIP, however, many threads may be attempting to perform database operations, and there is the potential for many more database accesses, meaning the concurrency is a horrible bottleneck if only one connection is shared. Asterisk has a connection pooling facility built into it, but the implementation has flaws. For one, there is a strict limit on the number of simultaneous connections that could be made to the database. Anything beyond the maximum would result in a failed operation. Attempting to predict what the maximum should be is nearly impossible even for someone intimately familiar with Asterisk's threading model. In addition, use of transactions in the dialplan can cause some severe bugs if connection pooling is enabled. This commit seeks to fix the concurrency problem by removing all connection management code from Asterisk and leaving that to the underlying unixODBC code instead. Now, Asterisk does not share a single connection, nor does it try to maintain a connection pool. Instead, all Asterisk ever does is request a connection from unixODBC and allow unixODBC to either allocate those connections or retrieve them from a pool. Doing this has a bit of a ripple effect. For one, since connections are not long-lived objects, several of the safeguards that previously existed have been removed. We don't have to worry about trying to use a connection that has gone stale. In every case, when we request a connection, it has just been made and we don't need to perform any sanity checks to be sure it's still active. Another major player affected by this change is transactions. Transactions and their respective connections were so tightly coupled that it was almost pornographic. This code change moves transaction-related code to its own file separate from the core ODBC functionality. This way, the core of ODBC does not even have to know that transactions exist. In making this large change, I had to look at a lot of code and understand it. When making this change, I discovered several places where the behavior is definitely not ideal, but it seemed outside the scope of this change to be fixing it. Instead, any place where I saw some sort of room for improvement has had a XXX comment added explaining what could be altered to improve it. Change-Id: I37a84def5ea4ddf93868ce8105f39de078297fbf
Diffstat (limited to 'res')
-rw-r--r--res/res_odbc.c1179
-rw-r--r--res/res_odbc.exports.in6
-rw-r--r--res/res_odbc_transaction.c529
-rw-r--r--res/res_odbc_transaction.exports.in6
4 files changed, 652 insertions, 1068 deletions
diff --git a/res/res_odbc.c b/res/res_odbc.c
index 06009a2a5..609d68a4c 100644
--- a/res/res_odbc.c
+++ b/res/res_odbc.c
@@ -64,66 +64,6 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/threadstorage.h"
#include "asterisk/data.h"
-/*** DOCUMENTATION
- <function name="ODBC" language="en_US">
- <synopsis>
- Controls ODBC transaction properties.
- </synopsis>
- <syntax>
- <parameter name="property" required="true">
- <enumlist>
- <enum name="transaction">
- <para>Gets or sets the active transaction ID. If set, and the transaction ID does not
- exist and a <replaceable>database name</replaceable> is specified as an argument, it will be created.</para>
- </enum>
- <enum name="forcecommit">
- <para>Controls whether a transaction will be automatically committed when the channel
- hangs up. Defaults to false. If a <replaceable>transaction ID</replaceable> is specified in the optional argument,
- the property will be applied to that ID, otherwise to the current active ID.</para>
- </enum>
- <enum name="isolation">
- <para>Controls the data isolation on uncommitted transactions. May be one of the
- following: <literal>read_committed</literal>, <literal>read_uncommitted</literal>,
- <literal>repeatable_read</literal>, or <literal>serializable</literal>. Defaults to the
- database setting in <filename>res_odbc.conf</filename> or <literal>read_committed</literal>
- if not specified. If a <replaceable>transaction ID</replaceable> is specified as an optional argument, it will be
- applied to that ID, otherwise the current active ID.</para>
- </enum>
- </enumlist>
- </parameter>
- <parameter name="argument" required="false" />
- </syntax>
- <description>
- <para>The ODBC() function allows setting several properties to influence how a connected
- database processes transactions.</para>
- </description>
- </function>
- <application name="ODBC_Commit" language="en_US">
- <synopsis>
- Commits a currently open database transaction.
- </synopsis>
- <syntax>
- <parameter name="transaction ID" required="no" />
- </syntax>
- <description>
- <para>Commits the database transaction specified by <replaceable>transaction ID</replaceable>
- or the current active transaction, if not specified.</para>
- </description>
- </application>
- <application name="ODBC_Rollback" language="en_US">
- <synopsis>
- Rollback a currently open database transaction.
- </synopsis>
- <syntax>
- <parameter name="transaction ID" required="no" />
- </syntax>
- <description>
- <para>Rolls back the database transaction specified by <replaceable>transaction ID</replaceable>
- or the current active transaction, if not specified.</para>
- </description>
- </application>
- ***/
-
struct odbc_class
{
AST_LIST_ENTRY(odbc_class) list;
@@ -133,21 +73,15 @@ struct odbc_class
char *password;
char *sanitysql;
SQLHENV env;
- unsigned int haspool:1; /*!< Boolean - TDS databases need this */
unsigned int delme:1; /*!< Purge the class */
unsigned int backslash_is_escape:1; /*!< On this database, the backslash is a native escape sequence */
unsigned int forcecommit:1; /*!< Should uncommitted transactions be auto-committed on handle release? */
unsigned int isolation; /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
- unsigned int limit; /*!< Maximum number of database handles we will allow */
- int count; /*!< Running count of pooled connections */
- unsigned int idlecheck; /*!< Recheck the connection if it is idle for this long (in seconds) */
unsigned int conntimeout; /*!< Maximum time the connection process should take */
/*! When a connection fails, cache that failure for how long? */
struct timeval negative_connection_cache;
/*! When a connection fails, when did that last occur? */
struct timeval last_negative_connect;
- /*! List of handles associated with this class */
- struct ao2_container *obj_container;
};
static struct ao2_container *class_container;
@@ -157,16 +91,9 @@ static AST_RWLIST_HEAD_STATIC(odbc_tables, odbc_cache_tables);
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_txn_free(void *data);
-static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx);
AST_THREADSTORAGE(errors_buf);
-static const struct ast_datastore_info txn_info = {
- .type = "ODBC_Transaction",
- .destroy = odbc_txn_free,
-};
-
struct odbc_txn_frame {
AST_LIST_ENTRY(odbc_txn_frame) list;
struct ast_channel *owner;
@@ -189,13 +116,11 @@ struct odbc_txn_frame {
MEMBER(odbc_class, dsn, AST_DATA_STRING) \
MEMBER(odbc_class, username, AST_DATA_STRING) \
MEMBER(odbc_class, password, AST_DATA_PASSWORD) \
- MEMBER(odbc_class, limit, AST_DATA_INTEGER) \
- MEMBER(odbc_class, count, AST_DATA_INTEGER) \
MEMBER(odbc_class, forcecommit, AST_DATA_BOOLEAN)
AST_DATA_STRUCTURE(odbc_class, DATA_EXPORT_ODBC_CLASS);
-static const char *isolation2text(int iso)
+const char *ast_odbc_isolation2text(int iso)
{
if (iso == SQL_TXN_READ_COMMITTED) {
return "read_committed";
@@ -210,7 +135,7 @@ static const char *isolation2text(int iso)
}
}
-static int text2isolation(const char *txt)
+int ast_odbc_text2isolation(const char *txt)
{
if (strncasecmp(txt, "read_", 5) == 0) {
if (strncasecmp(txt + 5, "c", 1) == 0) {
@@ -229,176 +154,6 @@ static int text2isolation(const char *txt)
}
}
-static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, struct odbc_obj *obj, const char *name, int active)
-{
- struct ast_datastore *txn_store;
- AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
- struct odbc_txn_frame *txn = NULL;
-
- if (!chan && obj && obj->txf && obj->txf->owner) {
- chan = obj->txf->owner;
- } else if (!chan) {
- /* No channel == no transaction */
- return NULL;
- }
-
- ast_channel_lock(chan);
- if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
- oldlist = txn_store->data;
- } else {
- /* Need to create a new datastore */
- if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
- ast_log(LOG_ERROR, "Unable to allocate a new datastore. Cannot create a new transaction.\n");
- ast_channel_unlock(chan);
- return NULL;
- }
-
- if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
- ast_log(LOG_ERROR, "Unable to allocate datastore list head. Cannot create a new transaction.\n");
- ast_datastore_free(txn_store);
- ast_channel_unlock(chan);
- return NULL;
- }
-
- txn_store->data = oldlist;
- AST_LIST_HEAD_INIT(oldlist);
- ast_channel_datastore_add(chan, txn_store);
- }
-
- AST_LIST_LOCK(oldlist);
- ast_channel_unlock(chan);
-
- /* Scanning for an object is *fast*. Scanning for a name is much slower. */
- if (obj != NULL || active == 1) {
- AST_LIST_TRAVERSE(oldlist, txn, list) {
- if (txn->obj == obj || txn->active) {
- AST_LIST_UNLOCK(oldlist);
- return txn;
- }
- }
- }
-
- if (name != NULL) {
- AST_LIST_TRAVERSE(oldlist, txn, list) {
- if (!strcasecmp(txn->name, name)) {
- AST_LIST_UNLOCK(oldlist);
- return txn;
- }
- }
- }
-
- /* Nothing found, create one */
- if (name && obj && (txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1))) {
- struct odbc_txn_frame *otxn;
-
- strcpy(txn->name, name); /* SAFE */
- txn->obj = obj;
- txn->isolation = obj->parent->isolation;
- txn->forcecommit = obj->parent->forcecommit;
- txn->owner = chan;
- txn->active = 1;
-
- /* On creation, the txn becomes active, and all others inactive */
- AST_LIST_TRAVERSE(oldlist, otxn, list) {
- otxn->active = 0;
- }
- AST_LIST_INSERT_TAIL(oldlist, txn, list);
-
- obj->txf = txn;
- obj->tx = 1;
- }
- AST_LIST_UNLOCK(oldlist);
-
- return txn;
-}
-
-static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
-{
- if (!tx) {
- return NULL;
- }
-
- ast_debug(2, "release_transaction(%p) called (tx->obj = %p, tx->obj->txf = %p)\n", tx, tx->obj, tx->obj ? tx->obj->txf : NULL);
-
- /* If we have an owner, disassociate */
- if (tx->owner) {
- struct ast_datastore *txn_store;
- AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
-
- ast_channel_lock(tx->owner);
- if ((txn_store = ast_channel_datastore_find(tx->owner, &txn_info, NULL))) {
- oldlist = txn_store->data;
- AST_LIST_LOCK(oldlist);
- AST_LIST_REMOVE(oldlist, tx, list);
- AST_LIST_UNLOCK(oldlist);
- }
- ast_channel_unlock(tx->owner);
- tx->owner = NULL;
- }
-
- if (tx->obj) {
- /* If we have any uncommitted transactions, they are handled when we release the object */
- struct odbc_obj *obj = tx->obj;
- /* Prevent recursion during destruction */
- tx->obj->txf = NULL;
- tx->obj = NULL;
- odbc_release_obj2(obj, tx);
- }
- ast_free(tx);
- return NULL;
-}
-
-static void odbc_txn_free(void *vdata)
-{
- struct odbc_txn_frame *tx;
- AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
-
- ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
-
- AST_LIST_LOCK(oldlist);
- while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
- release_transaction(tx);
- }
- AST_LIST_UNLOCK(oldlist);
- AST_LIST_HEAD_DESTROY(oldlist);
- ast_free(oldlist);
-}
-
-static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
-{
- struct ast_datastore *txn_store;
- AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
- struct odbc_txn_frame *active = NULL, *txn;
-
- if (!chan && tx && tx->owner) {
- chan = tx->owner;
- }
-
- if (!chan) {
- return -1;
- }
-
- ast_channel_lock(chan);
- if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
- ast_channel_unlock(chan);
- return -1;
- }
-
- oldlist = txn_store->data;
- AST_LIST_LOCK(oldlist);
- AST_LIST_TRAVERSE(oldlist, txn, list) {
- if (txn == tx) {
- txn->active = 1;
- active = txn;
- } else {
- txn->active = 0;
- }
- }
- AST_LIST_UNLOCK(oldlist);
- ast_channel_unlock(chan);
- return active ? 0 : -1;
-}
-
static void odbc_class_destructor(void *data)
{
struct odbc_class *class = data;
@@ -414,7 +169,6 @@ static void odbc_class_destructor(void *data)
if (class->sanitysql) {
ast_free(class->sanitysql);
}
- ao2_ref(class->obj_container, -1);
SQLFreeHandle(SQL_HANDLE_ENV, class->env);
}
@@ -451,6 +205,21 @@ static void destroy_table_cache(struct odbc_cache_tables *table) {
* \retval A structure describing the table layout, or NULL, if the table is not found or another error occurs.
* When a structure is returned, the contained columns list will be
* rdlock'ed, to ensure that it will be retained in memory.
+ *
+ * XXX This creates a connection and disconnects it. In some situations, the caller of
+ * this function has its own connection and could donate it to this function instead of
+ * needing to create another one.
+ *
+ * XXX The automatic readlock of the columns is awkward. It's done because it's possible for
+ * multiple threads to have references to the table, and the table is not refcounted. Possible
+ * changes here would be
+ * * Eliminate the table cache entirely. The use of ast_odbc_find_table() is generally
+ * questionable. The only real good use right now is from ast_realtime_require_field() in
+ * order to make sure the DB has the expected columns in it. Since that is only used sparingly,
+ * the need to cache tables is questionable. Instead, the table structure can be fetched from
+ * the DB directly each time, resulting in a single owner of the data.
+ * * Make odbc_cache_tables a refcounted object.
+ *
* \since 1.6.1
*/
struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *tablename)
@@ -460,7 +229,7 @@ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *
char columnname[80];
SQLLEN sqlptr;
SQLHSTMT stmt = NULL;
- int res = 0, error = 0, try = 0;
+ int res = 0, error = 0;
struct odbc_obj *obj;
AST_RWLIST_RDLOCK(&odbc_tables);
@@ -482,27 +251,16 @@ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *
}
/* Table structure not already cached; build it now. */
- ao2_lock(obj);
do {
res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- if (try == 0) {
- try = 1;
- ast_odbc_sanity_check(obj);
- continue;
- }
ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", database);
break;
}
res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)tablename, SQL_NTS, (unsigned char *)"%", SQL_NTS);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- if (try == 0) {
- try = 1;
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_sanity_check(obj);
- continue;
- }
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'.\n", database);
break;
}
@@ -553,7 +311,6 @@ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *
AST_RWLIST_RDLOCK(&(tableptr->columns));
break;
} while (1);
- ao2_unlock(obj);
AST_RWLIST_UNLOCK(&odbc_tables);
@@ -595,125 +352,52 @@ int ast_odbc_clear_cache(const char *database, const char *tablename)
SQLHSTMT ast_odbc_direct_execute(struct odbc_obj *obj, SQLHSTMT (*exec_cb)(struct odbc_obj *obj, void *data), void *data)
{
- int attempt;
SQLHSTMT stmt;
- ao2_lock(obj);
-
- for (attempt = 0; attempt < 2; attempt++) {
- stmt = exec_cb(obj, data);
-
- if (stmt) {
- break;
- } else if (obj->tx) {
- ast_log(LOG_WARNING, "Failed to execute, but unable to reconnect, as we're transactional.\n");
- break;
- } else if (attempt == 0) {
- ast_log(LOG_WARNING, "SQL Execute error! Verifying connection to %s [%s]...\n", obj->parent->name, obj->parent->dsn);
- }
- if (!ast_odbc_sanity_check(obj)) {
- break;
- }
- }
-
- ao2_unlock(obj);
+ stmt = exec_cb(obj, data);
return stmt;
}
SQLHSTMT ast_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;
- SQLSMALLINT diagbytes=0;
- unsigned char state[10], diagnostic[256];
+ int res = 0;
SQLHSTMT stmt;
- ao2_lock(obj);
-
- for (attempt = 0; attempt < 2; attempt++) {
- /* This prepare callback may do more than just prepare -- it may also
- * bind parameters, bind results, etc. The real key, here, is that
- * when we disconnect, all handles become invalid for most databases.
- * We must therefore redo everything when we establish a new
- * connection. */
- stmt = prepare_cb(obj, data);
-
- if (stmt) {
- res = SQLExecute(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
- if (res == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
+ /* This prepare callback may do more than just prepare -- it may also
+ * bind parameters, bind results, etc. The real key, here, is that
+ * when we disconnect, all handles become invalid for most databases.
+ * We must therefore redo everything when we establish a new
+ * connection. */
+ stmt = prepare_cb(obj, data);
- if (obj->tx) {
- ast_log(LOG_WARNING, "SQL Execute error, but unable to reconnect, as we're transactional.\n");
- break;
- } else {
- ast_log(LOG_WARNING, "SQL Execute error %d! Verifying connection to %s [%s]...\n", res, obj->parent->name, obj->parent->dsn);
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- stmt = NULL;
-
- obj->up = 0;
- /*
- * While this isn't the best way to try to correct an error, this won't automatically
- * fail when the statement handle invalidates.
- */
- if (!ast_odbc_sanity_check(obj)) {
- break;
- }
- continue;
- }
- } else {
- obj->last_used = ast_tvnow();
+ if (stmt) {
+ res = SQLExecute(stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
+ if (res == SQL_ERROR) {
+ ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Execute");
}
- break;
- } else if (attempt == 0) {
- ast_odbc_sanity_check(obj);
+
+ ast_log(LOG_WARNING, "SQL Execute error %d!\n", res);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ stmt = NULL;
}
}
- ao2_unlock(obj);
-
return stmt;
}
int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt)
{
- int res = 0, i;
- SQLINTEGER nativeerror=0, numfields=0;
- SQLSMALLINT diagbytes=0;
- unsigned char state[10], diagnostic[256];
-
- ao2_lock(obj);
+ int res = 0;
res = SQLExecute(stmt);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
if (res == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
+ ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Execute");
}
- } else {
- obj->last_used = ast_tvnow();
}
- ao2_unlock(obj);
-
return res;
}
@@ -734,39 +418,47 @@ SQLRETURN ast_odbc_ast_str_SQLGetData(struct ast_str **buf, int pmaxlen, SQLHSTM
return res;
}
-int ast_odbc_sanity_check(struct odbc_obj *obj)
-{
- char *test_sql = "select 1";
- SQLHSTMT stmt;
- int res = 0;
-
- if (!ast_strlen_zero(obj->parent->sanitysql))
- test_sql = obj->parent->sanitysql;
-
- if (obj->up) {
- res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- 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;
- } else {
- res = SQLExecute(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- obj->up = 0;
- }
- }
+struct ast_str *ast_odbc_print_errors(SQLSMALLINT handle_type, SQLHANDLE handle, const char *operation)
+{
+ struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
+ SQLINTEGER nativeerror = 0;
+ SQLINTEGER numfields = 0;
+ SQLSMALLINT diagbytes = 0;
+ SQLSMALLINT i;
+ unsigned char state[10];
+ unsigned char diagnostic[256];
+
+ ast_str_reset(errors);
+ SQLGetDiagField(handle_type, handle, 1, SQL_DIAG_NUMBER, &numfields,
+ SQL_IS_INTEGER, &diagbytes);
+ for (i = 0; i < numfields; i++) {
+ SQLGetDiagRec(handle_type, handle, i + 1, state, &nativeerror,
+ diagnostic, sizeof(diagnostic), &diagbytes);
+ ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
+ ast_log(LOG_WARNING, "%s returned an error: %s: %s\n", operation, state, diagnostic);
+ /* XXX Why is this here? */
+ if (i > 10) {
+ ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
+ break;
}
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
}
- if (!obj->up && !obj->tx) { /* Try to reconnect! */
- ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
- odbc_obj_disconnect(obj);
- odbc_obj_connect(obj);
- }
- return obj->up;
+ return errors;
+}
+
+unsigned int ast_odbc_class_get_isolation(struct odbc_class *class)
+{
+ return class->isolation;
+}
+
+unsigned int ast_odbc_class_get_forcecommit(struct odbc_class *class)
+{
+ return class->forcecommit;
+}
+
+const char *ast_odbc_class_get_name(struct odbc_class *class)
+{
+ return class->name;
}
static int load_odbc_config(void)
@@ -776,9 +468,8 @@ static int load_odbc_config(void)
struct ast_variable *v;
char *cat;
const char *dsn, *username, *password, *sanitysql;
- int enabled, pooling, limit, bse, conntimeout, forcecommit, isolation;
+ int enabled, bse, conntimeout, forcecommit, isolation;
struct timeval ncache = { 0, 0 };
- unsigned int idlecheck;
int preconnect = 0, res = 0;
struct ast_flags config_flags = { 0 };
@@ -799,33 +490,17 @@ static int load_odbc_config(void)
/* Reset all to defaults for each class of odbc connections */
dsn = username = password = sanitysql = NULL;
enabled = 1;
- preconnect = idlecheck = 0;
- pooling = 0;
- limit = 0;
+ preconnect = 0;
bse = 1;
conntimeout = 10;
forcecommit = 0;
isolation = SQL_TXN_READ_COMMITTED;
for (v = ast_variable_browse(config, cat); v; v = v->next) {
- if (!strcasecmp(v->name, "pooling")) {
- if (ast_true(v->value))
- pooling = 1;
- } else if (!strncasecmp(v->name, "share", 5)) {
- /* "shareconnections" is a little clearer in meaning than "pooling" */
- if (ast_false(v->value))
- pooling = 1;
- } else if (!strcasecmp(v->name, "limit")) {
- sscanf(v->value, "%30d", &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, "idlecheck")) {
- sscanf(v->value, "%30u", &idlecheck);
+ if (!strcasecmp(v->name, "pooling") ||
+ !strncasecmp(v->name, "share", 5) ||
+ !strcasecmp(v->name, "limit") ||
+ !strcasecmp(v->name, "idlecheck")) {
+ ast_log(LOG_WARNING, "The 'pooling', 'shared_connections', 'limit', and 'idlecheck' options are deprecated. Please see UPGRADE.txt for information");
} else if (!strcasecmp(v->name, "enabled")) {
enabled = ast_true(v->value);
} else if (!strcasecmp(v->name, "pre-connect")) {
@@ -859,7 +534,7 @@ static int load_odbc_config(void)
} else if (!strcasecmp(v->name, "forcecommit")) {
forcecommit = ast_true(v->value);
} else if (!strcasecmp(v->name, "isolation")) {
- if ((isolation = text2isolation(v->value)) == 0) {
+ if ((isolation = ast_odbc_text2isolation(v->value)) == 0) {
ast_log(LOG_ERROR, "Unrecognized value for 'isolation': '%s' in section '%s'\n", v->value, cat);
isolation = SQL_TXN_READ_COMMITTED;
}
@@ -883,22 +558,9 @@ static int load_odbc_config(void)
return res;
}
- new->obj_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr);
-
- 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;
- }
- }
-
new->backslash_is_escape = bse ? 1 : 0;
new->forcecommit = forcecommit ? 1 : 0;
new->isolation = isolation;
- new->idlecheck = idlecheck;
new->conntimeout = conntimeout;
new->negative_connection_cache = ncache;
@@ -934,7 +596,6 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
{
struct ao2_iterator aoi;
struct odbc_class *class;
- struct odbc_obj *current;
int length = 0;
int which = 0;
char *ret = NULL;
@@ -973,7 +634,6 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
aoi = ao2_iterator_init(class_container, 0);
while ((class = ao2_iterator_next(&aoi))) {
if ((a->argc == 2) || (a->argc == 3 && !strcmp(a->argv[2], "all")) || (!strcmp(a->argv[2], class->name))) {
- int count = 0;
char timestr[80];
struct ast_tm tm;
@@ -981,38 +641,6 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
ast_cli(a->fd, " Name: %s\n DSN: %s\n", class->name, class->dsn);
ast_cli(a->fd, " Last connection attempt: %s\n", timestr);
-
- if (class->haspool) {
- struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
-
- ast_cli(a->fd, " Pooled: Yes\n Limit: %u\n Connections in use: %d\n", class->limit, class->count);
-
- while ((current = ao2_iterator_next(&aoi2))) {
- ao2_lock(current);
-#ifdef DEBUG_THREADS
- ast_cli(a->fd, " - Connection %d: %s (%s:%d %s)\n", ++count,
- current->used ? "in use" :
- current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected",
- current->file, current->lineno, current->function);
-#else
- ast_cli(a->fd, " - Connection %d: %s\n", ++count,
- current->used ? "in use" :
- current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
-#endif
- ao2_unlock(current);
- ao2_ref(current, -1);
- }
- ao2_iterator_destroy(&aoi2);
- } else {
- /* Should only ever be one of these (unless there are transactions) */
- struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
- while ((current = ao2_iterator_next(&aoi2))) {
- ast_cli(a->fd, " Pooled: No\n Connected: %s\n", current->used ? "In use" :
- current->up && ast_odbc_sanity_check(current) ? "Yes" : "No");
- ao2_ref(current, -1);
- }
- ao2_iterator_destroy(&aoi2);
- }
ast_cli(a->fd, "\n");
}
ao2_ref(class, -1);
@@ -1048,150 +676,23 @@ static int odbc_register_class(struct odbc_class *class, int preconnect)
}
}
-static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx)
+void ast_odbc_release_obj(struct odbc_obj *obj)
{
- SQLINTEGER nativeerror=0, numfields=0;
- SQLSMALLINT diagbytes=0, i;
- unsigned char state[10], diagnostic[256];
-
- ast_debug(2, "odbc_release_obj2(%p) called (obj->txf = %p)\n", obj, obj->txf);
- if (tx) {
- ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
- if (SQLEndTran(SQL_HANDLE_DBC, obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
- /* Handle possible transaction commit failure */
- SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
- if (!strcmp((char *)state, "25S02") || !strcmp((char *)state, "08007")) {
- /* These codes mean that a commit failed and a transaction
- * is still active. We must rollback, or things will get
- * very, very weird for anybody using the handle next. */
- SQLEndTran(SQL_HANDLE_DBC, obj->con, SQL_ROLLBACK);
- }
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
-
- /* Transaction is done, reset autocommit */
- if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
- }
+ ast_debug(2, "Releasing ODBC handle %p\n", obj);
#ifdef DEBUG_THREADS
obj->file[0] = '\0';
obj->function[0] = '\0';
obj->lineno = 0;
#endif
-
- /* For pooled connections, this frees the connection to be
- * reused. For non-pooled connections, it does nothing. */
- obj->used = 0;
- if (obj->txf) {
- /* Prevent recursion -- transaction is already closed out. */
- obj->txf->obj = NULL;
- obj->txf = release_transaction(obj->txf);
- }
ao2_ref(obj, -1);
}
-void ast_odbc_release_obj(struct odbc_obj *obj)
-{
- struct odbc_txn_frame *tx = find_transaction(NULL, obj, NULL, 0);
- odbc_release_obj2(obj, tx);
-}
-
int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
{
return obj->parent->backslash_is_escape;
}
-static int commit_exec(struct ast_channel *chan, const char *data)
-{
- struct odbc_txn_frame *tx;
- SQLINTEGER nativeerror=0, numfields=0;
- SQLSMALLINT diagbytes=0, i;
- unsigned char state[10], diagnostic[256];
-
- if (ast_strlen_zero(data)) {
- tx = find_transaction(chan, NULL, NULL, 1);
- } else {
- tx = find_transaction(chan, NULL, data, 0);
- }
-
- pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
-
- if (tx) {
- if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
- struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
- ast_str_reset(errors);
-
- /* Handle possible transaction commit failure */
- SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
- ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
- }
- }
- return 0;
-}
-
-static int rollback_exec(struct ast_channel *chan, const char *data)
-{
- struct odbc_txn_frame *tx;
- SQLINTEGER nativeerror=0, numfields=0;
- SQLSMALLINT diagbytes=0, i;
- unsigned char state[10], diagnostic[256];
-
- if (ast_strlen_zero(data)) {
- tx = find_transaction(chan, NULL, NULL, 1);
- } else {
- tx = find_transaction(chan, NULL, data, 0);
- }
-
- pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
-
- if (tx) {
- if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
- struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
- ast_str_reset(errors);
-
- /* Handle possible transaction commit failure */
- SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
- ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
- }
- }
- return 0;
-}
-
static int aoro2_class_cb(void *obj, void *arg, int flags)
{
struct odbc_class *class = obj;
@@ -1202,275 +703,41 @@ static int aoro2_class_cb(void *obj, void *arg, int flags)
return 0;
}
-#define USE_TX (void *)(long)1
-#define NO_TX (void *)(long)2
-#define EOR_TX (void *)(long)3
-
-static int aoro2_obj_cb(void *vobj, void *arg, int flags)
-{
- struct odbc_obj *obj = vobj;
- ao2_lock(obj);
- if ((arg == NO_TX && !obj->tx) || (arg == EOR_TX && !obj->used) || (arg == USE_TX && obj->tx && !obj->used)) {
- obj->used = 1;
- ao2_unlock(obj);
- return CMP_MATCH | CMP_STOP;
- }
- ao2_unlock(obj);
- return 0;
-}
-
-/* This function should only be called for shared connections. Otherwise, the lack of
- * setting vobj->used breaks EOR_TX searching. For nonshared connections, use
- * aoro2_obj_cb instead. */
-static int aoro2_obj_notx_cb(void *vobj, void *arg, int flags)
-{
- struct odbc_obj *obj = vobj;
- if (!obj->tx) {
- return CMP_MATCH | CMP_STOP;
- }
- return 0;
-}
-
struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno)
{
struct odbc_obj *obj = NULL;
struct odbc_class *class;
- SQLINTEGER nativeerror=0, numfields=0;
- SQLSMALLINT diagbytes=0, i;
- unsigned char state[10], diagnostic[256];
if (!(class = ao2_callback(class_container, 0, aoro2_class_cb, (char *) name))) {
ast_debug(1, "Class '%s' not found!\n", name);
return NULL;
}
- ast_assert(ao2_ref(class, 0) > 1);
-
- if (class->haspool) {
- /* Recycle connections before building another */
- obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, EOR_TX);
-
- if (obj) {
- ast_assert(ao2_ref(obj, 0) > 1);
- }
- if (!obj && (ast_atomic_fetchadd_int(&class->count, +1) < class->limit)) {
- obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
- if (!obj) {
- class->count--;
- ao2_ref(class, -1);
- ast_debug(3, "Unable to allocate object\n");
- ast_atomic_fetchadd_int(&class->count, -1);
- return NULL;
- }
- ast_assert(ao2_ref(obj, 0) == 1);
- /* obj inherits the outstanding reference to class */
- obj->parent = class;
- class = NULL;
- if (odbc_obj_connect(obj) == ODBC_FAIL) {
- ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
- ast_assert(ao2_ref(obj->parent, 0) > 0);
- /* Because it was never within the container, we have to manually decrement the count here */
- ast_atomic_fetchadd_int(&obj->parent->count, -1);
- ao2_ref(obj, -1);
- obj = NULL;
- } else {
- obj->used = 1;
- ao2_link(obj->parent->obj_container, obj);
- }
- } else {
- /* If construction fails due to the limit (or negative timecache), reverse our increment. */
- if (!obj) {
- ast_atomic_fetchadd_int(&class->count, -1);
- }
- /* Object is not constructed, so delete outstanding reference to class. */
- ao2_ref(class, -1);
- class = NULL;
- }
-
- if (!obj) {
- return NULL;
- }
-
- ao2_lock(obj);
-
- if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
- /* Ensure this connection has autocommit turned off. */
- if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SQLSetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
- }
- } else if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
- /* Non-pooled connections -- but must use a separate connection handle */
- if (!(obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX))) {
- ast_debug(1, "Object not found\n");
- obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
- if (!obj) {
- ao2_ref(class, -1);
- ast_debug(3, "Unable to allocate object\n");
- return NULL;
- }
- /* obj inherits the outstanding reference to class */
- obj->parent = class;
- class = NULL;
- if (odbc_obj_connect(obj) == ODBC_FAIL) {
- ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
- ao2_ref(obj, -1);
- obj = NULL;
- } else {
- obj->used = 1;
- ao2_link(obj->parent->obj_container, obj);
- ast_atomic_fetchadd_int(&obj->parent->count, +1);
- }
- }
-
- if (!obj) {
- return NULL;
- }
-
- ao2_lock(obj);
-
- if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
- } else {
- /* Non-pooled connection: multiple modules can use the same connection. */
- if ((obj = ao2_callback(class->obj_container, 0, aoro2_obj_notx_cb, NO_TX))) {
- /* Object is not constructed, so delete outstanding reference to class. */
- ast_assert(ao2_ref(class, 0) > 1);
- ao2_ref(class, -1);
- class = NULL;
- } else {
- /* No entry: build one */
- if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) {
- ast_assert(ao2_ref(class, 0) > 1);
- ao2_ref(class, -1);
- ast_debug(3, "Unable to allocate object\n");
- return NULL;
- }
- /* obj inherits the outstanding reference to class */
- obj->parent = class;
- class = NULL;
- if (odbc_obj_connect(obj) == ODBC_FAIL) {
- ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
- ao2_ref(obj, -1);
- obj = NULL;
- } else {
- ao2_link(obj->parent->obj_container, obj);
- ast_assert(ao2_ref(obj, 0) > 1);
- }
- }
-
- if (!obj) {
- return NULL;
- }
-
- ao2_lock(obj);
-
- if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
- }
-
- ast_assert(obj != NULL);
-
- /* Set the isolation property */
- if (SQLSetConnectAttr(obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)obj->parent->isolation, 0) == SQL_ERROR) {
- SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- }
-
- if (ast_test_flag(&flags, RES_ODBC_CONNECTED) && !obj->up) {
- odbc_obj_connect(obj);
- } else if (ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) {
- ast_odbc_sanity_check(obj);
- } else if (obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) {
- odbc_obj_connect(obj);
+ /* XXX ODBC connection objects do not have shared ownership, so there is no reason
+ * to use refcounted objects here.
+ */
+ obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
+ /* Inherit reference from the ao2_callback from before */
+ obj->parent = class;
+ if (odbc_obj_connect(obj) == ODBC_FAIL) {
+ ao2_ref(obj, -1);
+ return NULL;
}
-#ifdef DEBUG_THREADS
- ast_copy_string(obj->file, file, sizeof(obj->file));
- ast_copy_string(obj->function, function, sizeof(obj->function));
- obj->lineno = lineno;
-#endif
-
- /* We had it locked because of the obj_connects we see here. */
- ao2_unlock(obj);
-
- ast_assert(class == NULL);
-
- ast_assert(ao2_ref(obj, 0) > 1);
return obj;
}
struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno)
{
struct ast_flags flags = { check ? RES_ODBC_SANITY_CHECK : 0 };
+ /* XXX New flow means that the "check" parameter doesn't do anything. We're requesting
+ * a connection from ODBC. We'll either get a new one, which obviously is already connected, or
+ * we'll get one from the ODBC connection pool. In that case, it will ensure to only give us a
+ * live connection
+ */
return _ast_odbc_request_obj2(name, flags, file, function, lineno);
}
-struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
-{
- struct ast_datastore *txn_store;
- AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
- struct odbc_txn_frame *txn = NULL;
-
- if (!chan) {
- /* No channel == no transaction */
- return NULL;
- }
-
- ast_channel_lock(chan);
- if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
- oldlist = txn_store->data;
- } else {
- ast_channel_unlock(chan);
- return NULL;
- }
-
- AST_LIST_LOCK(oldlist);
- ast_channel_unlock(chan);
-
- AST_LIST_TRAVERSE(oldlist, txn, list) {
- if (txn->obj && txn->obj->parent && !strcmp(txn->obj->parent->name, objname)) {
- AST_LIST_UNLOCK(oldlist);
- return txn->obj;
- }
- }
- AST_LIST_UNLOCK(oldlist);
- return NULL;
-}
-
static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
{
int res;
@@ -1490,20 +757,19 @@ static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
if (obj->parent) {
if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) {
- ast_debug(1, "Disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
+ ast_debug(3, "Disconnected %d from %s [%s](%p)\n", res, obj->parent->name, obj->parent->dsn, obj);
} else {
- ast_debug(1, "res_odbc: %s [%s] already disconnected\n", obj->parent->name, obj->parent->dsn);
+ ast_debug(3, "res_odbc: %s [%s](%p) already disconnected\n", obj->parent->name, obj->parent->dsn, obj);
}
}
if ((res = SQLFreeHandle(SQL_HANDLE_DBC, con)) == SQL_SUCCESS) {
- ast_debug(1, "Database handle %p deallocated\n", con);
+ ast_debug(3, "Database handle %p (connection %p) deallocated\n", obj, con);
} else {
SQLGetDiagRec(SQL_HANDLE_DBC, con, 1, state, &err, msg, 100, &mlen);
ast_log(LOG_WARNING, "Unable to deallocate database handle %p? %d errno=%d %s\n", con, res, (int)err, msg);
}
- obj->up = 0;
return ODBC_SUCCESS;
}
@@ -1520,13 +786,8 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
SQLHDBC con;
long int negative_cache_expiration;
- if (obj->up) {
- odbc_obj_disconnect(obj);
- ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
- } else {
- ast_assert(obj->con == NULL);
- ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
- }
+ ast_assert(obj->con == NULL);
+ ast_debug(3, "Connecting %s(%p)\n", obj->parent->name, obj);
/* Dont connect while server is marked as unreachable via negative_connection_cache */
negative_cache_expiration = obj->parent->last_negative_connect.tv_sec + obj->parent->negative_connection_cache.tv_sec;
@@ -1564,155 +825,13 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
}
return ODBC_FAIL;
} else {
- ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
- obj->up = 1;
- obj->last_used = ast_tvnow();
+ ast_debug(3, "res_odbc: Connected to %s [%s (%p)]\n", obj->parent->name, obj->parent->dsn, obj);
}
obj->con = con;
return ODBC_SUCCESS;
}
-static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
-{
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(property);
- AST_APP_ARG(opt);
- );
- struct odbc_txn_frame *tx;
-
- AST_STANDARD_APP_ARGS(args, data);
- if (strcasecmp(args.property, "transaction") == 0) {
- if ((tx = find_transaction(chan, NULL, NULL, 1))) {
- ast_copy_string(buf, tx->name, len);
- return 0;
- }
- } else if (strcasecmp(args.property, "isolation") == 0) {
- if (!ast_strlen_zero(args.opt)) {
- tx = find_transaction(chan, NULL, args.opt, 0);
- } else {
- tx = find_transaction(chan, NULL, NULL, 1);
- }
- if (tx) {
- ast_copy_string(buf, isolation2text(tx->isolation), len);
- return 0;
- }
- } else if (strcasecmp(args.property, "forcecommit") == 0) {
- if (!ast_strlen_zero(args.opt)) {
- tx = find_transaction(chan, NULL, args.opt, 0);
- } else {
- tx = find_transaction(chan, NULL, NULL, 1);
- }
- if (tx) {
- ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
- return 0;
- }
- }
- return -1;
-}
-
-static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
-{
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(property);
- AST_APP_ARG(opt);
- );
- struct odbc_txn_frame *tx;
- SQLINTEGER nativeerror=0, numfields=0;
- SQLSMALLINT diagbytes=0, i;
- unsigned char state[10], diagnostic[256];
-
- AST_STANDARD_APP_ARGS(args, s);
- if (strcasecmp(args.property, "transaction") == 0) {
- /* Set active transaction */
- struct odbc_obj *obj;
- if ((tx = find_transaction(chan, NULL, value, 0))) {
- mark_transaction_active(chan, tx);
- } else {
- /* No such transaction, create one */
- struct ast_flags flags = { RES_ODBC_INDEPENDENT_CONNECTION };
- if (ast_strlen_zero(args.opt) || !(obj = ast_odbc_request_obj2(args.opt, flags))) {
- ast_log(LOG_ERROR, "Could not create transaction: invalid database specification '%s'\n", S_OR(args.opt, ""));
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_DB");
- return -1;
- }
- if (!find_transaction(chan, obj, value, 0)) {
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
- return -1;
- }
- obj->tx = 1;
- }
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
- return 0;
- } else if (strcasecmp(args.property, "forcecommit") == 0) {
- /* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
- if (ast_strlen_zero(args.opt)) {
- tx = find_transaction(chan, NULL, NULL, 1);
- } else {
- tx = find_transaction(chan, NULL, args.opt, 0);
- }
- if (!tx) {
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
- return -1;
- }
- if (ast_true(value)) {
- tx->forcecommit = 1;
- } else if (ast_false(value)) {
- tx->forcecommit = 0;
- } else {
- ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
- return -1;
- }
-
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
- return 0;
- } else if (strcasecmp(args.property, "isolation") == 0) {
- /* How do uncommitted transactions affect reads? */
- int isolation = text2isolation(value);
- if (ast_strlen_zero(args.opt)) {
- tx = find_transaction(chan, NULL, NULL, 1);
- } else {
- tx = find_transaction(chan, NULL, args.opt, 0);
- }
- if (!tx) {
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
- return -1;
- }
- if (isolation == 0) {
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
- ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
- } else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
- SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
- for (i = 0; i < numfields; i++) {
- SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
- ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
- if (i > 10) {
- ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
- break;
- }
- }
- } else {
- pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
- tx->isolation = isolation;
- }
- return 0;
- } else {
- ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
- return -1;
- }
-}
-
-static struct ast_custom_function odbc_function = {
- .name = "ODBC",
- .read = acf_transaction_read,
- .write = acf_transaction_write,
-};
-
-static const char * const app_commit = "ODBC_Commit";
-static const char * const app_rollback = "ODBC_Rollback";
-
/*!
* \internal
* \brief Implements the channels provider.
@@ -1720,12 +839,10 @@ static const char * const app_rollback = "ODBC_Rollback";
static int data_odbc_provider_handler(const struct ast_data_search *search,
struct ast_data *root)
{
- struct ao2_iterator aoi, aoi2;
+ struct ao2_iterator aoi;
struct odbc_class *class;
- struct odbc_obj *current;
- struct ast_data *data_odbc_class, *data_odbc_connections, *data_odbc_connection;
+ struct ast_data *data_odbc_class, *data_odbc_connections;
struct ast_data *enum_node;
- int count;
aoi = ao2_iterator_init(class_container, 0);
while ((class = ao2_iterator_next(&aoi))) {
@@ -1737,18 +854,12 @@ static int data_odbc_provider_handler(const struct ast_data_search *search,
ast_data_add_structure(odbc_class, data_odbc_class, class);
- if (!ao2_container_count(class->obj_container)) {
- ao2_ref(class, -1);
- continue;
- }
-
data_odbc_connections = ast_data_add_node(data_odbc_class, "connections");
if (!data_odbc_connections) {
ao2_ref(class, -1);
continue;
}
- ast_data_add_bool(data_odbc_class, "shared", !class->haspool);
/* isolation */
enum_node = ast_data_add_node(data_odbc_class, "isolation");
if (!enum_node) {
@@ -1756,30 +867,7 @@ static int data_odbc_provider_handler(const struct ast_data_search *search,
continue;
}
ast_data_add_int(enum_node, "value", class->isolation);
- ast_data_add_str(enum_node, "text", isolation2text(class->isolation));
-
- count = 0;
- aoi2 = ao2_iterator_init(class->obj_container, 0);
- while ((current = ao2_iterator_next(&aoi2))) {
- data_odbc_connection = ast_data_add_node(data_odbc_connections, "connection");
- if (!data_odbc_connection) {
- ao2_ref(current, -1);
- continue;
- }
-
- ao2_lock(current);
- ast_data_add_str(data_odbc_connection, "status", current->used ? "in use" :
- current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
- ast_data_add_bool(data_odbc_connection, "transactional", current->tx);
- ao2_unlock(current);
-
- if (class->haspool) {
- ast_data_add_int(data_odbc_connection, "number", ++count);
- }
-
- ao2_ref(current, -1);
- }
- ao2_iterator_destroy(&aoi2);
+ ast_data_add_str(enum_node, "text", ast_odbc_isolation2text(class->isolation));
ao2_ref(class, -1);
if (!ast_data_search_match(search, data_odbc_class)) {
@@ -1807,7 +895,6 @@ static int reload(void)
{
struct odbc_cache_tables *table;
struct odbc_class *class;
- struct odbc_obj *current;
struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
/* First, mark all to be purged */
@@ -1819,51 +906,12 @@ static int reload(void)
load_odbc_config();
- /* Purge remaining classes */
-
- /* Note on how this works; this is a case of circular references, so we
- * explicitly do NOT want to use a callback here (or we wind up in
- * recursive hell).
- *
- * 1. Iterate through all the classes. Note that the classes will currently
- * contain two classes of the same name, one of which is marked delme and
- * will be purged when all remaining objects of the class are released, and
- * the other, which was created above when we re-parsed the config file.
- * 2. On each class, there is a reference held by the master container and
- * a reference held by each connection object. There are two cases for
- * destruction of the class, noted below. However, in all cases, all O-refs
- * (references to objects) will first be freed, which will cause the C-refs
- * (references to classes) to be decremented (but never to 0, because the
- * class container still has a reference).
- * a) If the class has outstanding objects, the C-ref by the class
- * container will then be freed, which leaves only C-refs by any
- * outstanding objects. When the final outstanding object is released
- * (O-refs held by applications and dialplan functions), it will in turn
- * free the final C-ref, causing class destruction.
- * b) If the class has no outstanding objects, when the class container
- * removes the final C-ref, the class will be destroyed.
- */
aoi = ao2_iterator_init(class_container, 0);
- while ((class = ao2_iterator_next(&aoi))) { /* C-ref++ (by iterator) */
+ while ((class = ao2_iterator_next(&aoi))) {
if (class->delme) {
- struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
- while ((current = ao2_iterator_next(&aoi2))) { /* O-ref++ (by iterator) */
- ao2_unlink(class->obj_container, current); /* unlink O-ref from class (reference handled implicitly) */
- ao2_ref(current, -1); /* O-ref-- (by iterator) */
- /* At this point, either
- * a) there's an outstanding O-ref, or
- * b) the object has already been destroyed.
- */
- }
- ao2_iterator_destroy(&aoi2);
- ao2_unlink(class_container, class); /* unlink C-ref from container (reference handled implicitly) */
- /* At this point, either
- * a) there's an outstanding O-ref, which holds an outstanding C-ref, or
- * b) the last remaining C-ref is held by the iterator, which will be
- * destroyed in the next step.
- */
+ ao2_unlink(class_container, class);
}
- ao2_ref(class, -1); /* C-ref-- (by iterator) */
+ ao2_ref(class, -1);
}
ao2_iterator_destroy(&aoi);
@@ -1901,9 +949,6 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
ast_cli_register_multiple(cli_odbc, ARRAY_LEN(cli_odbc));
ast_data_register_multiple(odbc_providers, ARRAY_LEN(odbc_providers));
- ast_register_application_xml(app_commit, commit_exec);
- ast_register_application_xml(app_rollback, rollback_exec);
- ast_custom_function_register(&odbc_function);
ast_log(LOG_NOTICE, "res_odbc loaded.\n");
return 0;
}
diff --git a/res/res_odbc.exports.in b/res/res_odbc.exports.in
index ad674beb1..84bbc48dd 100644
--- a/res/res_odbc.exports.in
+++ b/res/res_odbc.exports.in
@@ -12,9 +12,13 @@
LINKER_SYMBOL_PREFIX_ast_odbc_request_obj;
LINKER_SYMBOL_PREFIXast_odbc_request_obj2;
LINKER_SYMBOL_PREFIX_ast_odbc_request_obj2;
- LINKER_SYMBOL_PREFIXast_odbc_retrieve_transaction_obj;
LINKER_SYMBOL_PREFIXast_odbc_sanity_check;
LINKER_SYMBOL_PREFIXast_odbc_smart_execute;
+ LINKER_SYMBOL_PREFIXast_odbc_class_get_isolation;
+ LINKER_SYMBOL_PREFIXast_odbc_class_get_forcecommit;
+ LINKER_SYMBOL_PREFIXast_odbc_class_get_name;
+ LINKER_SYMBOL_PREFIXast_odbc_text2isolation;
+ LINKER_SYMBOL_PREFIXast_odbc_isolation2text;
local:
*;
};
diff --git a/res/res_odbc_transaction.c b/res/res_odbc_transaction.c
new file mode 100644
index 000000000..33800c3ce
--- /dev/null
+++ b/res/res_odbc_transaction.c
@@ -0,0 +1,529 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/res_odbc.h"
+#include "asterisk/res_odbc_transaction.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+
+/*** MODULEINFO
+ <depend>res_odbc</depend>
+ <support_level>core</support_level>
+ ***/
+
+/*** DOCUMENTATION
+ <function name="ODBC" language="en_US">
+ <synopsis>
+ Controls ODBC transaction properties.
+ </synopsis>
+ <syntax>
+ <parameter name="property" required="true">
+ <enumlist>
+ <enum name="transaction">
+ <para>Gets or sets the active transaction ID. If set, and the transaction ID does not
+ exist and a <replaceable>database name</replaceable> is specified as an argument, it will be created.</para>
+ </enum>
+ <enum name="forcecommit">
+ <para>Controls whether a transaction will be automatically committed when the channel
+ hangs up. Defaults to false. If a <replaceable>transaction ID</replaceable> is specified in the optional argument,
+ the property will be applied to that ID, otherwise to the current active ID.</para>
+ </enum>
+ <enum name="isolation">
+ <para>Controls the data isolation on uncommitted transactions. May be one of the
+ following: <literal>read_committed</literal>, <literal>read_uncommitted</literal>,
+ <literal>repeatable_read</literal>, or <literal>serializable</literal>. Defaults to the
+ database setting in <filename>res_odbc.conf</filename> or <literal>read_committed</literal>
+ if not specified. If a <replaceable>transaction ID</replaceable> is specified as an optional argument, it will be
+ applied to that ID, otherwise the current active ID.</para>
+ </enum>
+ </enumlist>
+ </parameter>
+ <parameter name="argument" required="false" />
+ </syntax>
+ <description>
+ <para>The ODBC() function allows setting several properties to influence how a connected
+ database processes transactions.</para>
+ </description>
+ </function>
+ <application name="ODBC_Commit" language="en_US">
+ <synopsis>
+ Commits a currently open database transaction.
+ </synopsis>
+ <syntax>
+ <parameter name="transaction ID" required="no" />
+ </syntax>
+ <description>
+ <para>Commits the database transaction specified by <replaceable>transaction ID</replaceable>
+ or the current active transaction, if not specified.</para>
+ </description>
+ </application>
+ <application name="ODBC_Rollback" language="en_US">
+ <synopsis>
+ Rollback a currently open database transaction.
+ </synopsis>
+ <syntax>
+ <parameter name="transaction ID" required="no" />
+ </syntax>
+ <description>
+ <para>Rolls back the database transaction specified by <replaceable>transaction ID</replaceable>
+ or the current active transaction, if not specified.</para>
+ </description>
+ </application>
+ ***/
+
+struct odbc_txn_frame {
+ AST_LIST_ENTRY(odbc_txn_frame) list;
+ struct odbc_obj *obj; /*!< Database handle within which transacted statements are run */
+ /*!\brief Is this record the current active transaction within the channel?
+ * Note that the active flag is really only necessary for statements which
+ * are triggered from the dialplan, as there isn't a direct correlation
+ * between multiple statements. Applications wishing to use transactions
+ * may simply perform each statement on the same odbc_obj, which keeps the
+ * transaction persistent.
+ */
+ unsigned int active:1;
+ unsigned int forcecommit:1; /*!< Should uncommitted transactions be auto-committed on handle release? */
+ unsigned int isolation; /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
+ char name[0]; /*!< Name of this transaction ID */
+};
+
+static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx);
+
+static void odbc_txn_free(void *vdata)
+{
+ struct odbc_txn_frame *tx;
+ AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
+
+ ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
+
+ AST_LIST_LOCK(oldlist);
+ while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
+ release_transaction(tx);
+ }
+ AST_LIST_UNLOCK(oldlist);
+ AST_LIST_HEAD_DESTROY(oldlist);
+ ast_free(oldlist);
+}
+
+static const struct ast_datastore_info txn_info = {
+ .type = "ODBC_Transaction",
+ .destroy = odbc_txn_free,
+};
+
+static struct odbc_txn_frame *create_transaction(struct ast_channel *chan, const char *name, const char *dsn)
+{
+ struct ast_datastore *txn_store;
+ AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+ struct odbc_txn_frame *txn = NULL;
+ struct odbc_txn_frame *otxn;
+
+ if (ast_strlen_zero(dsn)) {
+ return NULL;
+ }
+
+ ast_channel_lock(chan);
+ if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
+ oldlist = txn_store->data;
+ } else {
+ if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
+ ast_log(LOG_ERROR, "Unable to allocate a new datastore. Cannot create a new transaction.\n");
+ ast_channel_unlock(chan);
+ return NULL;
+ }
+
+ if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
+ ast_log(LOG_ERROR, "Unable to allocate datastore list head. Cannot create a new transaction.\n");
+ ast_datastore_free(txn_store);
+ ast_channel_unlock(chan);
+ return NULL;
+ }
+
+ txn_store->data = oldlist;
+ AST_LIST_HEAD_INIT(oldlist);
+ ast_channel_datastore_add(chan, txn_store);
+ }
+ ast_channel_unlock(chan);
+
+ txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1);
+ if (!txn) {
+ return NULL;
+ }
+
+ strcpy(txn->name, name); /* SAFE */
+ txn->obj = ast_odbc_request_obj(dsn, 0);
+ if (!txn->obj) {
+ ast_free(txn);
+ return NULL;
+ }
+ txn->isolation = ast_odbc_class_get_isolation(txn->obj->parent);
+ txn->forcecommit = ast_odbc_class_get_isolation(txn->obj->parent);
+ txn->active = 1;
+
+ if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
+ ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr (Autocommit)");
+ ast_odbc_release_obj(txn->obj);
+ ast_free(txn);
+ return NULL;
+ }
+
+ /* Set the isolation property */
+ if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)txn->isolation, 0) == SQL_ERROR) {
+ ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr");
+ ast_odbc_release_obj(txn->obj);
+ ast_free(txn);
+ return NULL;
+ }
+
+ /* On creation, the txn becomes active, and all others inactive */
+ AST_LIST_LOCK(oldlist);
+ AST_LIST_TRAVERSE(oldlist, otxn, list) {
+ otxn->active = 0;
+ }
+ AST_LIST_INSERT_TAIL(oldlist, txn, list);
+ AST_LIST_UNLOCK(oldlist);
+
+ return txn;
+}
+
+static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, const char *name, int active)
+{
+ struct ast_datastore *txn_store;
+ AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+ struct odbc_txn_frame *txn = NULL;
+
+ if (!chan || (!active && !name)) {
+ return NULL;
+ }
+
+ ast_channel_lock(chan);
+ txn_store = ast_channel_datastore_find(chan, &txn_info, NULL);
+ ast_channel_unlock(chan);
+
+ if (!txn_store) {
+ /* No datastore? Definitely no transaction then */
+ return NULL;
+ }
+
+ oldlist = txn_store->data;
+ AST_LIST_LOCK(oldlist);
+
+ AST_LIST_TRAVERSE(oldlist, txn, list) {
+ if (active) {
+ if (txn->active) {
+ break;
+ }
+ } else if (!strcasecmp(txn->name, name)) {
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(oldlist);
+
+ return txn;
+}
+
+static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
+{
+ if (!tx) {
+ return NULL;
+ }
+
+ ast_debug(2, "release_transaction(%p) called (tx->obj = %p\n", tx, tx->obj);
+
+ ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
+ if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
+ ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
+ }
+
+ /* Transaction is done, reset autocommit
+ *
+ * XXX I'm unsure if this is actually necessary, since we're releasing
+ * the connection back to unixODBC. However, if unixODBC pooling is enabled,
+ * it can't hurt to do just in case.
+ */
+ if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
+ ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLSetAttr");
+ }
+
+ ast_odbc_release_obj(tx->obj);
+ ast_free(tx);
+ return NULL;
+}
+
+static int commit_exec(struct ast_channel *chan, const char *data)
+{
+ struct odbc_txn_frame *tx;
+
+ if (ast_strlen_zero(data)) {
+ tx = find_transaction(chan, NULL, 1);
+ } else {
+ tx = find_transaction(chan, data, 0);
+ }
+
+ /* XXX COMMIT_RESULT is set to OK even if no transaction was found. Very misleading */
+ pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
+
+ if (tx) {
+ if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
+ struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
+ pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
+ }
+ }
+ return 0;
+}
+
+static int rollback_exec(struct ast_channel *chan, const char *data)
+{
+ struct odbc_txn_frame *tx;
+
+ if (ast_strlen_zero(data)) {
+ tx = find_transaction(chan, NULL, 1);
+ } else {
+ tx = find_transaction(chan, data, 0);
+ }
+
+ /* XXX ROLLBACK_RESULT is set to OK even if no transaction was found. Very misleading */
+ pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
+
+ if (tx) {
+ if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
+ struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
+ pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
+ }
+ }
+ return 0;
+}
+
+static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(property);
+ AST_APP_ARG(opt);
+ );
+ struct odbc_txn_frame *tx;
+
+ AST_STANDARD_APP_ARGS(args, data);
+ if (strcasecmp(args.property, "transaction") == 0) {
+ if ((tx = find_transaction(chan, NULL, 1))) {
+ ast_copy_string(buf, tx->name, len);
+ return 0;
+ }
+ } else if (strcasecmp(args.property, "isolation") == 0) {
+ if (!ast_strlen_zero(args.opt)) {
+ tx = find_transaction(chan, args.opt, 0);
+ } else {
+ tx = find_transaction(chan, NULL, 1);
+ }
+ if (tx) {
+ ast_copy_string(buf, ast_odbc_isolation2text(tx->isolation), len);
+ return 0;
+ }
+ } else if (strcasecmp(args.property, "forcecommit") == 0) {
+ if (!ast_strlen_zero(args.opt)) {
+ tx = find_transaction(chan, args.opt, 0);
+ } else {
+ tx = find_transaction(chan, NULL, 1);
+ }
+ if (tx) {
+ ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/* XXX The idea of "active" transactions is silly and makes things
+ * more prone to error. It would be much better if the transaction
+ * always had to be specified by name so that no implicit behavior
+ * occurred.
+ */
+static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
+{
+ struct ast_datastore *txn_store;
+ AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+ struct odbc_txn_frame *active = NULL, *txn;
+
+ if (!chan) {
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ oldlist = txn_store->data;
+ AST_LIST_LOCK(oldlist);
+ AST_LIST_TRAVERSE(oldlist, txn, list) {
+ if (txn == tx) {
+ txn->active = 1;
+ active = txn;
+ } else {
+ txn->active = 0;
+ }
+ }
+ AST_LIST_UNLOCK(oldlist);
+ ast_channel_unlock(chan);
+ return active ? 0 : -1;
+}
+
+static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
+{
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(property);
+ AST_APP_ARG(opt);
+ );
+ struct odbc_txn_frame *tx;
+
+ AST_STANDARD_APP_ARGS(args, s);
+ if (strcasecmp(args.property, "transaction") == 0) {
+ /* Set active transaction */
+ if ((tx = find_transaction(chan, value, 0))) {
+ mark_transaction_active(chan, tx);
+ } else if (!create_transaction(chan, value, args.opt)) {
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
+ return -1;
+ }
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
+ return 0;
+ } else if (strcasecmp(args.property, "forcecommit") == 0) {
+ /* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
+ if (ast_strlen_zero(args.opt)) {
+ tx = find_transaction(chan, NULL, 1);
+ } else {
+ tx = find_transaction(chan, args.opt, 0);
+ }
+ if (!tx) {
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
+ return -1;
+ }
+ if (ast_true(value)) {
+ tx->forcecommit = 1;
+ } else if (ast_false(value)) {
+ tx->forcecommit = 0;
+ } else {
+ ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
+ return -1;
+ }
+
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
+ return 0;
+ } else if (strcasecmp(args.property, "isolation") == 0) {
+ /* How do uncommitted transactions affect reads? */
+ /* XXX This is completely useless. The problem is that setting the isolation here
+ * does not actually alter the connection. The only time the isolation gets set is
+ * when the transaction is created. The only way to set isolation is to set it on
+ * the ODBC class's configuration in res_odbc.conf.
+ */
+ int isolation = ast_odbc_text2isolation(value);
+ if (ast_strlen_zero(args.opt)) {
+ tx = find_transaction(chan, NULL, 1);
+ } else {
+ tx = find_transaction(chan, args.opt, 0);
+ }
+ if (!tx) {
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
+ return -1;
+ }
+ if (isolation == 0) {
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
+ ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
+ } else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
+ ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SetConnectAttr (Txn isolation)");
+ } else {
+ pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
+ tx->isolation = isolation;
+ }
+ return 0;
+ } else {
+ ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
+ return -1;
+ }
+}
+
+struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
+{
+ struct ast_datastore *txn_store;
+ AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+ struct odbc_txn_frame *txn = NULL;
+
+ if (!chan || !objname) {
+ /* No channel == no transaction */
+ return NULL;
+ }
+
+ ast_channel_lock(chan);
+ if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
+ oldlist = txn_store->data;
+ } else {
+ ast_channel_unlock(chan);
+ return NULL;
+ }
+
+ AST_LIST_LOCK(oldlist);
+ ast_channel_unlock(chan);
+
+ AST_LIST_TRAVERSE(oldlist, txn, list) {
+ if (txn->obj && txn->obj->parent && !strcmp(ast_odbc_class_get_name(txn->obj->parent), objname)) {
+ AST_LIST_UNLOCK(oldlist);
+ return txn->obj;
+ }
+ }
+ AST_LIST_UNLOCK(oldlist);
+ return NULL;
+}
+
+static struct ast_custom_function odbc_function = {
+ .name = "ODBC",
+ .read = acf_transaction_read,
+ .write = acf_transaction_write,
+};
+
+static const char * const app_commit = "ODBC_Commit";
+static const char * const app_rollback = "ODBC_Rollback";
+
+/* XXX res_odbc takes the path of disallowing unloads from happening.
+ * It's not a great precedent, but since trying to deal with unloading the module
+ * while transactions are active seems like a huge pain to deal with, we'll go
+ * the same way here.
+ */
+static int unload_module(void)
+{
+ return -1;
+}
+
+static int load_module(void)
+{
+ ast_register_application_xml(app_commit, commit_exec);
+ ast_register_application_xml(app_rollback, rollback_exec);
+ ast_custom_function_register(&odbc_function);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC transaction resource",
+ .support_level = AST_MODULE_SUPPORT_CORE,
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_REALTIME_DEPEND,
+ );
diff --git a/res/res_odbc_transaction.exports.in b/res/res_odbc_transaction.exports.in
new file mode 100644
index 000000000..5b0615547
--- /dev/null
+++ b/res/res_odbc_transaction.exports.in
@@ -0,0 +1,6 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIXast_odbc_retrieve_transaction_obj;
+ local:
+ *;
+};