Merge "res_odbc: Remove connection management" into 13

changes/50/2150/1
Joshua Colp 9 years ago committed by Gerrit Code Review
commit f6551868be

@ -28,6 +28,13 @@ res_pjsip:
res_pjproject must be explicitly loaded or res_pjsip and all of its
dependents will fail to load.
ODBC:
- Connection pooling/sharing has been completely removed from Asterisk
in favor of letting ODBC take care of it instead. It is strongly
recommended that you enable connection pooling in unixODBC. As a result
of this, the "pooling", "shared_connection", "limit", and "idlecheck"
options in res_odbc.conf are deprecated and provide no function.
From 13.5.0 to 13.6.0:
ARI:

@ -42,6 +42,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/pbx.h"
#include "asterisk/config.h"
#include "asterisk/res_odbc.h"
#include "asterisk/res_odbc_transaction.h"
#include "asterisk/app.h"
#include "asterisk/cli.h"
#include "asterisk/strings.h"

@ -495,6 +495,17 @@ int ast_unload_realtime(const char *family);
*
* \note You should use the constant SENTINEL to terminate arguments, in
* order to preserve cross-platform compatibility.
*
* TODO The return value of this function is routinely ignored. Ignoring
* the return value means that it's mostly pointless to be calling this.
* You'll see some warning messages potentially, but that's it.
*
* XXX This function is super useful for detecting configuration problems
* early, but unfortunately, the latest in configuration management, sorcery,
* doesn't work well with this. Users of sorcery are familiar with the fields
* they will need to write but don't know if realtime is being used. Sorcery
* knows what storage mechanism is being used but has no high-level knowledge
* of what sort of data is going to be written.
*/
int ast_realtime_require_field(const char *family, ...) attribute_sentinel;

@ -46,16 +46,11 @@ enum {
struct odbc_obj {
SQLHDBC con; /*!< ODBC Connection Handle */
struct odbc_class *parent; /*!< Information about the connection is protected */
struct timeval last_used; /*!< Used by idlecheck to determine if the connection should be renegotiated */
#ifdef DEBUG_THREADS
char file[80];
char function[80];
int lineno;
#endif
unsigned int used:1; /*!< Is this connection currently in use? */
unsigned int up:1;
unsigned int tx:1; /*!< Should this connection be unshared, regardless of the class setting? */
struct odbc_txn_frame *txf; /*!< Reference back to the transaction frame, if applicable */
AST_LIST_ENTRY(odbc_obj) list;
};
@ -102,39 +97,29 @@ int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) __attribute__((d
/*!
* \brief Retrieves a connected ODBC object
* \param name The name of the ODBC class for which a connection is needed.
* \param flags One or more of the following flags:
* \li RES_ODBC_SANITY_CHECK Whether to ensure that a connection is valid before returning the handle. Usually unnecessary.
* \li RES_ODBC_INDEPENDENT_CONNECTION Return a handle which is independent from all others. Usually used when starting a transaction.
* \li RES_ODBC_CONNECTED Only return a connected handle. Intended for use with peers which use idlecheck, which are checked periodically for reachability.
* \param file, function, lineno
*
* \return ODBC object
* \retval NULL if there is no connection available with the requested name.
* \deprecated
*
* 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 ast_odbc_release_obj(), below.
* This is only around for backwards-compatibility with older versions of Asterisk.
*/
struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno);
/*!
* \brief Get a ODBC connection object
*
* The "check" parameter is leftover from an earlier implementation where database connections
* were cached by res_odbc. Since connections are managed by unixODBC now, this parameter is
* only kept around for API compatibility.
*
* \param name The name of the res_odbc.conf section describing the database to connect to
* \param check unused
* \return A connection to the database. Call ast_odbc_release_obj() when finished.
*/
struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno);
#define ast_odbc_request_obj2(a, b) _ast_odbc_request_obj2(a, b, __FILE__, __PRETTY_FUNCTION__, __LINE__)
#define ast_odbc_request_obj(a, b) _ast_odbc_request_obj(a, b, __FILE__, __PRETTY_FUNCTION__, __LINE__)
/*!
* \brief Retrieve a stored ODBC object, if a transaction has been started.
* \param chan Channel associated with the transaction.
* \param objname Name of the database handle. This name corresponds to the name passed
* to \see ast_odbc_request_obj2 (or formerly, to ast_odbc_request_obj). Note that the
* existence of this parameter name explicitly allows for multiple transactions to be open
* at once, albeit to different databases.
* \retval A stored ODBC object, if a transaction was already started.
* \retval NULL, if no transaction yet exists.
*/
struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname);
/*!
* \brief Releases an ODBC object previously allocated by ast_odbc_request_obj()
* \param obj The ODBC object
@ -223,4 +208,39 @@ int ast_odbc_clear_cache(const char *database, const char *tablename);
*/
SQLRETURN ast_odbc_ast_str_SQLGetData(struct ast_str **buf, int pmaxlen, SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLLEN *StrLen_or_Ind);
/*!
* \brief Shortcut for printing errors to logs after a failed SQL operation.
*
* \param handle_type The type of SQL handle on which to gather diagnostics
* \param handle The SQL handle to gather diagnostics from
* \param operation The name of the failed operation.
* \return The error string that was printed to the logs
*/
struct ast_str *ast_odbc_print_errors(SQLSMALLINT handle_type, SQLHANDLE handle, const char *operation);
/*!
* \brief Get the transaction isolation setting for an ODBC class
*/
unsigned int ast_odbc_class_get_isolation(struct odbc_class *class);
/*!
* \brief Get the transaction forcecommit setting for an ODBC class
*/
unsigned int ast_odbc_class_get_forcecommit(struct odbc_class *class);
/*!
* \brief Get the name of an ODBC class.
*/
const char *ast_odbc_class_get_name(struct odbc_class *class);
/*!
* \brief Convert from textual transaction isolation values to their numeric constants
*/
int ast_odbc_text2isolation(const char *txt);
/*!
* \brief Convert from numeric transaction isolation values to their textual counterparts
*/
const char *ast_odbc_isolation2text(int iso);
#endif /* _ASTERISK_RES_ODBC_H */

