2005-12-21 18:43:10 +00:00
|
|
|
/*
|
2006-02-11 03:14:05 +00:00
|
|
|
* Asterisk -- An open source telephony toolkit.
|
2005-12-21 18:43:10 +00:00
|
|
|
*
|
2006-02-12 04:28:58 +00:00
|
|
|
* Copyright (c) 2005, 2006 Tilghman Lesher
|
2009-09-07 17:15:37 +00:00
|
|
|
* Copyright (c) 2008, 2009 Digium, Inc.
|
2005-12-21 18:43:10 +00:00
|
|
|
*
|
|
|
|
* Tilghman Lesher <func_odbc__200508@the-tilghman.com>
|
|
|
|
*
|
2006-02-11 03:14:05 +00:00
|
|
|
* 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.
|
2005-12-21 18:43:10 +00:00
|
|
|
*/
|
|
|
|
|
2005-12-30 21:18:06 +00:00
|
|
|
/*!
|
|
|
|
* \file
|
|
|
|
*
|
|
|
|
* \brief ODBC lookups
|
|
|
|
*
|
|
|
|
* \author Tilghman Lesher <func_odbc__200508@the-tilghman.com>
|
2007-01-24 09:05:29 +00:00
|
|
|
*
|
|
|
|
* \ingroup functions
|
2005-12-30 21:18:06 +00:00
|
|
|
*/
|
|
|
|
|
2006-04-24 17:11:45 +00:00
|
|
|
/*** MODULEINFO
|
2007-02-09 23:53:51 +00:00
|
|
|
<depend>res_odbc</depend>
|
2011-07-14 20:28:54 +00:00
|
|
|
<support_level>core</support_level>
|
2006-04-24 17:11:45 +00:00
|
|
|
***/
|
|
|
|
|
2006-06-07 18:54:56 +00:00
|
|
|
#include "asterisk.h"
|
|
|
|
|
2006-02-11 03:14:05 +00:00
|
|
|
#include "asterisk/module.h"
|
|
|
|
#include "asterisk/file.h"
|
|
|
|
#include "asterisk/channel.h"
|
|
|
|
#include "asterisk/pbx.h"
|
|
|
|
#include "asterisk/config.h"
|
|
|
|
#include "asterisk/res_odbc.h"
|
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
2015-12-23 21:07:05 +00:00
|
|
|
#include "asterisk/res_odbc_transaction.h"
|
2006-05-06 13:36:29 +00:00
|
|
|
#include "asterisk/app.h"
|
2008-10-22 22:11:31 +00:00
|
|
|
#include "asterisk/cli.h"
|
2008-12-13 08:36:35 +00:00
|
|
|
#include "asterisk/strings.h"
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-11-01 21:10:07 +00:00
|
|
|
/*** DOCUMENTATION
|
|
|
|
<function name="ODBC_FETCH" language="en_US">
|
|
|
|
<synopsis>
|
|
|
|
Fetch a row from a multirow query.
|
|
|
|
</synopsis>
|
|
|
|
<syntax>
|
|
|
|
<parameter name="result-id" required="true" />
|
|
|
|
</syntax>
|
|
|
|
<description>
|
|
|
|
<para>For queries which are marked as mode=multirow, the original
|
|
|
|
query returns a <replaceable>result-id</replaceable> from which results
|
|
|
|
may be fetched. This function implements the actual fetch of the results.</para>
|
|
|
|
<para>This also sets <variable>ODBC_FETCH_STATUS</variable>.</para>
|
|
|
|
<variablelist>
|
|
|
|
<variable name="ODBC_FETCH_STATUS">
|
|
|
|
<value name="SUCESS">
|
|
|
|
If rows are available.
|
|
|
|
</value>
|
|
|
|
<value name="FAILURE">
|
|
|
|
If no rows are available.
|
|
|
|
</value>
|
|
|
|
</variable>
|
|
|
|
</variablelist>
|
|
|
|
</description>
|
|
|
|
</function>
|
|
|
|
<application name="ODBCFinish" language="en_US">
|
|
|
|
<synopsis>
|
|
|
|
Clear the resultset of a sucessful multirow query.
|
|
|
|
</synopsis>
|
|
|
|
<syntax>
|
|
|
|
<parameter name="result-id" required="true" />
|
|
|
|
</syntax>
|
|
|
|
<description>
|
|
|
|
<para>For queries which are marked as mode=multirow, this will clear
|
|
|
|
any remaining rows of the specified resultset.</para>
|
|
|
|
</description>
|
|
|
|
</application>
|
|
|
|
<function name="SQL_ESC" language="en_US">
|
|
|
|
<synopsis>
|
|
|
|
Escapes single ticks for use in SQL statements.
|
|
|
|
</synopsis>
|
|
|
|
<syntax>
|
|
|
|
<parameter name="string" required="true" />
|
|
|
|
</syntax>
|
|
|
|
<description>
|
|
|
|
<para>Used in SQL templates to escape data which may contain single ticks
|
|
|
|
<literal>'</literal> which are otherwise used to delimit data.</para>
|
2014-06-03 07:36:42 +00:00
|
|
|
<para>Example: SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'</para>
|
2008-11-01 21:10:07 +00:00
|
|
|
</description>
|
|
|
|
</function>
|
|
|
|
***/
|
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
static char *config = "func_odbc.conf";
|
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
#define DEFAULT_SINGLE_DB_CONNECTION 0
|
|
|
|
|
|
|
|
static int single_db_connection;
|
|
|
|
|
|
|
|
AST_RWLOCK_DEFINE_STATIC(single_db_connection_lock);
|
|
|
|
|
2009-06-15 17:34:30 +00:00
|
|
|
enum odbc_option_flags {
|
2006-05-22 03:40:31 +00:00
|
|
|
OPT_ESCAPECOMMAS = (1 << 0),
|
2007-05-31 15:05:56 +00:00
|
|
|
OPT_MULTIROW = (1 << 1),
|
2009-06-15 17:34:30 +00:00
|
|
|
};
|
2006-05-22 03:40:31 +00:00
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
struct acf_odbc_query {
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_ENTRY(acf_odbc_query) list;
|
2006-12-28 20:13:00 +00:00
|
|
|
char readhandle[5][30];
|
|
|
|
char writehandle[5][30];
|
2014-05-28 11:37:50 +00:00
|
|
|
char *sql_read;
|
|
|
|
char *sql_write;
|
|
|
|
char *sql_insert;
|
After some study, thought, comparing, etc. I've backed out the previous universal mod to make ast_flags a 64 bit thing. Instead, I added a 64-bit version of ast_flags (ast_flags64), and 64-bit versions of the test-flag, set-flag, etc. macros, and an app_parse_options64 routine, and I use these in app_dial alone, to eliminate the 30-option limit it had grown to meet. There is room now for 32 more options and flags. I was heavily tempted to implement some of the other ideas that were presented, but this solution does not intro any new versions of dial, doesn't have a different API, has a minimal/zero impact on code outside of dial, and doesn't seriously (I hope) affect the code structure of dial. It's the best I can think of right now. My goal was NOT to rewrite dial. I leave that to a future, coordinated effort.
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@75983 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2007-07-19 23:24:27 +00:00
|
|
|
unsigned int flags;
|
2007-05-31 15:05:56 +00:00
|
|
|
int rowlimit;
|
2005-12-21 18:43:10 +00:00
|
|
|
struct ast_custom_function *acf;
|
|
|
|
};
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
static void odbc_datastore_free(void *data);
|
|
|
|
|
2012-07-18 17:18:20 +00:00
|
|
|
static const struct ast_datastore_info odbc_info = {
|
2007-05-31 15:05:56 +00:00
|
|
|
.type = "FUNC_ODBC",
|
|
|
|
.destroy = odbc_datastore_free,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* For storing each result row */
|
|
|
|
struct odbc_datastore_row {
|
|
|
|
AST_LIST_ENTRY(odbc_datastore_row) list;
|
|
|
|
char data[0];
|
|
|
|
};
|
|
|
|
|
|
|
|
/* For storing each result set */
|
|
|
|
struct odbc_datastore {
|
|
|
|
AST_LIST_HEAD(, odbc_datastore_row);
|
|
|
|
char names[0];
|
|
|
|
};
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
/* \brief Data source name
|
|
|
|
*
|
|
|
|
* This holds data that pertains to a DSN
|
|
|
|
*/
|
|
|
|
struct dsn {
|
|
|
|
/*! A connection to the database */
|
|
|
|
struct odbc_obj *connection;
|
|
|
|
/*! The name of the DSN as defined in res_odbc.conf */
|
|
|
|
char name[0];
|
|
|
|
};
|
|
|
|
|
|
|
|
#define DSN_BUCKETS 37
|
|
|
|
|
|
|
|
struct ao2_container *dsns;
|
|
|
|
|
|
|
|
static int dsn_hash(const void *obj, const int flags)
|
|
|
|
{
|
|
|
|
const struct dsn *object;
|
|
|
|
const char *key;
|
|
|
|
|
|
|
|
switch (flags & OBJ_SEARCH_MASK) {
|
|
|
|
case OBJ_SEARCH_KEY:
|
|
|
|
key = obj;
|
|
|
|
break;
|
|
|
|
case OBJ_SEARCH_OBJECT:
|
|
|
|
object = obj;
|
|
|
|
key = object->name;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ast_assert(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return ast_str_hash(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dsn_cmp(void *obj, void *arg, int flags)
|
|
|
|
{
|
|
|
|
const struct dsn *object_left = obj;
|
|
|
|
const struct dsn *object_right = arg;
|
|
|
|
const char *right_key = arg;
|
|
|
|
int cmp;
|
|
|
|
|
|
|
|
switch (flags & OBJ_SEARCH_MASK) {
|
|
|
|
case OBJ_SEARCH_OBJECT:
|
|
|
|
right_key = object_right->name;
|
|
|
|
/* Fall through */
|
|
|
|
case OBJ_SEARCH_KEY:
|
|
|
|
cmp = strcmp(object_left->name, right_key);
|
|
|
|
break;
|
|
|
|
case OBJ_SEARCH_PARTIAL_KEY:
|
|
|
|
cmp = strncmp(object_left->name, right_key, strlen(right_key));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
cmp = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmp) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CMP_MATCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dsn_destructor(void *obj)
|
|
|
|
{
|
|
|
|
struct dsn *dsn = obj;
|
|
|
|
|
|
|
|
if (dsn->connection) {
|
|
|
|
ast_odbc_release_obj(dsn->connection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Create a DSN and connect to the database
|
|
|
|
*
|
|
|
|
* \param name The name of the DSN as found in res_odbc.conf
|
|
|
|
* \retval NULL Fail
|
|
|
|
* \retval non-NULL The newly-created structure
|
|
|
|
*/
|
|
|
|
static struct dsn *create_dsn(const char *name)
|
|
|
|
{
|
|
|
|
struct dsn *dsn;
|
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!dsns) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
dsn = ao2_alloc(sizeof(*dsn) + strlen(name) + 1, dsn_destructor);
|
|
|
|
if (!dsn) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Safe */
|
|
|
|
strcpy(dsn->name, name);
|
|
|
|
|
|
|
|
dsn->connection = ast_odbc_request_obj(name, 0);
|
|
|
|
if (!dsn->connection) {
|
|
|
|
ao2_ref(dsn, -1);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ao2_link_flags(dsns, dsn, OBJ_NOLOCK)) {
|
|
|
|
ao2_ref(dsn, -1);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dsn;
|
|
|
|
}
|
|
|
|
|
2016-04-27 18:23:37 +00:00
|
|
|
static SQLHSTMT silent_execute(struct odbc_obj *obj, void *data);
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Determine if the connection has died.
|
|
|
|
*
|
|
|
|
* \param connection The connection to check
|
|
|
|
* \retval 1 Yep, it's dead
|
|
|
|
* \retval 0 It's alive and well
|
|
|
|
*/
|
|
|
|
static int connection_dead(struct odbc_obj *connection)
|
|
|
|
{
|
|
|
|
SQLINTEGER dead;
|
|
|
|
SQLRETURN res;
|
|
|
|
SQLHSTMT stmt;
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = SQLGetConnectAttr(connection->con, SQL_ATTR_CONNECTION_DEAD, &dead, 0, 0);
|
|
|
|
if (SQL_SUCCEEDED(res)) {
|
|
|
|
return dead == SQL_CD_TRUE ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the Driver doesn't support SQL_ATTR_CONNECTION_DEAD do a direct
|
|
|
|
* execute of a probing statement and see if that succeeds instead
|
|
|
|
*/
|
|
|
|
stmt = ast_odbc_direct_execute(connection, silent_execute, "SELECT 1");
|
|
|
|
if (!stmt) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
/*!
|
|
|
|
* \brief Retrieve a DSN, or create it if it does not exist.
|
|
|
|
*
|
|
|
|
* The created DSN is returned locked. This should be inconsequential
|
|
|
|
* to callers in most cases.
|
|
|
|
*
|
|
|
|
* When finished with the returned structure, the caller must call
|
|
|
|
* \ref release_dsn
|
|
|
|
*
|
|
|
|
* \param name Name of the DSN as found in res_odbc.conf
|
|
|
|
* \retval NULL Unable to retrieve or create the DSN
|
|
|
|
* \retval non-NULL The retrieved/created locked DSN
|
|
|
|
*/
|
|
|
|
static struct dsn *get_dsn(const char *name)
|
|
|
|
{
|
|
|
|
struct dsn *dsn;
|
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!dsns) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
ao2_lock(dsns);
|
|
|
|
dsn = ao2_find(dsns, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
|
|
|
|
if (!dsn) {
|
|
|
|
dsn = create_dsn(name);
|
|
|
|
}
|
|
|
|
ao2_unlock(dsns);
|
|
|
|
|
|
|
|
if (!dsn) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-04-27 18:23:37 +00:00
|
|
|
ao2_lock(dsn);
|
|
|
|
if (!dsn->connection) {
|
|
|
|
dsn->connection = ast_odbc_request_obj(name, 0);
|
|
|
|
if (!dsn->connection) {
|
|
|
|
ao2_unlock(dsn);
|
|
|
|
ao2_ref(dsn, -1);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return dsn;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (connection_dead(dsn->connection)) {
|
|
|
|
ast_odbc_release_obj(dsn->connection);
|
|
|
|
dsn->connection = ast_odbc_request_obj(name, 0);
|
|
|
|
if (!dsn->connection) {
|
|
|
|
ao2_unlock(dsn);
|
|
|
|
ao2_ref(dsn, -1);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
|
|
|
|
return dsn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-05-12 20:18:22 +00:00
|
|
|
* \brief Get a DB handle via a DSN or directly
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
*
|
2016-05-12 20:18:22 +00:00
|
|
|
* If single db connection then get the DB handle via DSN
|
|
|
|
* else by requesting a connection directly
|
|
|
|
*
|
|
|
|
* \param dsn_name Name of the DSN as found in res_odbc.conf
|
|
|
|
* \param dsn The pointer to the DSN
|
|
|
|
* \retval NULL Unable to retrieve the DB handle
|
|
|
|
* \retval non-NULL The retrieved DB handle
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
*/
|
2016-05-12 20:18:22 +00:00
|
|
|
static struct odbc_obj *get_odbc_obj(const char *dsn_name, struct dsn **dsn)
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
{
|
2016-05-12 20:18:22 +00:00
|
|
|
struct odbc_obj *obj = NULL;
|
|
|
|
|
|
|
|
ast_rwlock_rdlock(&single_db_connection_lock);
|
|
|
|
if (single_db_connection) {
|
|
|
|
if (dsn) {
|
|
|
|
*dsn = get_dsn(dsn_name);
|
|
|
|
if (*dsn) {
|
|
|
|
obj = (*dsn)->connection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
obj = ast_odbc_request_obj(dsn_name, 0);
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
ast_rwlock_unlock(&single_db_connection_lock);
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
return obj;
|
|
|
|
}
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
/*!
|
|
|
|
* \brief Release an ODBC obj or a DSN
|
|
|
|
*
|
|
|
|
* If single db connection then unlock and unreference the DSN
|
|
|
|
* else release the ODBC obj
|
|
|
|
*
|
|
|
|
* \param obj The pointer to the ODBC obj to release
|
|
|
|
* \param dsn The pointer to the dsn to unlock and unreference
|
|
|
|
*/
|
|
|
|
static inline void release_obj_or_dsn(struct odbc_obj **obj, struct dsn **dsn)
|
|
|
|
{
|
|
|
|
if (dsn && *dsn) {
|
2016-07-11 00:08:28 +00:00
|
|
|
/* If multiple connections are not enabled then the guarantee
|
|
|
|
* of a single connection already exists and holding on to the
|
|
|
|
* connection would prevent any other user from acquiring it
|
|
|
|
* indefinitely.
|
|
|
|
*/
|
|
|
|
if (ast_odbc_get_max_connections((*dsn)->name) < 2) {
|
|
|
|
ast_odbc_release_obj((*dsn)->connection);
|
|
|
|
(*dsn)->connection = NULL;
|
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
ao2_unlock(*dsn);
|
|
|
|
ao2_ref(*dsn, -1);
|
|
|
|
*dsn = NULL;
|
2016-07-11 00:08:28 +00:00
|
|
|
/* Some callers may provide both an obj and dsn. To ensure that
|
|
|
|
* the connection is not released twice we set it to NULL here if
|
|
|
|
* present.
|
|
|
|
*/
|
|
|
|
if (obj) {
|
|
|
|
*obj = NULL;
|
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
} else if (obj && *obj) {
|
|
|
|
ast_odbc_release_obj(*obj);
|
|
|
|
*obj = NULL;
|
|
|
|
}
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
}
|
|
|
|
|
2009-06-15 17:34:30 +00:00
|
|
|
static AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query);
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
static int resultcount = 0;
|
|
|
|
|
2008-10-22 22:11:31 +00:00
|
|
|
AST_THREADSTORAGE(sql_buf);
|
2008-10-31 17:18:49 +00:00
|
|
|
AST_THREADSTORAGE(sql2_buf);
|
2008-10-15 19:07:39 +00:00
|
|
|
AST_THREADSTORAGE(coldata_buf);
|
|
|
|
AST_THREADSTORAGE(colnames_buf);
|
|
|
|
|
2009-09-07 17:15:37 +00:00
|
|
|
static int acf_fetch(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
static void odbc_datastore_free(void *data)
|
|
|
|
{
|
|
|
|
struct odbc_datastore *result = data;
|
|
|
|
struct odbc_datastore_row *row;
|
2012-05-18 15:51:16 +00:00
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
AST_LIST_LOCK(result);
|
|
|
|
while ((row = AST_LIST_REMOVE_HEAD(result, list))) {
|
|
|
|
ast_free(row);
|
|
|
|
}
|
|
|
|
AST_LIST_UNLOCK(result);
|
|
|
|
AST_LIST_HEAD_DESTROY(result);
|
|
|
|
ast_free(result);
|
|
|
|
}
|
|
|
|
|
2016-04-27 18:23:37 +00:00
|
|
|
/*!
|
|
|
|
* \brief Common execution function for SQL queries.
|
|
|
|
*
|
|
|
|
* \param obj DB connection
|
|
|
|
* \param data The query to execute
|
|
|
|
* \param silent If true, do not print warnings on failure
|
|
|
|
* \retval NULL Failed to execute query
|
|
|
|
* \retval non-NULL The executed statement
|
|
|
|
*/
|
|
|
|
static SQLHSTMT execute(struct odbc_obj *obj, void *data, int silent)
|
2005-12-21 18:43:10 +00:00
|
|
|
{
|
2006-08-20 15:46:00 +00:00
|
|
|
int res;
|
|
|
|
char *sql = data;
|
|
|
|
SQLHSTMT stmt;
|
|
|
|
|
|
|
|
res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2008-10-31 17:18:49 +00:00
|
|
|
ast_log(LOG_WARNING, "SQL Alloc Handle failed (%d)!\n", res);
|
2006-08-20 15:46:00 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2008-01-18 06:58:35 +00:00
|
|
|
res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS);
|
2011-02-28 09:34:16 +00:00
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
|
2016-04-27 18:23:37 +00:00
|
|
|
if (res == SQL_ERROR && !silent) {
|
2008-10-31 17:18:49 +00:00
|
|
|
int i;
|
|
|
|
SQLINTEGER nativeerror=0, numfields=0;
|
|
|
|
SQLSMALLINT diagbytes=0;
|
|
|
|
unsigned char state[10], diagnostic[256];
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-27 18:23:37 +00:00
|
|
|
if (!silent) {
|
|
|
|
ast_log(LOG_WARNING, "SQL Exec Direct failed (%d)![%s]\n", res, sql);
|
|
|
|
}
|
2007-06-01 21:49:34 +00:00
|
|
|
SQLCloseCursor(stmt);
|
2008-10-31 17:18:49 +00:00
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2006-08-20 15:46:00 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return stmt;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2016-04-27 18:23:37 +00:00
|
|
|
static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
|
|
|
|
{
|
|
|
|
return execute(obj, data, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static SQLHSTMT silent_execute(struct odbc_obj *obj, void *data)
|
|
|
|
{
|
|
|
|
return execute(obj, data, 1);
|
|
|
|
}
|
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
/*
|
|
|
|
* Master control routine
|
|
|
|
*/
|
2007-01-06 00:13:33 +00:00
|
|
|
static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
|
2005-12-21 18:43:10 +00:00
|
|
|
{
|
2006-12-30 03:42:20 +00:00
|
|
|
struct odbc_obj *obj = NULL;
|
2005-12-21 18:43:10 +00:00
|
|
|
struct acf_odbc_query *query;
|
2008-08-06 14:51:51 +00:00
|
|
|
char *t, varname[15];
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
int i, dsn_num, bogus_chan = 0;
|
2009-02-19 00:26:01 +00:00
|
|
|
int transactional = 0;
|
2006-05-16 21:14:01 +00:00
|
|
|
AST_DECLARE_APP_ARGS(values,
|
|
|
|
AST_APP_ARG(field)[100];
|
|
|
|
);
|
|
|
|
AST_DECLARE_APP_ARGS(args,
|
|
|
|
AST_APP_ARG(field)[100];
|
|
|
|
);
|
2007-02-03 22:04:09 +00:00
|
|
|
SQLHSTMT stmt = NULL;
|
2006-12-19 14:57:45 +00:00
|
|
|
SQLLEN rows=0;
|
2008-10-23 15:28:43 +00:00
|
|
|
struct ast_str *buf = ast_str_thread_get(&sql_buf, 16);
|
2008-10-31 17:18:49 +00:00
|
|
|
struct ast_str *insertbuf = ast_str_thread_get(&sql2_buf, 16);
|
|
|
|
const char *status = "FAILURE";
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
struct dsn *dsn = NULL;
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2010-06-03 19:46:42 +00:00
|
|
|
if (!buf || !insertbuf) {
|
2008-08-06 14:51:51 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
AST_RWLIST_RDLOCK(&queries);
|
|
|
|
AST_RWLIST_TRAVERSE(&queries, query, list) {
|
2006-05-06 13:36:29 +00:00
|
|
|
if (!strcmp(query->acf->name, cmd)) {
|
2005-12-21 18:43:10 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query) {
|
|
|
|
ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
}
|
2006-02-12 04:28:58 +00:00
|
|
|
return -1;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2007-10-28 14:11:01 +00:00
|
|
|
if (!chan) {
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!(chan = ast_dummy_channel_alloc())) {
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
bogus_chan = 1;
|
2007-10-28 14:11:01 +00:00
|
|
|
}
|
|
|
|
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_start(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
}
|
2007-10-28 14:11:01 +00:00
|
|
|
|
2008-10-22 22:11:31 +00:00
|
|
|
ast_str_make_space(&buf, strlen(query->sql_write) * 2 + 300);
|
2014-06-03 07:36:42 +00:00
|
|
|
/* We only get here if sql_write is set. sql_insert is optional however. */
|
|
|
|
if (query->sql_insert) {
|
|
|
|
ast_str_make_space(&insertbuf, strlen(query->sql_insert) * 2 + 300);
|
|
|
|
}
|
2008-08-06 14:51:51 +00:00
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
/* Parse our arguments */
|
2006-02-12 04:28:58 +00:00
|
|
|
t = value ? ast_strdupa(value) : "";
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2006-01-21 17:50:04 +00:00
|
|
|
if (!s || !t) {
|
|
|
|
ast_log(LOG_ERROR, "Out of memory\n");
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2008-10-31 17:18:49 +00:00
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
2009-07-09 20:04:43 +00:00
|
|
|
} else {
|
2011-09-26 19:40:12 +00:00
|
|
|
ast_channel_unref(chan);
|
2008-10-31 17:18:49 +00:00
|
|
|
}
|
2006-02-12 04:28:58 +00:00
|
|
|
return -1;
|
2006-01-21 17:50:04 +00:00
|
|
|
}
|
|
|
|
|
2006-05-16 21:14:01 +00:00
|
|
|
AST_STANDARD_APP_ARGS(args, s);
|
|
|
|
for (i = 0; i < args.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "ARG%d", i + 1);
|
|
|
|
pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse values, just like arguments */
|
2007-07-23 19:51:41 +00:00
|
|
|
AST_STANDARD_APP_ARGS(values, t);
|
2006-05-16 21:14:01 +00:00
|
|
|
for (i = 0; i < values.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "VAL%d", i + 1);
|
|
|
|
pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2006-05-16 21:14:01 +00:00
|
|
|
/* Additionally set the value as a whole (but push an empty string if value is NULL) */
|
|
|
|
pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : "");
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_str_substitute_variables(&buf, 0, chan, query->sql_write);
|
2014-06-03 07:36:42 +00:00
|
|
|
if (query->sql_insert) {
|
|
|
|
ast_str_substitute_variables(&insertbuf, 0, chan, query->sql_insert);
|
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2009-07-09 20:04:43 +00:00
|
|
|
if (bogus_chan) {
|
2011-09-26 19:40:12 +00:00
|
|
|
chan = ast_channel_unref(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
} else {
|
|
|
|
/* Restore prior values */
|
|
|
|
for (i = 0; i < args.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "ARG%d", i + 1);
|
|
|
|
pbx_builtin_setvar_helper(chan, varname, NULL);
|
2009-02-19 00:26:01 +00:00
|
|
|
}
|
|
|
|
|
2009-07-09 20:04:43 +00:00
|
|
|
for (i = 0; i < values.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "VAL%d", i + 1);
|
|
|
|
pbx_builtin_setvar_helper(chan, varname, NULL);
|
|
|
|
}
|
|
|
|
pbx_builtin_setvar_helper(chan, "VALUE", NULL);
|
2011-03-12 20:08:19 +00:00
|
|
|
}
|
2009-07-09 20:04:43 +00:00
|
|
|
|
2011-03-12 20:08:19 +00:00
|
|
|
/*!\note
|
|
|
|
* Okay, this part is confusing. Transactions belong to a single database
|
|
|
|
* handle. Therefore, when working with transactions, we CANNOT failover
|
|
|
|
* to multiple DSNs. We MUST have a single handle all the way through the
|
|
|
|
* transaction, or else we CANNOT enforce atomicity.
|
|
|
|
*/
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
for (dsn_num = 0; dsn_num < 5; dsn_num++) {
|
|
|
|
if (!ast_strlen_zero(query->writehandle[dsn_num])) {
|
2009-07-09 20:04:43 +00:00
|
|
|
if (transactional) {
|
|
|
|
/* This can only happen second time through or greater. */
|
|
|
|
ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n");
|
2009-02-19 00:26:01 +00:00
|
|
|
}
|
2009-07-09 20:04:43 +00:00
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) {
|
2011-03-12 20:08:19 +00:00
|
|
|
transactional = 1;
|
|
|
|
} else {
|
2016-05-12 20:18:22 +00:00
|
|
|
obj = get_odbc_obj(query->writehandle[dsn_num], &dsn);
|
2011-03-12 20:08:19 +00:00
|
|
|
transactional = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (obj && (stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(buf)))) {
|
|
|
|
break;
|
2009-02-19 00:26:01 +00:00
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!transactional) {
|
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
|
|
|
}
|
2008-10-31 17:18:49 +00:00
|
|
|
}
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2011-02-15 07:03:44 +00:00
|
|
|
if (stmt) {
|
|
|
|
SQLRowCount(stmt, &rows);
|
2008-10-31 17:18:49 +00:00
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2011-03-12 20:08:19 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if (rows != 0) {
|
|
|
|
status = "SUCCESS";
|
2011-03-12 20:08:19 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
} else if (query->sql_insert) {
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!transactional) {
|
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
|
|
|
}
|
2014-06-03 07:36:42 +00:00
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
for (transactional = 0, dsn_num = 0; dsn_num < 5; dsn_num++) {
|
|
|
|
if (!ast_strlen_zero(query->writehandle[dsn_num])) {
|
2014-06-03 07:36:42 +00:00
|
|
|
if (transactional) {
|
|
|
|
/* This can only happen second time through or greater. */
|
|
|
|
ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n");
|
2016-05-12 20:18:22 +00:00
|
|
|
} else {
|
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2014-06-03 07:36:42 +00:00
|
|
|
}
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) {
|
2014-06-03 07:36:42 +00:00
|
|
|
transactional = 1;
|
|
|
|
} else {
|
2016-05-12 20:18:22 +00:00
|
|
|
obj = get_odbc_obj(query->writehandle[dsn_num], &dsn);
|
2014-06-03 07:36:42 +00:00
|
|
|
transactional = 0;
|
|
|
|
}
|
|
|
|
if (obj) {
|
|
|
|
stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(insertbuf));
|
|
|
|
}
|
2011-03-12 20:08:19 +00:00
|
|
|
}
|
2014-06-03 07:36:42 +00:00
|
|
|
if (stmt) {
|
|
|
|
status = "FAILOVER";
|
|
|
|
SQLRowCount(stmt, &rows);
|
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
|
|
|
break;
|
2008-10-31 17:18:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2008-10-31 17:18:49 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
/* Output the affected rows, for all cases. In the event of failure, we
|
|
|
|
* flag this as -1 rows. Note that this is different from 0 affected rows
|
|
|
|
* which would be the case if we succeeded in our query, but the values did
|
|
|
|
* not change. */
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
snprintf(varname, sizeof(varname), "%d", (int)rows);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", varname);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!transactional) {
|
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
|
|
|
}
|
2006-02-12 04:28:58 +00:00
|
|
|
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
}
|
2007-10-28 14:11:01 +00:00
|
|
|
|
2006-02-12 04:28:58 +00:00
|
|
|
return 0;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2007-01-06 00:13:33 +00:00
|
|
|
static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len)
|
2005-12-21 18:43:10 +00:00
|
|
|
{
|
2016-05-12 20:18:22 +00:00
|
|
|
struct odbc_obj *obj = NULL;
|
2005-12-21 18:43:10 +00:00
|
|
|
struct acf_odbc_query *query;
|
2008-10-15 19:07:39 +00:00
|
|
|
char varname[15], rowcount[12] = "-1";
|
|
|
|
struct ast_str *colnames = ast_str_thread_get(&colnames_buf, 16);
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn_num, bogus_chan = 0;
|
2006-05-16 21:14:01 +00:00
|
|
|
AST_DECLARE_APP_ARGS(args,
|
|
|
|
AST_APP_ARG(field)[100];
|
|
|
|
);
|
2007-02-03 22:04:09 +00:00
|
|
|
SQLHSTMT stmt = NULL;
|
2005-12-21 18:43:10 +00:00
|
|
|
SQLSMALLINT colcount=0;
|
2006-12-19 14:57:45 +00:00
|
|
|
SQLLEN indicator;
|
2006-12-28 20:13:00 +00:00
|
|
|
SQLSMALLINT collength;
|
2007-05-31 15:05:56 +00:00
|
|
|
struct odbc_datastore *resultset = NULL;
|
|
|
|
struct odbc_datastore_row *row = NULL;
|
2008-10-23 15:28:43 +00:00
|
|
|
struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
|
2008-10-31 17:18:49 +00:00
|
|
|
const char *status = "FAILURE";
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
struct dsn *dsn = NULL;
|
2008-08-06 14:51:51 +00:00
|
|
|
|
2010-06-03 19:46:42 +00:00
|
|
|
if (!sql || !colnames) {
|
2009-07-09 20:04:43 +00:00
|
|
|
if (chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
}
|
2008-08-06 14:51:51 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-11-07 22:03:50 +00:00
|
|
|
ast_str_reset(colnames);
|
|
|
|
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_RDLOCK(&queries);
|
|
|
|
AST_RWLIST_TRAVERSE(&queries, query, list) {
|
2006-05-06 13:36:29 +00:00
|
|
|
if (!strcmp(query->acf->name, cmd)) {
|
2005-12-21 18:43:10 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query) {
|
|
|
|
ast_log(LOG_ERROR, "No such function '%s'\n", cmd);
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
}
|
2006-02-12 04:28:58 +00:00
|
|
|
return -1;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2007-10-28 14:11:01 +00:00
|
|
|
if (!chan) {
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!(chan = ast_dummy_channel_alloc())) {
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return -1;
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2009-07-09 20:04:43 +00:00
|
|
|
bogus_chan = 1;
|
2007-10-28 14:11:01 +00:00
|
|
|
}
|
|
|
|
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_start(chan);
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2007-10-28 14:11:01 +00:00
|
|
|
|
2006-05-16 21:14:01 +00:00
|
|
|
AST_STANDARD_APP_ARGS(args, s);
|
|
|
|
for (x = 0; x < args.argc; x++) {
|
|
|
|
snprintf(varname, sizeof(varname), "ARG%d", x + 1);
|
|
|
|
pbx_builtin_pushvar_helper(chan, varname, args.field[x]);
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_str_substitute_variables(&sql, 0, chan, query->sql_read);
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2009-07-09 20:04:43 +00:00
|
|
|
if (bogus_chan) {
|
2011-09-26 19:40:12 +00:00
|
|
|
chan = ast_channel_unref(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
} else {
|
|
|
|
/* Restore prior values */
|
|
|
|
for (x = 0; x < args.argc; x++) {
|
|
|
|
snprintf(varname, sizeof(varname), "ARG%d", x + 1);
|
|
|
|
pbx_builtin_setvar_helper(chan, varname, NULL);
|
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
/* Save these flags, so we can release the lock */
|
2006-05-22 03:40:31 +00:00
|
|
|
escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan && ast_test_flag(query, OPT_MULTIROW)) {
|
2009-09-07 17:15:37 +00:00
|
|
|
if (!(resultset = ast_calloc(1, sizeof(*resultset)))) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
ast_autoservice_stop(chan);
|
|
|
|
return -1;
|
|
|
|
}
|
2007-05-31 15:05:56 +00:00
|
|
|
AST_LIST_HEAD_INIT(resultset);
|
2008-10-23 15:28:43 +00:00
|
|
|
if (query->rowlimit) {
|
2007-05-31 15:05:56 +00:00
|
|
|
rowlimit = query->rowlimit;
|
2008-10-23 15:28:43 +00:00
|
|
|
} else {
|
2007-05-31 15:05:56 +00:00
|
|
|
rowlimit = INT_MAX;
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2009-09-07 17:15:37 +00:00
|
|
|
multirow = 1;
|
|
|
|
} else if (!bogus_chan) {
|
|
|
|
if (query->rowlimit > 1) {
|
|
|
|
rowlimit = query->rowlimit;
|
|
|
|
if (!(resultset = ast_calloc(1, sizeof(*resultset)))) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
ast_autoservice_stop(chan);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
AST_LIST_HEAD_INIT(resultset);
|
|
|
|
}
|
2007-05-31 15:05:56 +00:00
|
|
|
}
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2005-12-21 18:43:10 +00:00
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
for (dsn_num = 0; dsn_num < 5; dsn_num++) {
|
|
|
|
if (!ast_strlen_zero(query->readhandle[dsn_num])) {
|
2016-05-12 20:18:22 +00:00
|
|
|
obj = get_odbc_obj(query->readhandle[dsn_num], &dsn);
|
|
|
|
if (!obj) {
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
continue;
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql));
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
2008-10-23 15:28:43 +00:00
|
|
|
if (stmt) {
|
2006-12-28 20:13:00 +00:00
|
|
|
break;
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2006-08-20 15:46:00 +00:00
|
|
|
if (!stmt) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_log(LOG_ERROR, "Unable to execute query [%s]\n", ast_str_buffer(sql));
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2012-05-18 15:51:16 +00:00
|
|
|
odbc_datastore_free(resultset);
|
2006-02-12 04:28:58 +00:00
|
|
|
return -1;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
res = SQLNumResultCols(stmt, &colcount);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql));
|
2007-06-01 21:49:34 +00:00
|
|
|
SQLCloseCursor(stmt);
|
2005-12-21 18:43:10 +00:00
|
|
|
SQLFreeHandle (SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2012-05-18 15:51:16 +00:00
|
|
|
odbc_datastore_free(resultset);
|
2006-02-12 04:28:58 +00:00
|
|
|
return -1;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
res = SQLFetch(stmt);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2006-08-20 15:46:00 +00:00
|
|
|
int res1 = -1;
|
2005-12-21 18:43:10 +00:00
|
|
|
if (res == SQL_NO_DATA) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_verb(4, "Found no rows [%s]\n", ast_str_buffer(sql));
|
2006-08-20 15:46:00 +00:00
|
|
|
res1 = 0;
|
2008-10-06 21:09:05 +00:00
|
|
|
buf[0] = '\0';
|
2007-05-31 15:05:56 +00:00
|
|
|
ast_copy_string(rowcount, "0", sizeof(rowcount));
|
2008-10-31 17:18:49 +00:00
|
|
|
status = "NODATA";
|
2007-05-31 15:05:56 +00:00
|
|
|
} else {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql));
|
2008-10-31 17:18:49 +00:00
|
|
|
status = "FETCHERROR";
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
2007-06-01 21:49:34 +00:00
|
|
|
SQLCloseCursor(stmt);
|
2006-05-16 21:14:01 +00:00
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
}
|
2012-05-18 15:51:16 +00:00
|
|
|
odbc_datastore_free(resultset);
|
2006-08-20 15:46:00 +00:00
|
|
|
return res1;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2008-10-31 17:18:49 +00:00
|
|
|
status = "SUCCESS";
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
for (y = 0; y < rowlimit; y++) {
|
2009-05-07 17:51:13 +00:00
|
|
|
buf[0] = '\0';
|
2007-05-31 15:05:56 +00:00
|
|
|
for (x = 0; x < colcount; x++) {
|
|
|
|
int i;
|
2008-10-15 19:07:39 +00:00
|
|
|
struct ast_str *coldata = ast_str_thread_get(&coldata_buf, 16);
|
2008-12-13 08:36:35 +00:00
|
|
|
char *ptrcoldata;
|
2007-05-31 15:05:56 +00:00
|
|
|
|
2010-06-03 19:46:42 +00:00
|
|
|
if (!coldata) {
|
2012-05-18 15:51:16 +00:00
|
|
|
odbc_datastore_free(resultset);
|
2010-06-03 19:46:42 +00:00
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2011-09-26 19:40:12 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
|
2010-06-03 19:46:42 +00:00
|
|
|
ast_autoservice_stop(chan);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
if (y == 0) {
|
|
|
|
char colname[256];
|
2011-03-10 05:54:53 +00:00
|
|
|
SQLULEN maxcol = 0;
|
2007-05-31 15:05:56 +00:00
|
|
|
|
2008-10-15 19:07:39 +00:00
|
|
|
res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, &maxcol, NULL, NULL);
|
|
|
|
ast_debug(3, "Got collength of %d and maxcol of %d for column '%s' (offset %d)\n", (int)collength, (int)maxcol, colname, x);
|
2007-05-31 15:05:56 +00:00
|
|
|
if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
|
|
|
|
snprintf(colname, sizeof(colname), "field%d", x);
|
|
|
|
}
|
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_str_make_space(&coldata, maxcol + 1);
|
2008-10-15 19:07:39 +00:00
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
if (ast_str_strlen(colnames)) {
|
2008-10-15 19:07:39 +00:00
|
|
|
ast_str_append(&colnames, 0, ",");
|
|
|
|
}
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_str_append_escapecommas(&colnames, 0, colname, sizeof(colname));
|
2007-05-31 15:05:56 +00:00
|
|
|
|
|
|
|
if (resultset) {
|
2008-12-13 08:36:35 +00:00
|
|
|
void *tmp = ast_realloc(resultset, sizeof(*resultset) + ast_str_strlen(colnames) + 1);
|
2007-05-31 15:05:56 +00:00
|
|
|
if (!tmp) {
|
|
|
|
ast_log(LOG_ERROR, "No space for a new resultset?\n");
|
2012-05-18 15:51:16 +00:00
|
|
|
odbc_datastore_free(resultset);
|
2007-06-01 21:49:34 +00:00
|
|
|
SQLCloseCursor(stmt);
|
2007-05-31 15:05:56 +00:00
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2011-09-26 19:40:12 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
|
|
|
|
ast_autoservice_stop(chan);
|
|
|
|
}
|
2007-05-31 15:05:56 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
resultset = tmp;
|
2008-12-13 08:36:35 +00:00
|
|
|
strcpy((char *)resultset + sizeof(*resultset), ast_str_buffer(colnames));
|
2007-05-31 15:05:56 +00:00
|
|
|
}
|
|
|
|
}
|
2006-12-28 20:13:00 +00:00
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
buflen = strlen(buf);
|
2009-01-19 21:42:46 +00:00
|
|
|
res = ast_odbc_ast_str_SQLGetData(&coldata, -1, stmt, x + 1, SQL_CHAR, &indicator);
|
2007-05-31 15:05:56 +00:00
|
|
|
if (indicator == SQL_NULL_DATA) {
|
2008-10-15 19:07:39 +00:00
|
|
|
ast_debug(3, "Got NULL data\n");
|
|
|
|
ast_str_reset(coldata);
|
2007-05-31 15:05:56 +00:00
|
|
|
res = SQL_SUCCESS;
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", ast_str_buffer(sql));
|
2007-05-31 15:05:56 +00:00
|
|
|
y = -1;
|
2008-10-15 17:16:00 +00:00
|
|
|
buf[0] = '\0';
|
2007-05-31 15:05:56 +00:00
|
|
|
goto end_acf_read;
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_debug(2, "Got coldata of '%s'\n", ast_str_buffer(coldata));
|
|
|
|
|
2009-04-08 17:08:43 +00:00
|
|
|
if (x) {
|
2008-12-13 08:36:35 +00:00
|
|
|
buf[buflen++] = ',';
|
|
|
|
}
|
2008-10-15 19:07:39 +00:00
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
/* Copy data, encoding '\' and ',' for the argument parser */
|
2008-12-13 08:36:35 +00:00
|
|
|
ptrcoldata = ast_str_buffer(coldata);
|
|
|
|
for (i = 0; i < ast_str_strlen(coldata); i++) {
|
|
|
|
if (escapecommas && (ptrcoldata[i] == '\\' || ptrcoldata[i] == ',')) {
|
2007-05-31 15:05:56 +00:00
|
|
|
buf[buflen++] = '\\';
|
|
|
|
}
|
2008-12-13 08:36:35 +00:00
|
|
|
buf[buflen++] = ptrcoldata[i];
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
if (buflen >= len - 2) {
|
2007-05-31 15:05:56 +00:00
|
|
|
break;
|
2008-12-13 08:36:35 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
if (ptrcoldata[i] == '\0') {
|
2007-05-31 15:05:56 +00:00
|
|
|
break;
|
2008-12-13 08:36:35 +00:00
|
|
|
}
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
buf[buflen] = '\0';
|
2008-10-15 19:07:39 +00:00
|
|
|
ast_debug(2, "buf is now set to '%s'\n", buf);
|
2007-05-31 15:05:56 +00:00
|
|
|
}
|
2008-10-15 19:07:39 +00:00
|
|
|
ast_debug(2, "buf is now set to '%s'\n", buf);
|
2007-05-31 15:05:56 +00:00
|
|
|
|
|
|
|
if (resultset) {
|
2008-12-13 08:36:35 +00:00
|
|
|
row = ast_calloc(1, sizeof(*row) + buflen + 1);
|
2007-05-31 15:05:56 +00:00
|
|
|
if (!row) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to allocate space for more rows in this resultset.\n");
|
2008-10-31 17:18:49 +00:00
|
|
|
status = "MEMERROR";
|
2007-05-31 15:05:56 +00:00
|
|
|
goto end_acf_read;
|
|
|
|
}
|
|
|
|
strcpy((char *)row + sizeof(*row), buf);
|
|
|
|
AST_LIST_INSERT_TAIL(resultset, row, list);
|
|
|
|
|
|
|
|
/* Get next row */
|
|
|
|
res = SQLFetch(stmt);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2008-12-13 08:36:35 +00:00
|
|
|
if (res != SQL_NO_DATA) {
|
|
|
|
ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql));
|
|
|
|
}
|
|
|
|
/* Number of rows in the resultset */
|
2007-05-31 15:05:56 +00:00
|
|
|
y++;
|
2006-05-06 13:36:29 +00:00
|
|
|
break;
|
2007-05-31 15:05:56 +00:00
|
|
|
}
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
end_acf_read:
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
|
|
|
snprintf(rowcount, sizeof(rowcount), "%d", y);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
|
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
|
|
|
|
pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(colnames));
|
|
|
|
if (resultset) {
|
|
|
|
struct ast_datastore *odbc_store;
|
2009-09-07 17:15:37 +00:00
|
|
|
if (multirow) {
|
2012-04-17 18:57:40 +00:00
|
|
|
int uid;
|
2009-09-07 17:15:37 +00:00
|
|
|
uid = ast_atomic_fetchadd_int(&resultcount, +1) + 1;
|
|
|
|
snprintf(buf, len, "%d", uid);
|
|
|
|
} else {
|
|
|
|
/* Name of the query is name of the resultset */
|
|
|
|
ast_copy_string(buf, cmd, len);
|
|
|
|
|
|
|
|
/* If there's one with the same name already, free it */
|
|
|
|
ast_channel_lock(chan);
|
|
|
|
if ((odbc_store = ast_channel_datastore_find(chan, &odbc_info, buf))) {
|
|
|
|
ast_channel_datastore_remove(chan, odbc_store);
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_datastore_free(odbc_store);
|
2009-09-07 17:15:37 +00:00
|
|
|
}
|
|
|
|
ast_channel_unlock(chan);
|
|
|
|
}
|
2009-07-09 20:04:43 +00:00
|
|
|
odbc_store = ast_datastore_alloc(&odbc_info, buf);
|
|
|
|
if (!odbc_store) {
|
|
|
|
ast_log(LOG_ERROR, "Rows retrieved, but unable to store it in the channel. Results fail.\n");
|
|
|
|
odbc_datastore_free(resultset);
|
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2009-07-09 20:04:43 +00:00
|
|
|
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
odbc_store->data = resultset;
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_channel_lock(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
ast_channel_datastore_add(chan, odbc_store);
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_channel_unlock(chan);
|
2007-05-31 15:05:56 +00:00
|
|
|
}
|
|
|
|
}
|
2007-06-01 21:49:34 +00:00
|
|
|
SQLCloseCursor(stmt);
|
2005-12-21 18:43:10 +00:00
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2009-09-07 17:15:37 +00:00
|
|
|
if (resultset && !multirow) {
|
|
|
|
/* Fetch the first resultset */
|
|
|
|
if (!acf_fetch(chan, "", buf, buf, len)) {
|
|
|
|
buf[0] = '\0';
|
|
|
|
}
|
|
|
|
}
|
2009-07-09 20:04:43 +00:00
|
|
|
if (!bogus_chan) {
|
2007-10-28 14:11:01 +00:00
|
|
|
ast_autoservice_stop(chan);
|
2009-07-09 20:04:43 +00:00
|
|
|
}
|
2006-02-12 04:28:58 +00:00
|
|
|
return 0;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2007-01-06 00:13:33 +00:00
|
|
|
static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
2005-12-27 02:02:23 +00:00
|
|
|
{
|
2006-02-12 04:28:58 +00:00
|
|
|
char *out = buf;
|
|
|
|
|
|
|
|
for (; *data && out - buf < len; data++) {
|
|
|
|
if (*data == '\'') {
|
2005-12-27 02:02:23 +00:00
|
|
|
*out = '\'';
|
|
|
|
out++;
|
|
|
|
}
|
2006-02-12 04:28:58 +00:00
|
|
|
*out++ = *data;
|
2005-12-27 02:02:23 +00:00
|
|
|
}
|
|
|
|
*out = '\0';
|
2006-02-12 04:28:58 +00:00
|
|
|
|
|
|
|
return 0;
|
2005-12-27 02:02:23 +00:00
|
|
|
}
|
|
|
|
|
2006-02-11 03:14:05 +00:00
|
|
|
static struct ast_custom_function escape_function = {
|
2005-12-27 02:02:23 +00:00
|
|
|
.name = "SQL_ESC",
|
|
|
|
.read = acf_escape,
|
|
|
|
.write = NULL,
|
|
|
|
};
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
static int acf_fetch(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
|
|
|
{
|
|
|
|
struct ast_datastore *store;
|
|
|
|
struct odbc_datastore *resultset;
|
|
|
|
struct odbc_datastore_row *row;
|
2012-01-24 17:04:20 +00:00
|
|
|
|
2014-03-27 19:21:44 +00:00
|
|
|
if (!chan) {
|
|
|
|
ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_channel_lock(chan);
|
2007-05-31 15:05:56 +00:00
|
|
|
store = ast_channel_datastore_find(chan, &odbc_info, data);
|
|
|
|
if (!store) {
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_channel_unlock(chan);
|
2008-10-02 17:16:54 +00:00
|
|
|
pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "FAILURE");
|
2007-05-31 15:05:56 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
resultset = store->data;
|
|
|
|
AST_LIST_LOCK(resultset);
|
|
|
|
row = AST_LIST_REMOVE_HEAD(resultset, list);
|
|
|
|
AST_LIST_UNLOCK(resultset);
|
|
|
|
if (!row) {
|
|
|
|
/* Cleanup datastore */
|
|
|
|
ast_channel_datastore_remove(chan, store);
|
2008-08-05 16:56:11 +00:00
|
|
|
ast_datastore_free(store);
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_channel_unlock(chan);
|
2008-10-02 17:16:54 +00:00
|
|
|
pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "FAILURE");
|
2007-05-31 15:05:56 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", resultset->names);
|
2012-01-24 17:04:20 +00:00
|
|
|
ast_channel_unlock(chan);
|
2007-05-31 15:05:56 +00:00
|
|
|
ast_copy_string(buf, row->data, len);
|
|
|
|
ast_free(row);
|
2008-10-02 17:16:54 +00:00
|
|
|
pbx_builtin_setvar_helper(chan, "ODBC_FETCH_STATUS", "SUCCESS");
|
2007-05-31 15:05:56 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ast_custom_function fetch_function = {
|
|
|
|
.name = "ODBC_FETCH",
|
|
|
|
.read = acf_fetch,
|
|
|
|
.write = NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *app_odbcfinish = "ODBCFinish";
|
|
|
|
|
2009-05-21 21:13:09 +00:00
|
|
|
static int exec_odbcfinish(struct ast_channel *chan, const char *data)
|
2007-05-31 15:05:56 +00:00
|
|
|
{
|
2012-01-24 17:04:20 +00:00
|
|
|
struct ast_datastore *store;
|
|
|
|
|
|
|
|
ast_channel_lock(chan);
|
|
|
|
store = ast_channel_datastore_find(chan, &odbc_info, data);
|
|
|
|
if (store) {
|
|
|
|
ast_channel_datastore_remove(chan, store);
|
|
|
|
ast_datastore_free(store);
|
|
|
|
}
|
|
|
|
ast_channel_unlock(chan);
|
2007-05-31 15:05:56 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-28 11:37:50 +00:00
|
|
|
static int free_acf_query(struct acf_odbc_query *query)
|
|
|
|
{
|
|
|
|
if (query) {
|
|
|
|
if (query->acf) {
|
|
|
|
if (query->acf->name)
|
|
|
|
ast_free((char *)query->acf->name);
|
|
|
|
ast_string_field_free_memory(query->acf);
|
|
|
|
ast_free(query->acf);
|
|
|
|
}
|
|
|
|
ast_free(query->sql_read);
|
|
|
|
ast_free(query->sql_write);
|
|
|
|
ast_free(query->sql_insert);
|
|
|
|
ast_free(query);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query)
|
|
|
|
{
|
2006-09-20 20:40:39 +00:00
|
|
|
const char *tmp;
|
2014-06-03 07:36:42 +00:00
|
|
|
const char *tmp2;
|
2006-12-28 20:13:00 +00:00
|
|
|
int i;
|
2005-12-21 18:43:10 +00:00
|
|
|
|
|
|
|
if (!cfg || !catg) {
|
2006-12-28 20:13:00 +00:00
|
|
|
return EINVAL;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if (!(*query = ast_calloc(1, sizeof(**query)))) {
|
2006-12-28 20:13:00 +00:00
|
|
|
return ENOMEM;
|
2014-06-03 07:36:42 +00:00
|
|
|
}
|
2006-12-28 20:13:00 +00:00
|
|
|
|
|
|
|
if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) {
|
|
|
|
char *tmp2 = ast_strdupa(tmp);
|
2008-08-07 01:07:40 +00:00
|
|
|
AST_DECLARE_APP_ARGS(writeconf,
|
2006-12-28 20:13:00 +00:00
|
|
|
AST_APP_ARG(dsn)[5];
|
|
|
|
);
|
2008-08-07 01:07:40 +00:00
|
|
|
AST_STANDARD_APP_ARGS(writeconf, tmp2);
|
2006-12-28 20:13:00 +00:00
|
|
|
for (i = 0; i < 5; i++) {
|
2008-08-07 01:07:40 +00:00
|
|
|
if (!ast_strlen_zero(writeconf.dsn[i]))
|
|
|
|
ast_copy_string((*query)->writehandle[i], writeconf.dsn[i], sizeof((*query)->writehandle[i]));
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2006-12-28 20:13:00 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) {
|
|
|
|
char *tmp2 = ast_strdupa(tmp);
|
2008-08-07 01:07:40 +00:00
|
|
|
AST_DECLARE_APP_ARGS(readconf,
|
2006-12-28 20:13:00 +00:00
|
|
|
AST_APP_ARG(dsn)[5];
|
|
|
|
);
|
2008-08-07 01:07:40 +00:00
|
|
|
AST_STANDARD_APP_ARGS(readconf, tmp2);
|
2006-12-28 20:13:00 +00:00
|
|
|
for (i = 0; i < 5; i++) {
|
2008-08-07 01:07:40 +00:00
|
|
|
if (!ast_strlen_zero(readconf.dsn[i]))
|
|
|
|
ast_copy_string((*query)->readhandle[i], readconf.dsn[i], sizeof((*query)->readhandle[i]));
|
2006-12-28 20:13:00 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
} else {
|
2006-12-28 20:13:00 +00:00
|
|
|
/* If no separate readhandle, then use the writehandle for reading */
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
if (!ast_strlen_zero((*query)->writehandle[i]))
|
|
|
|
ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i]));
|
|
|
|
}
|
2014-06-03 07:36:42 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "readsql")) ||
|
|
|
|
(tmp2 = ast_variable_retrieve(cfg, catg, "read"))) {
|
|
|
|
if (!tmp) {
|
|
|
|
ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s. Please use 'readsql' instead.\n", catg);
|
|
|
|
tmp = tmp2;
|
|
|
|
}
|
|
|
|
if (*tmp != '\0') { /* non-empty string */
|
|
|
|
if (!((*query)->sql_read = ast_strdup(tmp))) {
|
|
|
|
free_acf_query(*query);
|
|
|
|
*query = NULL;
|
|
|
|
return ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
2007-07-31 01:10:47 +00:00
|
|
|
}
|
2006-12-28 20:13:00 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((*query)->sql_read && ast_strlen_zero((*query)->readhandle[0])) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2007-01-17 23:35:43 +00:00
|
|
|
*query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg);
|
|
|
|
return EINVAL;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "writesql")) ||
|
|
|
|
(tmp2 = ast_variable_retrieve(cfg, catg, "write"))) {
|
|
|
|
if (!tmp) {
|
|
|
|
ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s. Please use 'writesql' instead.\n", catg);
|
|
|
|
tmp = tmp2;
|
|
|
|
}
|
|
|
|
if (*tmp != '\0') { /* non-empty string */
|
|
|
|
if (!((*query)->sql_write = ast_strdup(tmp))) {
|
|
|
|
free_acf_query(*query);
|
|
|
|
*query = NULL;
|
|
|
|
return ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
2007-07-31 01:10:47 +00:00
|
|
|
}
|
2006-12-28 20:13:00 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((*query)->sql_write && ast_strlen_zero((*query)->writehandle[0])) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2007-01-17 23:35:43 +00:00
|
|
|
*query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg);
|
|
|
|
return EINVAL;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2008-10-31 17:18:49 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "insertsql"))) {
|
2014-06-03 07:36:42 +00:00
|
|
|
if (*tmp != '\0') { /* non-empty string */
|
|
|
|
if (!((*query)->sql_insert = ast_strdup(tmp))) {
|
|
|
|
free_acf_query(*query);
|
|
|
|
*query = NULL;
|
|
|
|
return ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
2008-10-31 17:18:49 +00:00
|
|
|
}
|
|
|
|
|
2006-05-22 03:40:31 +00:00
|
|
|
/* Allow escaping of embedded commas in fields to be turned off */
|
|
|
|
ast_set_flag((*query), OPT_ESCAPECOMMAS);
|
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) {
|
|
|
|
if (ast_false(tmp))
|
|
|
|
ast_clear_flag((*query), OPT_ESCAPECOMMAS);
|
|
|
|
}
|
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "mode"))) {
|
|
|
|
if (strcasecmp(tmp, "multirow") == 0)
|
|
|
|
ast_set_flag((*query), OPT_MULTIROW);
|
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "rowlimit")))
|
2009-08-10 19:20:57 +00:00
|
|
|
sscanf(tmp, "%30d", &((*query)->rowlimit));
|
2007-05-31 15:05:56 +00:00
|
|
|
}
|
|
|
|
|
2006-05-06 13:36:29 +00:00
|
|
|
(*query)->acf = ast_calloc(1, sizeof(struct ast_custom_function));
|
2014-06-03 07:36:42 +00:00
|
|
|
if (!(*query)->acf) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2007-01-17 23:35:43 +00:00
|
|
|
*query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
return ENOMEM;
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
2008-11-01 21:10:07 +00:00
|
|
|
if (ast_string_field_init((*query)->acf, 128)) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2008-11-01 21:10:07 +00:00
|
|
|
*query = NULL;
|
|
|
|
return ENOMEM;
|
|
|
|
}
|
2006-05-06 13:36:29 +00:00
|
|
|
|
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) {
|
2012-08-21 21:01:11 +00:00
|
|
|
if (ast_asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg) < 0) {
|
|
|
|
(*query)->acf->name = NULL;
|
2008-11-02 18:52:13 +00:00
|
|
|
}
|
2006-05-06 13:36:29 +00:00
|
|
|
} else {
|
2012-08-21 21:01:11 +00:00
|
|
|
if (ast_asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg) < 0) {
|
|
|
|
(*query)->acf->name = NULL;
|
2008-11-02 18:52:13 +00:00
|
|
|
}
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if (!(*query)->acf->name) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2007-01-17 23:35:43 +00:00
|
|
|
*query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
return ENOMEM;
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-10-02 17:16:54 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "syntax")) && !ast_strlen_zero(tmp)) {
|
2008-11-01 21:10:07 +00:00
|
|
|
ast_string_field_build((*query)->acf, syntax, "%s(%s)", (*query)->acf->name, tmp);
|
2008-10-02 17:16:54 +00:00
|
|
|
} else {
|
2008-11-01 21:10:07 +00:00
|
|
|
ast_string_field_build((*query)->acf, syntax, "%s(<arg1>[...[,<argN>]])", (*query)->acf->name);
|
2008-10-02 17:16:54 +00:00
|
|
|
}
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-11-01 21:10:07 +00:00
|
|
|
if (ast_strlen_zero((*query)->acf->syntax)) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2007-01-17 23:35:43 +00:00
|
|
|
*query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
return ENOMEM;
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
|
|
|
|
2008-10-02 17:16:54 +00:00
|
|
|
if ((tmp = ast_variable_retrieve(cfg, catg, "synopsis")) && !ast_strlen_zero(tmp)) {
|
2008-11-01 21:10:07 +00:00
|
|
|
ast_string_field_set((*query)->acf, synopsis, tmp);
|
2008-10-02 17:16:54 +00:00
|
|
|
} else {
|
2008-11-01 21:10:07 +00:00
|
|
|
ast_string_field_set((*query)->acf, synopsis, "Runs the referenced query with the specified arguments");
|
2008-10-02 17:16:54 +00:00
|
|
|
}
|
|
|
|
|
2008-11-01 21:10:07 +00:00
|
|
|
if (ast_strlen_zero((*query)->acf->synopsis)) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2008-10-02 17:16:54 +00:00
|
|
|
*query = NULL;
|
|
|
|
return ENOMEM;
|
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((*query)->sql_read && (*query)->sql_write) {
|
2008-11-01 21:10:07 +00:00
|
|
|
ast_string_field_build((*query)->acf, desc,
|
2006-05-06 13:36:29 +00:00
|
|
|
"Runs the following query, as defined in func_odbc.conf, performing\n"
|
2014-06-03 07:36:42 +00:00
|
|
|
"substitution of the arguments into the query as specified by ${ARG1},\n"
|
2006-05-06 13:36:29 +00:00
|
|
|
"${ARG2}, ... ${ARGn}. When setting the function, the values are provided\n"
|
|
|
|
"either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
|
2008-10-31 17:18:49 +00:00
|
|
|
"%s"
|
2014-06-03 07:36:42 +00:00
|
|
|
"\nRead:\n%s\n\nWrite:\n%s%s%s",
|
|
|
|
(*query)->sql_insert ?
|
2008-10-31 17:18:49 +00:00
|
|
|
"If the write query affects no rows, the insert query will be\n"
|
2014-06-03 07:36:42 +00:00
|
|
|
"performed.\n" : "",
|
2006-05-06 13:36:29 +00:00
|
|
|
(*query)->sql_read,
|
2008-10-31 17:18:49 +00:00
|
|
|
(*query)->sql_write,
|
2014-06-03 07:36:42 +00:00
|
|
|
(*query)->sql_insert ? "\n\nInsert:\n" : "",
|
|
|
|
(*query)->sql_insert ? (*query)->sql_insert : "");
|
|
|
|
} else if ((*query)->sql_read) {
|
2008-11-01 21:10:07 +00:00
|
|
|
ast_string_field_build((*query)->acf, desc,
|
2006-05-06 13:36:29 +00:00
|
|
|
"Runs the following query, as defined in func_odbc.conf, performing\n"
|
2014-06-03 07:36:42 +00:00
|
|
|
"substitution of the arguments into the query as specified by ${ARG1},\n"
|
|
|
|
"${ARG2}, ... ${ARGn}. This function may only be read, not set.\n\nSQL:\n%s",
|
|
|
|
(*query)->sql_read);
|
|
|
|
} else if ((*query)->sql_write) {
|
|
|
|
ast_string_field_build((*query)->acf, desc,
|
|
|
|
"Runs the following query, as defined in func_odbc.conf, performing\n"
|
|
|
|
"substitution of the arguments into the query as specified by ${ARG1},\n"
|
2006-05-06 13:36:29 +00:00
|
|
|
"${ARG2}, ... ${ARGn}. The values are provided either in whole as\n"
|
|
|
|
"${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n"
|
2014-06-03 07:36:42 +00:00
|
|
|
"This function may only be set.\n%s\nSQL:\n%s%s%s",
|
|
|
|
(*query)->sql_insert ?
|
2008-10-31 17:18:49 +00:00
|
|
|
"If the write query affects no rows, the insert query will be\n"
|
2014-06-03 07:36:42 +00:00
|
|
|
"performed.\n" : "",
|
2008-10-31 17:18:49 +00:00
|
|
|
(*query)->sql_write,
|
2014-06-03 07:36:42 +00:00
|
|
|
(*query)->sql_insert ? "\n\nInsert:\n" : "",
|
|
|
|
(*query)->sql_insert ? (*query)->sql_insert : "");
|
2006-12-28 20:13:00 +00:00
|
|
|
} else {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
|
|
|
*query = NULL;
|
2008-10-31 17:18:49 +00:00
|
|
|
ast_log(LOG_WARNING, "Section '%s' was found, but there was no SQL to execute. Ignoring.\n", catg);
|
2006-12-28 20:13:00 +00:00
|
|
|
return EINVAL;
|
2006-05-06 13:36:29 +00:00
|
|
|
}
|
|
|
|
|
2008-11-01 21:10:07 +00:00
|
|
|
if (ast_strlen_zero((*query)->acf->desc)) {
|
2014-05-28 11:37:50 +00:00
|
|
|
free_acf_query(*query);
|
2007-01-17 23:35:43 +00:00
|
|
|
*query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
return ENOMEM;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
2006-05-06 13:36:29 +00:00
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((*query)->sql_read) {
|
2006-05-06 13:36:29 +00:00
|
|
|
(*query)->acf->read = acf_odbc_read;
|
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if ((*query)->sql_write) {
|
2006-05-06 13:36:29 +00:00
|
|
|
(*query)->acf->write = acf_odbc_write;
|
|
|
|
}
|
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-10-22 22:11:31 +00:00
|
|
|
static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
AST_DECLARE_APP_ARGS(args,
|
|
|
|
AST_APP_ARG(field)[100];
|
|
|
|
);
|
2008-12-09 14:49:30 +00:00
|
|
|
struct ast_str *sql;
|
2008-10-22 22:11:31 +00:00
|
|
|
char *char_args, varname[10];
|
|
|
|
struct acf_odbc_query *query;
|
|
|
|
struct ast_channel *chan;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "odbc read";
|
|
|
|
e->usage =
|
|
|
|
"Usage: odbc read <name> <args> [exec]\n"
|
|
|
|
" Evaluates the SQL provided in the ODBC function <name>, and\n"
|
|
|
|
" optionally executes the function. This function is intended for\n"
|
|
|
|
" testing purposes. Remember to quote arguments containing spaces.\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
if (a->pos == 2) {
|
|
|
|
int wordlen = strlen(a->word), which = 0;
|
|
|
|
/* Complete function name */
|
|
|
|
AST_RWLIST_RDLOCK(&queries);
|
|
|
|
AST_RWLIST_TRAVERSE(&queries, query, list) {
|
|
|
|
if (!strncasecmp(query->acf->name, a->word, wordlen)) {
|
|
|
|
if (++which > a->n) {
|
|
|
|
char *res = ast_strdup(query->acf->name);
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return NULL;
|
|
|
|
} else if (a->pos == 4) {
|
2017-02-11 15:57:03 +00:00
|
|
|
static const char * const completions[] = { "exec", NULL };
|
|
|
|
return ast_cli_complete(a->word, completions, a->n);
|
2008-10-22 22:11:31 +00:00
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->argc < 4 || a->argc > 5) {
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
}
|
|
|
|
|
2008-12-09 14:49:30 +00:00
|
|
|
sql = ast_str_thread_get(&sql_buf, 16);
|
|
|
|
if (!sql) {
|
|
|
|
return CLI_FAILURE;
|
|
|
|
}
|
|
|
|
|
2008-10-22 22:11:31 +00:00
|
|
|
AST_RWLIST_RDLOCK(&queries);
|
|
|
|
AST_RWLIST_TRAVERSE(&queries, query, list) {
|
|
|
|
if (!strcmp(query->acf->name, a->argv[2])) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query) {
|
|
|
|
ast_cli(a->fd, "No such query '%s'\n", a->argv[2]);
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if (!query->sql_read) {
|
2011-07-18 07:12:22 +00:00
|
|
|
ast_cli(a->fd, "The function %s has no readsql parameter.\n", a->argv[2]);
|
2008-10-22 22:11:31 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_str_make_space(&sql, strlen(query->sql_read) * 2 + 300);
|
|
|
|
|
|
|
|
/* Evaluate function */
|
|
|
|
char_args = ast_strdupa(a->argv[3]);
|
|
|
|
|
2009-06-26 15:28:53 +00:00
|
|
|
chan = ast_dummy_channel_alloc();
|
2011-09-26 19:40:12 +00:00
|
|
|
if (!chan) {
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_FAILURE;
|
|
|
|
}
|
2008-10-22 22:11:31 +00:00
|
|
|
|
|
|
|
AST_STANDARD_APP_ARGS(args, char_args);
|
|
|
|
for (i = 0; i < args.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "ARG%d", i + 1);
|
|
|
|
pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
|
|
|
|
}
|
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_str_substitute_variables(&sql, 0, chan, query->sql_read);
|
2011-09-26 19:40:12 +00:00
|
|
|
chan = ast_channel_unref(chan);
|
2008-10-22 22:11:31 +00:00
|
|
|
|
|
|
|
if (a->argc == 5 && !strcmp(a->argv[4], "exec")) {
|
|
|
|
/* Execute the query */
|
2016-05-12 20:18:22 +00:00
|
|
|
struct odbc_obj *obj = NULL;
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
struct dsn *dsn = NULL;
|
|
|
|
int dsn_num, executed = 0;
|
2008-10-22 22:11:31 +00:00
|
|
|
SQLHSTMT stmt;
|
|
|
|
int rows = 0, res, x;
|
|
|
|
SQLSMALLINT colcount = 0, collength;
|
|
|
|
SQLLEN indicator;
|
2008-10-23 15:28:43 +00:00
|
|
|
struct ast_str *coldata = ast_str_thread_get(&coldata_buf, 16);
|
|
|
|
char colname[256];
|
|
|
|
SQLULEN maxcol;
|
2008-10-22 22:11:31 +00:00
|
|
|
|
2010-06-03 19:46:42 +00:00
|
|
|
if (!coldata) {
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
for (dsn_num = 0; dsn_num < 5; dsn_num++) {
|
|
|
|
if (ast_strlen_zero(query->readhandle[dsn_num])) {
|
2008-10-23 15:28:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
obj = get_odbc_obj(query->readhandle[dsn_num], &dsn);
|
|
|
|
if (!obj) {
|
2008-10-23 15:28:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
ast_debug(1, "Found handle %s\n", query->readhandle[dsn_num]);
|
2008-10-23 15:28:43 +00:00
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) {
|
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2008-10-23 15:28:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
executed = 1;
|
|
|
|
|
|
|
|
res = SQLNumResultCols(stmt, &colcount);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql));
|
2008-10-23 15:28:43 +00:00
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle (SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2008-10-23 15:28:43 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = SQLFetch(stmt);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2008-10-23 15:28:43 +00:00
|
|
|
if (res == SQL_NO_DATA) {
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn_num, query->readhandle[dsn_num], ast_str_buffer(sql));
|
2008-10-23 15:28:43 +00:00
|
|
|
break;
|
|
|
|
} else {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql));
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
for (;;) {
|
|
|
|
for (x = 0; x < colcount; x++) {
|
2011-03-10 05:54:53 +00:00
|
|
|
maxcol = 0;
|
|
|
|
|
2008-10-23 15:28:43 +00:00
|
|
|
res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, &maxcol, NULL, NULL);
|
|
|
|
if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) {
|
|
|
|
snprintf(colname, sizeof(colname), "field%d", x);
|
|
|
|
}
|
|
|
|
|
2009-01-19 21:42:46 +00:00
|
|
|
res = ast_odbc_ast_str_SQLGetData(&coldata, maxcol, stmt, x + 1, SQL_CHAR, &indicator);
|
2008-10-23 15:28:43 +00:00
|
|
|
if (indicator == SQL_NULL_DATA) {
|
|
|
|
ast_str_set(&coldata, 0, "(nil)");
|
|
|
|
res = SQL_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "SQL Get Data error %d!\n[%s]\n\n", res, ast_str_buffer(sql));
|
2008-10-22 22:11:31 +00:00
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2008-10-23 15:28:43 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
2008-10-23 15:28:43 +00:00
|
|
|
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "%-20.20s %s\n", colname, ast_str_buffer(coldata));
|
2008-10-23 15:28:43 +00:00
|
|
|
}
|
2009-02-17 18:49:20 +00:00
|
|
|
rows++;
|
|
|
|
|
2008-10-23 15:28:43 +00:00
|
|
|
/* Get next row */
|
|
|
|
res = SQLFetch(stmt);
|
|
|
|
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
|
|
|
break;
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
2008-10-23 15:28:43 +00:00
|
|
|
ast_cli(a->fd, "%-20.20s %s\n", "----------", "----------");
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
2008-10-23 15:28:43 +00:00
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn_num, query->readhandle[dsn_num]);
|
2008-10-23 15:28:43 +00:00
|
|
|
break;
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2008-10-22 22:11:31 +00:00
|
|
|
|
|
|
|
if (!executed) {
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "Failed to execute query. [%s]\n", ast_str_buffer(sql));
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
2008-10-23 15:28:43 +00:00
|
|
|
} else { /* No execution, just print out the resulting SQL */
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "%s\n", ast_str_buffer(sql));
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *cli_odbc_write(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
AST_DECLARE_APP_ARGS(values,
|
|
|
|
AST_APP_ARG(field)[100];
|
|
|
|
);
|
|
|
|
AST_DECLARE_APP_ARGS(args,
|
|
|
|
AST_APP_ARG(field)[100];
|
|
|
|
);
|
2008-12-09 14:49:30 +00:00
|
|
|
struct ast_str *sql;
|
2008-10-22 22:11:31 +00:00
|
|
|
char *char_args, *char_values, varname[10];
|
|
|
|
struct acf_odbc_query *query;
|
|
|
|
struct ast_channel *chan;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "odbc write";
|
|
|
|
e->usage =
|
|
|
|
"Usage: odbc write <name> <args> <value> [exec]\n"
|
|
|
|
" Evaluates the SQL provided in the ODBC function <name>, and\n"
|
|
|
|
" optionally executes the function. This function is intended for\n"
|
|
|
|
" testing purposes. Remember to quote arguments containing spaces.\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
if (a->pos == 2) {
|
|
|
|
int wordlen = strlen(a->word), which = 0;
|
|
|
|
/* Complete function name */
|
|
|
|
AST_RWLIST_RDLOCK(&queries);
|
|
|
|
AST_RWLIST_TRAVERSE(&queries, query, list) {
|
|
|
|
if (!strncasecmp(query->acf->name, a->word, wordlen)) {
|
|
|
|
if (++which > a->n) {
|
|
|
|
char *res = ast_strdup(query->acf->name);
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return NULL;
|
|
|
|
} else if (a->pos == 5) {
|
2017-02-11 15:57:03 +00:00
|
|
|
static const char * const completions[] = { "exec", NULL };
|
|
|
|
return ast_cli_complete(a->word, completions, a->n);
|
2008-10-22 22:11:31 +00:00
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->argc < 5 || a->argc > 6) {
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
}
|
|
|
|
|
2008-12-09 14:49:30 +00:00
|
|
|
sql = ast_str_thread_get(&sql_buf, 16);
|
|
|
|
if (!sql) {
|
|
|
|
return CLI_FAILURE;
|
|
|
|
}
|
|
|
|
|
2008-10-22 22:11:31 +00:00
|
|
|
AST_RWLIST_RDLOCK(&queries);
|
|
|
|
AST_RWLIST_TRAVERSE(&queries, query, list) {
|
|
|
|
if (!strcmp(query->acf->name, a->argv[2])) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query) {
|
|
|
|
ast_cli(a->fd, "No such query '%s'\n", a->argv[2]);
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
if (!query->sql_write) {
|
2008-10-22 22:11:31 +00:00
|
|
|
ast_cli(a->fd, "The function %s has no writesql parameter.\n", a->argv[2]);
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2014-06-03 07:36:42 +00:00
|
|
|
/* FIXME: The code below duplicates code found in acf_odbc_write but
|
|
|
|
* lacks the newer sql_insert additions. */
|
|
|
|
|
2008-10-22 22:11:31 +00:00
|
|
|
ast_str_make_space(&sql, strlen(query->sql_write) * 2 + 300);
|
|
|
|
|
|
|
|
/* Evaluate function */
|
|
|
|
char_args = ast_strdupa(a->argv[3]);
|
|
|
|
char_values = ast_strdupa(a->argv[4]);
|
|
|
|
|
2009-06-26 15:28:53 +00:00
|
|
|
chan = ast_dummy_channel_alloc();
|
2011-09-26 19:40:12 +00:00
|
|
|
if (!chan) {
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_FAILURE;
|
|
|
|
}
|
2008-10-22 22:11:31 +00:00
|
|
|
|
|
|
|
AST_STANDARD_APP_ARGS(args, char_args);
|
|
|
|
for (i = 0; i < args.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "ARG%d", i + 1);
|
|
|
|
pbx_builtin_pushvar_helper(chan, varname, args.field[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse values, just like arguments */
|
|
|
|
AST_STANDARD_APP_ARGS(values, char_values);
|
|
|
|
for (i = 0; i < values.argc; i++) {
|
|
|
|
snprintf(varname, sizeof(varname), "VAL%d", i + 1);
|
|
|
|
pbx_builtin_pushvar_helper(chan, varname, values.field[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Additionally set the value as a whole (but push an empty string if value is NULL) */
|
|
|
|
pbx_builtin_pushvar_helper(chan, "VALUE", S_OR(a->argv[4], ""));
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_str_substitute_variables(&sql, 0, chan, query->sql_write);
|
|
|
|
ast_debug(1, "SQL is %s\n", ast_str_buffer(sql));
|
2011-09-26 19:40:12 +00:00
|
|
|
|
|
|
|
chan = ast_channel_unref(chan);
|
2008-10-22 22:11:31 +00:00
|
|
|
|
|
|
|
if (a->argc == 6 && !strcmp(a->argv[5], "exec")) {
|
|
|
|
/* Execute the query */
|
2016-05-12 20:18:22 +00:00
|
|
|
struct odbc_obj *obj = NULL;
|
|
|
|
struct dsn *dsn = NULL;
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
int dsn_num, executed = 0;
|
2008-10-22 22:11:31 +00:00
|
|
|
SQLHSTMT stmt;
|
|
|
|
SQLLEN rows = -1;
|
|
|
|
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
for (dsn_num = 0; dsn_num < 5; dsn_num++) {
|
|
|
|
if (ast_strlen_zero(query->writehandle[dsn_num])) {
|
2008-10-23 15:28:43 +00:00
|
|
|
continue;
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
obj = get_odbc_obj(query->writehandle[dsn_num], &dsn);
|
|
|
|
if (!obj) {
|
2008-10-23 15:28:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) {
|
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
2008-10-23 15:28:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
SQLRowCount(stmt, &rows);
|
|
|
|
SQLCloseCursor(stmt);
|
|
|
|
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
2016-05-12 20:18:22 +00:00
|
|
|
release_obj_or_dsn (&obj, &dsn);
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn_num, query->writehandle[dsn_num]);
|
2008-10-23 15:28:43 +00:00
|
|
|
executed = 1;
|
|
|
|
break;
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!executed) {
|
|
|
|
ast_cli(a->fd, "Failed to execute query.\n");
|
|
|
|
}
|
2008-10-23 15:28:43 +00:00
|
|
|
} else { /* No execution, just print out the resulting SQL */
|
2008-12-13 08:36:35 +00:00
|
|
|
ast_cli(a->fd, "%s\n", ast_str_buffer(sql));
|
2008-10-22 22:11:31 +00:00
|
|
|
}
|
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ast_cli_entry cli_func_odbc[] = {
|
|
|
|
AST_CLI_DEFINE(cli_odbc_write, "Test setting a func_odbc function"),
|
|
|
|
AST_CLI_DEFINE(cli_odbc_read, "Test reading a func_odbc function"),
|
|
|
|
};
|
|
|
|
|
2006-12-28 20:13:00 +00:00
|
|
|
static int load_module(void)
|
2005-12-21 18:43:10 +00:00
|
|
|
{
|
|
|
|
int res = 0;
|
|
|
|
struct ast_config *cfg;
|
|
|
|
char *catg;
|
2016-05-12 20:18:22 +00:00
|
|
|
const char *s;
|
2007-08-16 21:09:46 +00:00
|
|
|
struct ast_flags config_flags = { 0 };
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2007-05-31 15:05:56 +00:00
|
|
|
res |= ast_custom_function_register(&fetch_function);
|
2008-11-01 21:10:07 +00:00
|
|
|
res |= ast_register_application_xml(app_odbcfinish, exec_odbcfinish);
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2007-08-16 21:09:46 +00:00
|
|
|
cfg = ast_config_load(config, config_flags);
|
2008-09-12 23:30:03 +00:00
|
|
|
if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
|
2006-05-26 03:08:15 +00:00
|
|
|
ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
|
2006-08-31 21:00:20 +00:00
|
|
|
return AST_MODULE_LOAD_DECLINE;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
ast_rwlock_wrlock(&single_db_connection_lock);
|
|
|
|
if ((s = ast_variable_retrieve(cfg, "general", "single_db_connection"))) {
|
|
|
|
single_db_connection = ast_true(s);
|
|
|
|
} else {
|
|
|
|
single_db_connection = DEFAULT_SINGLE_DB_CONNECTION;
|
|
|
|
}
|
|
|
|
|
|
|
|
dsns = NULL;
|
|
|
|
|
|
|
|
if (single_db_connection) {
|
|
|
|
dsns = ao2_container_alloc(DSN_BUCKETS, dsn_hash, dsn_cmp);
|
|
|
|
if (!dsns) {
|
|
|
|
ast_log(LOG_ERROR, "Could not initialize DSN container\n");
|
|
|
|
ast_rwlock_unlock(&single_db_connection_lock);
|
|
|
|
return AST_MODULE_LOAD_DECLINE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ast_rwlock_unlock(&single_db_connection_lock);
|
|
|
|
|
|
|
|
AST_RWLIST_WRLOCK(&queries);
|
2005-12-21 18:43:10 +00:00
|
|
|
for (catg = ast_category_browse(cfg, NULL);
|
2006-02-11 03:14:05 +00:00
|
|
|
catg;
|
|
|
|
catg = ast_category_browse(cfg, catg)) {
|
2006-05-06 13:36:29 +00:00
|
|
|
struct acf_odbc_query *query = NULL;
|
2006-12-28 20:13:00 +00:00
|
|
|
int err;
|
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!strcasecmp(catg, "general")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2006-12-28 20:13:00 +00:00
|
|
|
if ((err = init_acf_query(cfg, catg, &query))) {
|
|
|
|
if (err == ENOMEM)
|
|
|
|
ast_log(LOG_ERROR, "Out of memory\n");
|
|
|
|
else if (err == EINVAL)
|
|
|
|
ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg);
|
|
|
|
else
|
|
|
|
ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err);
|
2005-12-21 18:43:10 +00:00
|
|
|
} else {
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_INSERT_HEAD(&queries, query, list);
|
2005-12-21 18:43:10 +00:00
|
|
|
ast_custom_function_register(query->acf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_config_destroy(cfg);
|
2006-12-28 20:13:00 +00:00
|
|
|
res |= ast_custom_function_register(&escape_function);
|
2008-10-22 22:11:31 +00:00
|
|
|
ast_cli_register_multiple(cli_func_odbc, ARRAY_LEN(cli_func_odbc));
|
2006-05-06 13:36:29 +00:00
|
|
|
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2005-12-21 18:43:10 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2006-12-28 20:13:00 +00:00
|
|
|
static int unload_module(void)
|
2005-12-21 18:43:10 +00:00
|
|
|
{
|
2006-05-06 13:36:29 +00:00
|
|
|
struct acf_odbc_query *query;
|
2006-12-28 20:13:00 +00:00
|
|
|
int res = 0;
|
2006-05-06 13:36:29 +00:00
|
|
|
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_WRLOCK(&queries);
|
|
|
|
while (!AST_RWLIST_EMPTY(&queries)) {
|
|
|
|
query = AST_RWLIST_REMOVE_HEAD(&queries, list);
|
2006-05-06 13:36:29 +00:00
|
|
|
ast_custom_function_unregister(query->acf);
|
|
|
|
free_acf_query(query);
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2006-12-28 20:13:00 +00:00
|
|
|
res |= ast_custom_function_unregister(&escape_function);
|
2007-05-31 15:05:56 +00:00
|
|
|
res |= ast_custom_function_unregister(&fetch_function);
|
|
|
|
res |= ast_unregister_application(app_odbcfinish);
|
2008-10-22 22:11:31 +00:00
|
|
|
ast_cli_unregister_multiple(cli_func_odbc, ARRAY_LEN(cli_func_odbc));
|
2005-12-27 02:02:23 +00:00
|
|
|
|
2006-05-06 13:36:29 +00:00
|
|
|
/* Allow any threads waiting for this lock to pass (avoids a race) */
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2006-12-28 20:13:00 +00:00
|
|
|
usleep(1);
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_WRLOCK(&queries);
|
2006-05-06 13:36:29 +00:00
|
|
|
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
func_odbc: Use one connection per DSN.
res_odbc was changed in Asterisk 13.8.0 to remove connection management,
opting instead to let unixodbc maintain open connections and return
those to Asterisk as requested.
This was a boon for realtime, since it meant that multiple threads could
potentially run parallel queries since they could each be using their
own database connections.
However, on the user-facing side, func_odbc, there were some inherent
behaviors being relied on that no longer hold true after the change.
One such reported behavior was that MySQL's LAST_INSERTED_ID() works
per-connection. This means that if Asterisk uses separate connections
for every database operation, whereas before it used one connection for
everything, we have broken expectations and functionality.
The fix provided in this patch is to make func_odbc use a single
database connection per DSN. This way, user-facing database usage will
have the same behavior as it did pre-13.8.0. However, realtime, which is
the real workhorse of database interaction, will continue to let
unixodbc manage connections.
ASTERISK-25938 #close
Reported by Edwin Vandamme
Change-Id: Iac961fe79154c6211569afcdfec843c0c24c46dc
2016-04-22 18:49:50 +00:00
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (dsns) {
|
|
|
|
ao2_ref(dsns, -1);
|
|
|
|
}
|
2012-02-08 20:49:48 +00:00
|
|
|
return res;
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
2006-08-21 02:11:39 +00:00
|
|
|
static int reload(void)
|
2005-12-21 18:43:10 +00:00
|
|
|
{
|
|
|
|
int res = 0;
|
|
|
|
struct ast_config *cfg;
|
2006-05-06 13:36:29 +00:00
|
|
|
struct acf_odbc_query *oldquery;
|
2005-12-21 18:43:10 +00:00
|
|
|
char *catg;
|
2016-05-12 20:18:22 +00:00
|
|
|
const char *s;
|
2007-08-16 21:09:46 +00:00
|
|
|
struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
|
|
|
|
|
|
|
|
cfg = ast_config_load(config, config_flags);
|
2008-09-12 23:30:03 +00:00
|
|
|
if (cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID)
|
2007-08-16 21:09:46 +00:00
|
|
|
return 0;
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
ast_rwlock_wrlock(&single_db_connection_lock);
|
|
|
|
|
|
|
|
if (dsns) {
|
|
|
|
ao2_ref(dsns, -1);
|
|
|
|
dsns = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg && (s = ast_variable_retrieve(cfg, "general", "single_db_connection"))) {
|
|
|
|
single_db_connection = ast_true(s);
|
|
|
|
} else {
|
|
|
|
single_db_connection = DEFAULT_SINGLE_DB_CONNECTION;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (single_db_connection) {
|
|
|
|
dsns = ao2_container_alloc(DSN_BUCKETS, dsn_hash, dsn_cmp);
|
|
|
|
if (!dsns) {
|
|
|
|
ast_log(LOG_ERROR, "Could not initialize DSN container\n");
|
|
|
|
ast_rwlock_unlock(&single_db_connection_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ast_rwlock_unlock(&single_db_connection_lock);
|
|
|
|
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_WRLOCK(&queries);
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2008-08-06 14:51:51 +00:00
|
|
|
while (!AST_RWLIST_EMPTY(&queries)) {
|
|
|
|
oldquery = AST_RWLIST_REMOVE_HEAD(&queries, list);
|
2006-05-06 13:36:29 +00:00
|
|
|
ast_custom_function_unregister(oldquery->acf);
|
|
|
|
free_acf_query(oldquery);
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!cfg) {
|
|
|
|
ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config);
|
|
|
|
goto reload_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (catg = ast_category_browse(cfg, NULL);
|
2006-02-11 03:14:05 +00:00
|
|
|
catg;
|
|
|
|
catg = ast_category_browse(cfg, catg)) {
|
2005-12-21 18:43:10 +00:00
|
|
|
struct acf_odbc_query *query = NULL;
|
|
|
|
|
2016-05-12 20:18:22 +00:00
|
|
|
if (!strcasecmp(catg, "general")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2005-12-21 18:43:10 +00:00
|
|
|
if (init_acf_query(cfg, catg, &query)) {
|
2006-05-06 13:36:29 +00:00
|
|
|
ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg);
|
2005-12-21 18:43:10 +00:00
|
|
|
} else {
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_INSERT_HEAD(&queries, query, list);
|
2006-05-06 13:36:29 +00:00
|
|
|
ast_custom_function_register(query->acf);
|
2005-12-21 18:43:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_config_destroy(cfg);
|
|
|
|
reload_out:
|
2008-08-06 14:51:51 +00:00
|
|
|
AST_RWLIST_UNLOCK(&queries);
|
2005-12-21 18:43:10 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2006-04-14 14:08:19 +00:00
|
|
|
/* XXX need to revise usecount - set if query_lock is set */
|
2005-12-21 18:43:10 +00:00
|
|
|
|
2006-08-21 02:11:39 +00:00
|
|
|
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ODBC lookups",
|
2015-05-06 00:49:04 +00:00
|
|
|
.support_level = AST_MODULE_SUPPORT_CORE,
|
|
|
|
.load = load_module,
|
|
|
|
.unload = unload_module,
|
|
|
|
.reload = reload,
|
|
|
|
);
|
2006-04-24 17:11:45 +00:00
|
|
|
|