@ -0,0 +1,54 @@
/*
* 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.
*/
#ifndef RES_ODBC_TRANSACTION_H
#define RES_ODBC_TRANSACTION_H
/*!
* \brief
*
* Retrieve an ODBC transaction connection with the given ODBC class name.
*
* \note The name passed here is *not* the name of the transaction but the name of the
* ODBC class defined in res_odbc.conf.
*
* \note Do not call ast_odbc_release_obj() on the retrieved connection. Calling this function
* does not make you the owner of the connection.
*
* XXX This function is majorly flawed because it ignores properties of transactions and simply
* finds one that corresponds to the given DSN. The problem here is that transactions have names
* and they maintain which transaction is "active" for operations like transaction creation,
* commit, and rollback. However, when it comes to intermediary operations to be made on the
* transactions, all that is ignored. It means that if a channel has created multiple transactions
* for the same DSN, it's a crapshoot which of those transactions the operation will be performed
* on. This can potentially lead to baffling errors under the right circumstances.
*
* XXX The semantics of this function make for writing some awkward code. If you use func_odbc as
* an example, it has to first try to retrieve a transactional connection, then failing that, create
* a non-transactional connection. The result is that it has to remember which type of connection it's
* using and know whether to release the connection when completed or not. It would be much better
* if callers did not have to jump through such hoops.
*
* \param chan Channel on which the ODBC transaction was created
* \param objname The name of the ODBC class configured in res_odbc.conf
* \retval NULL Transaction connection could not be found.
* \retval non-NULL A transactional connection
*/
struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname);
#endif /* RES_ODBC_TRANSACTION_H */

File diff suppressed because it is too large Load Diff

@ -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:
*;
};

@ -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,
);

@ -0,0 +1,6 @@
{
global:
LINKER_SYMBOL_PREFIXast_odbc_retrieve_transaction_obj;
local:
*;
};
Loading…
Cancel
Save