[asterisk-commits] tilghman: trunk r120789 - in /trunk: apps/ channels/ configs/ contrib/scripts...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Thu Jun 5 14:07:27 CDT 2008
Author: tilghman
Date: Thu Jun 5 14:07:27 2008
New Revision: 120789
URL: http://svn.digium.com/view/asterisk?view=rev&rev=120789
Log:
Merge the adaptive realtime branch, which will make adding new required fields
to realtime less painful in the future.
Modified:
trunk/apps/app_meetme.c
trunk/apps/app_queue.c
trunk/apps/app_voicemail.c
trunk/channels/chan_iax2.c
trunk/channels/chan_sip.c
trunk/configs/res_pgsql.conf.sample
trunk/contrib/scripts/dbsep.cgi
trunk/include/asterisk/config.h
trunk/include/asterisk/res_odbc.h
trunk/main/config.c
trunk/res/res_config_curl.c
trunk/res/res_config_odbc.c
trunk/res/res_config_pgsql.c
Modified: trunk/apps/app_meetme.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_meetme.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/apps/app_meetme.c (original)
+++ trunk/apps/app_meetme.c Thu Jun 5 14:07:27 2008
@@ -1743,6 +1743,7 @@
if (rt_log_members) {
/* Update table */
snprintf(members, sizeof(members), "%d", conf->users);
+ ast_realtime_require_field("meetme", "confno", RQ_INTEGER, strlen(conf->confno), "members", RQ_INTEGER, strlen(members), NULL);
ast_update_realtime("meetme", "confno", conf->confno, "members", members, NULL);
}
setusercount = 1;
@@ -2682,6 +2683,7 @@
if (rt_log_members) {
/* Update table */
snprintf(members, sizeof(members), "%d", conf->users);
+ ast_realtime_require_field("meetme", "confno", RQ_INTEGER, strlen(conf->confno), "members", RQ_INTEGER, strlen(members), NULL);
ast_update_realtime("meetme", "confno", conf->confno, "members", members, NULL);
}
if (confflags & CONFFLAG_MARKEDUSER)
@@ -5677,6 +5679,7 @@
sla_destroy();
res |= ast_custom_function_unregister(&meetme_info_acf);
+ ast_unload_realtime("meetme");
return res;
}
@@ -5707,12 +5710,14 @@
res |= ast_devstate_prov_add("SLA", sla_state);
res |= ast_custom_function_register(&meetme_info_acf);
+ ast_realtime_require_field("meetme", "confno", RQ_INTEGER, 3, "members", RQ_INTEGER, 3, NULL);
return res;
}
static int reload(void)
{
+ ast_unload_realtime("meetme");
return load_config(1);
}
Modified: trunk/apps/app_queue.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_queue.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/apps/app_queue.c (original)
+++ trunk/apps/app_queue.c Thu Jun 5 14:07:27 2008
@@ -6284,6 +6284,7 @@
}
ao2_ref(queues, -1);
devicestate_tps = ast_taskprocessor_unreference(devicestate_tps);
+ ast_unload_realtime("queue_members");
return res;
}
@@ -6335,12 +6336,14 @@
if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, device_state_cb, NULL, AST_EVENT_IE_END)))
res = -1;
+ ast_realtime_require_field("queue_members", "paused", RQ_INTEGER, 1, "uniqueid", RQ_INTEGER, 5, NULL);
return res ? AST_MODULE_LOAD_DECLINE : 0;
}
static int reload(void)
{
+ ast_unload_realtime("queue_members");
reload_queues(1);
return 0;
}
Modified: trunk/apps/app_voicemail.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_voicemail.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/apps/app_voicemail.c (original)
+++ trunk/apps/app_voicemail.c Thu Jun 5 14:07:27 2008
@@ -928,6 +928,9 @@
{
int res;
if (!ast_strlen_zero(vmu->uniqueid)) {
+ if (strlen(password) > 10) {
+ ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), NULL);
+ }
res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
if (res > 0) {
ast_copy_string(vmu->password, password, sizeof(vmu->password));
@@ -9367,6 +9370,9 @@
int tmpadsi[4];
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ ast_unload_realtime("voicemail");
+ ast_unload_realtime("voicemail_data");
+
if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
return 0;
@@ -10050,6 +10056,8 @@
stop_poll_thread();
mwi_subscription_tps = ast_taskprocessor_unreference(mwi_subscription_tps);
+ ast_unload_realtime("voicemail");
+ ast_unload_realtime("voicemail_data");
return res;
}
@@ -10081,6 +10089,8 @@
ast_cli_register_multiple(cli_voicemail, sizeof(cli_voicemail) / sizeof(struct ast_cli_entry));
ast_install_vm_functions(has_voicemail, inboxcount, messagecount, sayname);
+ ast_realtime_require_field("voicemail", "uniqueid", RQ_INTEGER, 11, "password", RQ_CHAR, 10, NULL);
+ ast_realtime_require_field("voicemail_data", "filename", RQ_CHAR, 30, "duration", RQ_INTEGER, 5, NULL);
return res;
}
Modified: trunk/channels/chan_iax2.c
URL: http://svn.digium.com/view/asterisk/trunk/channels/chan_iax2.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/channels/chan_iax2.c (original)
+++ trunk/channels/chan_iax2.c Thu Jun 5 14:07:27 2008
@@ -11284,6 +11284,7 @@
reload_firmware(0);
iax_provision_reload(1);
+ ast_unload_realtime("iaxpeers");
return 0;
}
@@ -12034,7 +12035,7 @@
con = ast_context_find(regcontext);
if (con)
ast_context_destroy(con, "IAX2");
-
+ ast_unload_realtime("iaxpeers");
return 0;
}
@@ -12187,6 +12188,8 @@
reload_firmware(0);
iax_provision_reload(0);
+ ast_realtime_require_field("iaxpeers", "name", RQ_CHAR, 10, "ipaddr", RQ_CHAR, 15, "port", RQ_INTEGER, 5, "regseconds", RQ_INTEGER, 6, NULL);
+
return AST_MODULE_LOAD_SUCCESS;
}
Modified: trunk/channels/chan_sip.c
URL: http://svn.digium.com/view/asterisk/trunk/channels/chan_sip.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/channels/chan_sip.c (original)
+++ trunk/channels/chan_sip.c Thu Jun 5 14:07:27 2008
@@ -21334,7 +21334,8 @@
time_t run_start, run_end;
run_start = time(0);
-
+ ast_unload_realtime("sipregs");
+ ast_unload_realtime("sippeers");
cfg = ast_config_load(config, config_flags);
/* We *must* have a config file otherwise stop immediately */
@@ -22766,6 +22767,16 @@
/* And start the monitor for the first time */
restart_monitor();
+ ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers",
+ "name", RQ_CHAR, 10,
+ "ipaddr", RQ_CHAR, 15,
+ "port", RQ_INTEGER, 5,
+ "regseconds", RQ_INTEGER, 5,
+ "defaultuser", RQ_CHAR, 10,
+ "fullcontact", RQ_CHAR, 20,
+ "regserver", RQ_CHAR, 20,
+ NULL);
+
return AST_MODULE_LOAD_SUCCESS;
}
@@ -22879,6 +22890,8 @@
con = ast_context_find(used_context);
if (con)
ast_context_destroy(con, "SIP");
+ ast_unload_realtime("sipregs");
+ ast_unload_realtime("sippeers");
return 0;
}
Modified: trunk/configs/res_pgsql.conf.sample
URL: http://svn.digium.com/view/asterisk/trunk/configs/res_pgsql.conf.sample?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/configs/res_pgsql.conf.sample (original)
+++ trunk/configs/res_pgsql.conf.sample Thu Jun 5 14:07:27 2008
@@ -12,3 +12,11 @@
dbname=asterisk
dbuser=asterisk
dbpass=password
+;
+; requirements - At startup, each realtime family will make requirements
+; on the backend. There are several strategies for handling requirements:
+; warn - Warn if the required column does not exist.
+; createclose - Create columns as close to the requirements as possible.
+; createchar - Create char columns only
+;
+requirements=warn
Modified: trunk/contrib/scripts/dbsep.cgi
URL: http://svn.digium.com/view/asterisk/trunk/contrib/scripts/dbsep.cgi?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/contrib/scripts/dbsep.cgi (original)
+++ trunk/contrib/scripts/dbsep.cgi Thu Jun 5 14:07:27 2008
@@ -28,6 +28,7 @@
# dsn=<some valid dsn>
# dbuser=<user>
# dbpass=<passwd>
+# dbschema=<dbname>
# backslash_is_escape={yes|no}
#
open CFG, "</etc/asterisk/dbsep.conf";
@@ -120,6 +121,43 @@
$affected = $dbh->do($sql);
$dbh->disconnect();
print "Content-type: text/html\n\n$affected\n";
+} elsif ($ENV{PATH_INFO} =~ m/require$/) {
+ my $result = 0;
+ my $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
+ my $sql = "SELECT data_type, character_maximum_length FROM information_schema.tables AS t " .
+ "JOIN information_schema.columns AS c " .
+ "ON t.table_catalog=c.table_catalog AND " .
+ "t.table_schema=c.table_schema AND " .
+ "t.table_name=c.table_name " .
+ "WHERE c.table_schema='$cfg{dbschema}' AND " .
+ "c.table_name=? AND c.column_name=?";
+ my $sth = $dbh->prepare($sql);
+ foreach my $param (cgi_to_where_clause($cgi, \%cfg)) {
+ my ($colname, $value) = split /=/, $param;
+ my ($type, $size) = split /:/, $value;
+ $sth->execute($table, $colname);
+ my ($dbtype, $dblen) = $sth->fetchrow_array();
+ $sth->finish();
+ if ($type eq 'char') {
+ if ($dbtype !~ m#char#i) {
+ print STDERR "REQUIRE: $table: Type of column $colname requires char($size), but column is of type $dbtype instead!\n";
+ $result = -1;
+ } elsif ($dblen < $size) {
+ print STDERR "REQUIRE: $table: Size of column $colname requires $size, but column is only $dblen long!\n";
+ $result = -1;
+ }
+ } elsif ($type eq 'integer') {
+ if ($dbtype =~ m#char#i and $dblen < $size) {
+ print STDERR "REQUIRE: $table: Size of column $colname requires $size, but column is only $dblen long!\n";
+ $result = -1;
+ } elsif ($dbtype !~ m#int|float|double|dec|num#i) {
+ print STDERR "REQUIRE: $table: Type of column $colname requires integer($size), but column is of type $dbtype instead!\n";
+ $result = -1;
+ }
+ } # TODO More type checks
+ }
+ $dbh->disconnect();
+ print "Content-type: text/html\n\n$result\n";
} elsif ($ENV{PATH_INFO} =~ m/static$/) {
# file parameter in GET, no POST
my (@get, $filename, $sql, $sth);
Modified: trunk/include/asterisk/config.h
URL: http://svn.digium.com/view/asterisk/trunk/include/asterisk/config.h?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/include/asterisk/config.h (original)
+++ trunk/include/asterisk/config.h Thu Jun 5 14:07:27 2008
@@ -46,6 +46,17 @@
#define CONFIG_STATUS_FILEUNCHANGED (void *)-1
+/*!
+ * \brief Types used in ast_realtime_require_field
+ */
+typedef enum {
+ RQ_INTEGER,
+ RQ_CHAR,
+ RQ_FLOAT,
+ RQ_DATE,
+ RQ_DATETIME,
+} require_type;
+
/*! \brief Structure for variables, used for configurations and for channel variables
*/
struct ast_variable {
@@ -70,6 +81,8 @@
typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
typedef int realtime_store(const char *database, const char *table, va_list ap);
typedef int realtime_destroy(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
+typedef int realtime_require(const char *database, const char *table, va_list ap);
+typedef int realtime_unload(const char *database, const char *table);
/*! \brief Configuration engine structure, used to define realtime drivers */
struct ast_config_engine {
@@ -80,6 +93,8 @@
realtime_update *update_func;
realtime_store *store_func;
realtime_destroy *destroy_func;
+ realtime_require *require_func;
+ realtime_unload *unload_func;
struct ast_config_engine *next;
};
@@ -185,6 +200,26 @@
struct ast_variable *ast_load_realtime(const char *family, ...) attribute_sentinel;
struct ast_variable *ast_load_realtime_all(const char *family, ...) attribute_sentinel;
+/*!
+ * \brief Release any resources cached for a realtime family
+ * \param family which family/config to destroy
+ * Various backends may cache attributes about a realtime data storage
+ * facility; on reload, a front end resource may request to purge that cache.
+ */
+int ast_unload_realtime(const char *family);
+
+/*!
+ * \brief Inform realtime what fields that may be stored
+ * \param family which family/config is referenced
+ * This will inform builtin configuration backends that particular fields
+ * may be updated during the use of that configuration section. This is
+ * mainly to be used during startup routines, to ensure that various fields
+ * exist in the backend. The backends may take various actions, such as
+ * creating new fields in the data store or warning the administrator that
+ * new fields may need to be created, in order to ensure proper function.
+ */
+int ast_require_realtime_fields(const char *family, ...) attribute_sentinel;
+
/*!
* \brief Retrieve realtime configuration
* \param family which family/config to lookup
@@ -231,6 +266,8 @@
* \return 1 if family is configured in realtime and engine exists
*/
int ast_check_realtime(const char *family);
+
+int ast_realtime_require_field(const char *family, ...) attribute_sentinel;
/*! \brief Check if there's any realtime engines loaded */
int ast_realtime_enabled(void);
Modified: trunk/include/asterisk/res_odbc.h
URL: http://svn.digium.com/view/asterisk/trunk/include/asterisk/res_odbc.h?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/include/asterisk/res_odbc.h (original)
+++ trunk/include/asterisk/res_odbc.h Thu Jun 5 14:07:27 2008
@@ -30,6 +30,7 @@
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
+#include "asterisk/linkedlists.h"
typedef enum { ODBC_SUCCESS=0, ODBC_FAIL=-1} odbc_status;
@@ -42,6 +43,27 @@
unsigned int used:1;
unsigned int up:1;
AST_LIST_ENTRY(odbc_obj) list;
+};
+
+/*!\brief These aren't used in any API calls, but they are kept in a common
+ * location, simply for convenience and to avoid duplication.
+ */
+struct odbc_cache_columns {
+ char *name;
+ SQLSMALLINT type;
+ SQLINTEGER size;
+ SQLSMALLINT decimals;
+ SQLSMALLINT radix;
+ SQLSMALLINT nullable;
+ SQLINTEGER octetlen;
+ AST_RWLIST_ENTRY(odbc_cache_columns) list;
+};
+
+struct odbc_cache_tables {
+ char *connection;
+ char *table;
+ AST_RWLIST_HEAD(_columns, odbc_cache_columns) columns;
+ AST_RWLIST_ENTRY(odbc_cache_tables) list;
};
/* functions */
Modified: trunk/main/config.c
URL: http://svn.digium.com/view/asterisk/trunk/main/config.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/main/config.c (original)
+++ trunk/main/config.c Thu Jun 5 14:07:27 2008
@@ -2101,6 +2101,38 @@
return config_maps ? 1 : 0;
}
+int ast_realtime_require_field(const char *family, ...)
+{
+ struct ast_config_engine *eng;
+ char db[256] = "";
+ char table[256] = "";
+ va_list ap;
+ int res = -1;
+
+ va_start(ap, family);
+ eng = find_engine(family, db, sizeof(db), table, sizeof(table));
+ if (eng && eng->require_func) {
+ res = eng->require_func(db, table, ap);
+ }
+ va_end(ap);
+
+ return res;
+}
+
+int ast_unload_realtime(const char *family)
+{
+ struct ast_config_engine *eng;
+ char db[256] = "";
+ char table[256] = "";
+ int res = -1;
+
+ eng = find_engine(family, db, sizeof(db), table, sizeof(table));
+ if (eng && eng->unload_func) {
+ res = eng->unload_func(db, table);
+ }
+ return res;
+}
+
struct ast_config *ast_load_realtime_multientry(const char *family, ...)
{
struct ast_config_engine *eng;
Modified: trunk/res/res_config_curl.c
URL: http://svn.digium.com/view/asterisk/trunk/res/res_config_curl.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/res/res_config_curl.c (original)
+++ trunk/res/res_config_curl.c Thu Jun 5 14:07:27 2008
@@ -406,6 +406,42 @@
return -1;
}
+static int require_curl(const char *url, const char *unused, va_list ap)
+{
+ struct ast_str *query;
+ char *elm, field[256], buffer[128];
+ int type, size;
+ const int EncodeSpecialChars = 1;
+
+ if (!ast_custom_function_find("CURL")) {
+ ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
+ return -1;
+ }
+
+ if (!(query = ast_str_create(100))) {
+ return -1;
+ }
+
+ ast_str_set(&query, 0, "${CURL(%s/require,", url);
+
+ while ((elm = va_arg(ap, char *))) {
+ type = va_arg(ap, require_type);
+ size = va_arg(ap, int);
+ ast_uri_encode(elm, field, sizeof(field), EncodeSpecialChars);
+ ast_str_append(&query, 0, "%s=%s%%3A%d", field,
+ type == RQ_CHAR ? "char" :
+ type == RQ_INTEGER ? "integer" :
+ type == RQ_DATE ? "date" :
+ type == RQ_DATETIME ? "datetime" :
+ type == RQ_FLOAT ? "float" :
+ "unknown", size);
+ }
+ va_end(ap);
+
+ ast_str_append(&query, 0, ")}");
+ pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer));
+ return atoi(buffer);
+}
static struct ast_config *config_curl(const char *url, const char *unused, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl, const char *who_asked)
{
@@ -489,7 +525,8 @@
.realtime_multi_func = realtime_multi_curl,
.store_func = store_curl,
.destroy_func = destroy_curl,
- .update_func = update_curl
+ .update_func = update_curl,
+ .require_func = require_curl,
};
static int unload_module (void)
Modified: trunk/res/res_config_odbc.c
URL: http://svn.digium.com/view/asterisk/trunk/res/res_config_odbc.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/res/res_config_odbc.c (original)
+++ trunk/res/res_config_odbc.c Thu Jun 5 14:07:27 2008
@@ -51,11 +51,159 @@
const char *sql;
const char *extra;
va_list ap;
+ unsigned long long skip;
};
+/*!\brief The structures referenced are in include/asterisk/res_odbc.h */
+static AST_RWLIST_HEAD_STATIC(odbc_tables, odbc_cache_tables);
+
+static void destroy_table_cache(struct odbc_cache_tables *table) {
+ struct odbc_cache_columns *col;
+ ast_debug(1, "Destroying table cache for %s\n", table->table);
+ AST_RWLIST_WRLOCK(&table->columns);
+ while ((col = AST_RWLIST_REMOVE_HEAD(&table->columns, list))) {
+ ast_free(col);
+ }
+ AST_RWLIST_UNLOCK(&table->columns);
+ AST_RWLIST_HEAD_DESTROY(&table->columns);
+ ast_free(table);
+}
+
+#define release_table(ptr) if (ptr) { AST_RWLIST_UNLOCK(&(ptr)->columns); }
+
+/*!
+ * \brief Find or create an entry describing the table specified.
+ * \param obj An active ODBC handle on which to query the table
+ * \param table Tablename to describe
+ * \retval A structure describing the table layout, or NULL, if the table is not found or another error occurs.
+ * When a structure is returned, the contained columns list will be
+ * rdlock'ed, to ensure that it will be retained in memory.
+ */
+static struct odbc_cache_tables *find_table(const char *database, const char *tablename)
+{
+ struct odbc_cache_tables *tableptr;
+ struct odbc_cache_columns *entry;
+ char columnname[80];
+ SQLLEN sqlptr;
+ SQLHSTMT stmt = NULL;
+ int res = 0, error = 0, try = 0;
+ struct odbc_obj *obj = ast_odbc_request_obj(database, 0);
+
+ AST_RWLIST_RDLOCK(&odbc_tables);
+ AST_RWLIST_TRAVERSE(&odbc_tables, tableptr, list) {
+ if (strcmp(tableptr->connection, database) == 0 && strcmp(tableptr->table, tablename) == 0) {
+ break;
+ }
+ }
+ if (tableptr) {
+ AST_RWLIST_RDLOCK(&tableptr->columns);
+ AST_RWLIST_UNLOCK(&odbc_tables);
+ return tableptr;
+ }
+
+ if (!obj) {
+ ast_log(LOG_WARNING, "Unable to retrieve database handle for table description '%s@%s'\n", tablename, database);
+ return NULL;
+ }
+
+ /* Table structure not already cached; build it now. */
+ do {
+retry:
+ res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (try == 0) {
+ try = 1;
+ ast_odbc_sanity_check(obj);
+ goto retry;
+ }
+ ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", database);
+ break;
+ }
+
+ res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)tablename, SQL_NTS, (unsigned char *)"%", SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (try == 0) {
+ try = 1;
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_sanity_check(obj);
+ goto retry;
+ }
+ ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'.\n", database);
+ break;
+ }
+
+ if (!(tableptr = ast_calloc(sizeof(char), sizeof(*tableptr) + strlen(database) + 1 + strlen(tablename) + 1))) {
+ ast_log(LOG_ERROR, "Out of memory creating entry for table '%s' on connection '%s'\n", tablename, database);
+ break;
+ }
+
+ tableptr->connection = (char *)tableptr + sizeof(*tableptr);
+ tableptr->table = (char *)tableptr + sizeof(*tableptr) + strlen(database) + 1;
+ strcpy(tableptr->connection, database); /* SAFE */
+ strcpy(tableptr->table, tablename); /* SAFE */
+ AST_RWLIST_HEAD_INIT(&(tableptr->columns));
+
+ while ((res = SQLFetch(stmt)) != SQL_NO_DATA && res != SQL_ERROR) {
+ SQLGetData(stmt, 4, SQL_C_CHAR, columnname, sizeof(columnname), &sqlptr);
+
+ if (!(entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(columnname) + 1))) {
+ ast_log(LOG_ERROR, "Out of memory creating entry for column '%s' in table '%s' on connection '%s'\n", columnname, tablename, database);
+ error = 1;
+ break;
+ }
+ entry->name = (char *)entry + sizeof(*entry);
+ strcpy(entry->name, columnname);
+
+ SQLGetData(stmt, 5, SQL_C_SHORT, &entry->type, sizeof(entry->type), NULL);
+ SQLGetData(stmt, 7, SQL_C_LONG, &entry->size, sizeof(entry->size), NULL);
+ SQLGetData(stmt, 9, SQL_C_SHORT, &entry->decimals, sizeof(entry->decimals), NULL);
+ SQLGetData(stmt, 10, SQL_C_SHORT, &entry->radix, sizeof(entry->radix), NULL);
+ SQLGetData(stmt, 11, SQL_C_SHORT, &entry->nullable, sizeof(entry->nullable), NULL);
+ SQLGetData(stmt, 16, SQL_C_LONG, &entry->octetlen, sizeof(entry->octetlen), NULL);
+
+ /* Specification states that the octenlen should be the maximum number of bytes
+ * returned in a char or binary column, but it seems that some drivers just set
+ * it to NULL. (Bad Postgres! No biscuit!) */
+ if (entry->octetlen == 0) {
+ entry->octetlen = entry->size;
+ }
+
+ ast_verb(10, "Found %s column with type %hd with len %ld, octetlen %ld, and numlen (%hd,%hd)\n", entry->name, entry->type, (long) entry->size, (long) entry->octetlen, entry->decimals, entry->radix);
+ /* Insert column info into column list */
+ AST_LIST_INSERT_TAIL(&(tableptr->columns), entry, list);
+ }
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+ AST_RWLIST_INSERT_TAIL(&odbc_tables, tableptr, list);
+ AST_RWLIST_RDLOCK(&(tableptr->columns));
+ } while (0);
+
+ AST_RWLIST_UNLOCK(&odbc_tables);
+
+ if (error) {
+ destroy_table_cache(tableptr);
+ tableptr = NULL;
+ }
+ if (obj) {
+ ast_odbc_release_obj(obj);
+ }
+ return tableptr;
+}
+
+static struct odbc_cache_columns *find_column(struct odbc_cache_tables *table, const char *colname)
+{
+ struct odbc_cache_columns *col;
+ AST_RWLIST_TRAVERSE(&table->columns, col, list) {
+ if (strcasecmp(col->name, colname) == 0) {
+ return col;
+ }
+ }
+ return NULL;
+}
+
static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
{
- int res, x = 1;
+ int res, x = 1, count = 0;
struct custom_prepare_struct *cps = data;
const char *newparam, *newval;
SQLHSTMT stmt;
@@ -78,6 +226,9 @@
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
+ if ((1 << count) & cps->skip) {
+ continue;
+ }
SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
}
va_end(ap);
@@ -398,33 +549,51 @@
char sql[256];
SQLLEN rowcount=0;
const char *newparam, *newval;
- int res;
+ int res, count = 0;
va_list aq;
struct custom_prepare_struct cps = { .sql = sql, .extra = lookup };
+ struct odbc_cache_tables *tableptr = find_table(database, table);
+ struct odbc_cache_columns *column;
va_copy(cps.ap, ap);
va_copy(aq, ap);
- if (!table)
- return -1;
+ if (!table) {
+ release_table(tableptr);
+ return -1;
+ }
obj = ast_odbc_request_obj(database, 0);
- if (!obj)
- return -1;
+ if (!obj) {
+ release_table(tableptr);
+ return -1;
+ }
newparam = va_arg(aq, const char *);
if (!newparam) {
ast_odbc_release_obj(obj);
+ release_table(tableptr);
return -1;
}
newval = va_arg(aq, const char *);
+
+ if (tableptr && !(column = find_column(tableptr, newparam))) {
+ ast_log(LOG_WARNING, "Key field '%s' does not exist in table '%s@%s'. Update will fail\n", newparam, table, database);
+ }
+
snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam);
while((newparam = va_arg(aq, const char *))) {
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam);
- newval = va_arg(aq, const char *);
+ if ((tableptr && (column = find_column(tableptr, newparam))) || count > 63) {
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam);
+ newval = va_arg(aq, const char *);
+ } else { /* the column does not exist in the table OR we've exceeded the space in our flag field */
+ cps.skip |= (((long long)1) << count);
+ }
+ count++;
}
va_end(aq);
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
+ release_table(tableptr);
stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
@@ -709,6 +878,112 @@
return cfg;
}
+#define warn_length(col, size) ast_log(LOG_WARNING, "Column %s is not long enough to contain realtime data (needs %d)\n", col->name, size)
+#define warn_type(col, type) ast_log(LOG_WARNING, "Column %s is of the incorrect type to contain realtime data\n", col->name)
+
+static int require_odbc(const char *database, const char *table, va_list ap)
+{
+ struct odbc_cache_tables *tableptr = find_table(database, table);
+ struct odbc_cache_columns *col;
+ char *elm;
+ int type, size;
+
+ if (!tableptr) {
+ return -1;
+ }
+
+ while ((elm = va_arg(ap, char *))) {
+ type = va_arg(ap, require_type);
+ size = va_arg(ap, int);
+ /* Check if the field matches the criteria */
+ AST_RWLIST_TRAVERSE(&tableptr->columns, col, list) {
+ if (strcmp(col->name, elm) == 0) {
+ /* Type check, first. Some fields are more particular than others */
+ switch (col->type) {
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ case SQL_LONGVARBINARY:
+ case SQL_GUID:
+ if ((type == RQ_INTEGER && size > 10) || (type == RQ_CHAR && col->size < size)) {
+ warn_length(col, size);
+ } else if (type == RQ_DATE && col->size < 10) {
+ warn_length(col, 10);
+ } else if (type == RQ_DATETIME && col->size < 19) {
+ warn_length(col, 19);
+ } else if (type == RQ_FLOAT && col->size < 10) {
+ warn_length(col, 10);
+ }
+ break;
+ case SQL_TYPE_DATE:
+ if (type != RQ_DATE) {
+ warn_type(col, type);
+ }
+ break;
+ case SQL_TYPE_TIMESTAMP:
+ case SQL_TIMESTAMP:
+ if (type != RQ_DATE && type != RQ_DATETIME) {
+ warn_type(col, type);
+ }
+ break;
+ case SQL_INTEGER:
+ case SQL_BIGINT:
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ case SQL_BIT:
+ if (type != RQ_INTEGER) {
+ warn_type(col, type);
+ }
+ if ((col->type == SQL_BIT && size > 1) ||
+ (col->type == SQL_TINYINT && size > 2) ||
+ (col->type == SQL_SMALLINT && size > 4) ||
+ (col->type == SQL_INTEGER && size > 10)) {
+ warn_length(col, size);
+ }
+ break;
+ case SQL_NUMERIC:
+ case SQL_DECIMAL:
+ case SQL_FLOAT:
+ case SQL_REAL:
+ case SQL_DOUBLE:
+ if (type != RQ_INTEGER && type != RQ_FLOAT) {
+ warn_type(col, type);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Column type (%d) unrecognized for field '%s' in %s@%s\n", col->type, elm, table, database);
+ }
+ break;
+ }
+ }
+ if (!col) {
+ ast_log(LOG_WARNING, "Table %s@%s requires column '%s', but that column does not exist!\n", table, database, elm);
+ }
+ }
+ va_end(ap);
+ AST_RWLIST_UNLOCK(&tableptr->columns);
+ return 0;
+}
+
+static int unload_odbc(const char *database, const char *tablename)
+{
+ struct odbc_cache_tables *tableptr;
+
+ AST_RWLIST_RDLOCK(&odbc_tables);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&odbc_tables, tableptr, list) {
+ if (strcmp(tableptr->connection, database) == 0 && strcmp(tableptr->table, tablename) == 0) {
+ AST_LIST_REMOVE_CURRENT(list);
+ destroy_table_cache(tableptr);
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END
+ AST_RWLIST_UNLOCK(&odbc_tables);
+ return tableptr ? 0 : -1;
+}
+
static struct ast_config_engine odbc_engine = {
.name = "odbc",
.load_func = config_odbc,
@@ -716,12 +991,24 @@
.realtime_multi_func = realtime_multi_odbc,
.store_func = store_odbc,
.destroy_func = destroy_odbc,
- .update_func = update_odbc
+ .update_func = update_odbc,
+ .require_func = require_odbc,
+ .unload_func = unload_odbc,
};
static int unload_module (void)
{
+ struct odbc_cache_tables *table;
+
ast_config_engine_deregister(&odbc_engine);
+
+ /* Empty the cache */
+ AST_RWLIST_WRLOCK(&odbc_tables);
+ while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
+ destroy_table_cache(table);
+ }
+ AST_RWLIST_UNLOCK(&odbc_tables);
+
ast_verb(1, "res_config_odbc unloaded.\n");
return 0;
}
@@ -733,7 +1020,22 @@
return 0;
}
+static int reload_module(void)
+{
+ struct odbc_cache_tables *table;
+
+ /* Empty the cache; it will get rebuilt the next time the tables are needed. */
+ AST_RWLIST_WRLOCK(&odbc_tables);
+ while ((table = AST_RWLIST_REMOVE_HEAD(&odbc_tables, list))) {
+ destroy_table_cache(table);
+ }
+ AST_RWLIST_UNLOCK(&odbc_tables);
+
+ return 0;
+}
+
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Realtime ODBC configuration",
.load = load_module,
.unload = unload_module,
+ .reload = reload_module,
);
Modified: trunk/res/res_config_pgsql.c
URL: http://svn.digium.com/view/asterisk/trunk/res/res_config_pgsql.c?view=diff&rev=120789&r1=120788&r2=120789
==============================================================================
--- trunk/res/res_config_pgsql.c (original)
+++ trunk/res/res_config_pgsql.c Thu Jun 5 14:07:27 2008
@@ -49,6 +49,24 @@
#define MAX_DB_OPTION_SIZE 64
+struct columns {
+ char *name;
+ char *type;
+ int len;
+ unsigned int notnull:1;
+ unsigned int hasdefault:1;
+ AST_LIST_ENTRY(columns) list;
+};
+
+struct tables {
+ ast_mutex_t lock;
+ AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
+ AST_LIST_ENTRY(tables) list;
+ char name[0];
+};
+
+static AST_LIST_HEAD_STATIC(psql_tables, tables);
+
static char dbhost[MAX_DB_OPTION_SIZE] = "";
static char dbuser[MAX_DB_OPTION_SIZE] = "";
static char dbpass[MAX_DB_OPTION_SIZE] = "";
@@ -60,10 +78,111 @@
static int parse_config(int reload);
static int pgsql_reconnect(const char *database);
static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+
+enum { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR } requirements;
static struct ast_cli_entry cli_realtime[] = {
AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"),
+ AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
};
+
+static void destroy_table(struct tables *table)
+{
+ struct columns *column;
+ ast_mutex_lock(&table->lock);
+ while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
+ ast_free(column);
+ }
+ ast_mutex_unlock(&table->lock);
+ ast_mutex_destroy(&table->lock);
+ ast_free(table);
+}
+
+static struct tables *find_table(const char *tablename)
+{
+ struct columns *column;
+ struct tables *table;
+ struct ast_str *sql = ast_str_create(330);
+ char *pgerror;
+ PGresult *result;
+ char *fname, *ftype, *flen, *fnotnull, *fdef;
+ int i, rows;
+
+ AST_LIST_LOCK(&psql_tables);
+ AST_LIST_TRAVERSE(&psql_tables, table, list) {
+ if (!strcasecmp(table->name, tablename)) {
+ ast_debug(1, "Found table in cache; now locking\n");
+ ast_mutex_lock(&table->lock);
+ ast_debug(1, "Lock cached table; now returning\n");
+ AST_LIST_UNLOCK(&psql_tables);
+ return table;
+ }
+ }
+
+ ast_debug(1, "Table '%s' not found in cache, querying now\n", tablename);
+
+ /* Not found, scan the table */
+ ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", tablename);
+ result = PQexec(pgsqlConn, sql->str);
+ ast_debug(1, "Query of table structure complete. Now retrieving results.\n");
+ if (PQresultStatus(result) != PGRES_TUPLES_OK) {
+ pgerror = PQresultErrorMessage(result);
+ ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
+ PQclear(result);
+ AST_LIST_UNLOCK(&psql_tables);
+ return NULL;
+ }
+
+ if (!(table = ast_calloc(1, sizeof(*table) + strlen(tablename) + 1))) {
+ ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
+ AST_LIST_UNLOCK(&psql_tables);
+ return NULL;
+ }
+ strcpy(table->name, tablename); /* SAFE */
+ ast_mutex_init(&table->lock);
+ AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
+
+ rows = PQntuples(result);
+ for (i = 0; i < rows; i++) {
+ fname = PQgetvalue(result, i, 0);
+ ftype = PQgetvalue(result, i, 1);
+ flen = PQgetvalue(result, i, 2);
+ fnotnull = PQgetvalue(result, i, 3);
+ fdef = PQgetvalue(result, i, 4);
+ ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
+
+ if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + 2))) {
+ ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", tablename, fname);
+ destroy_table(table);
+ AST_LIST_UNLOCK(&psql_tables);
+ return NULL;
+ }
+
+ sscanf(flen, "%d", &column->len);
+ column->name = (char *)column + sizeof(*column);
+ column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
+ strcpy(column->name, fname);
+ strcpy(column->type, ftype);
+ if (*fnotnull == 't') {
+ column->notnull = 1;
+ } else {
+ column->notnull = 0;
+ }
+ if (!ast_strlen_zero(fdef)) {
+ column->hasdefault = 1;
+ } else {
+ column->hasdefault = 0;
+ }
+ AST_LIST_INSERT_TAIL(&table->columns, column, list);
+ }
+ PQclear(result);
+
+ AST_LIST_INSERT_TAIL(&psql_tables, table, list);
+ ast_mutex_lock(&table->lock);
+ AST_LIST_UNLOCK(&psql_tables);
+ return table;
+}
static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
{
@@ -360,16 +479,26 @@
return cfg;
}
-static int update_pgsql(const char *database, const char *table, const char *keyfield,
+static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
const char *lookup, va_list ap)
{
PGresult *result = NULL;
int numrows = 0, pgerror;
- char sql[256], escapebuf[513];
+ char escapebuf[513];
const char *newparam, *newval;
-
- if (!table) {
+ struct ast_str *sql = ast_str_create(100);
+ struct tables *table;
+ struct columns *column = NULL;
+
+ if (!tablename) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
+ ast_free(sql);
+ return -1;
+ }
+
+ if (!(table = find_table(tablename))) {
+ ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
+ ast_free(sql);
return -1;
}
@@ -383,6 +512,22 @@
PQfinish(pgsqlConn);
pgsqlConn = NULL;
};
+ ast_mutex_unlock(&table->lock);
+ ast_free(sql);
+ return -1;
+ }
+
+ /* Check that the column exists in the table */
+ AST_LIST_TRAVERSE(&table->columns, column, list) {
+ if (strcmp(column->name, newparam) == 0) {
+ break;
+ }
+ }
+
+ if (!column) {
+ ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
+ ast_mutex_unlock(&table->lock);
+ ast_free(sql);
return -1;
}
@@ -393,50 +538,68 @@
if (pgerror) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
- return -1;
- }
- snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, escapebuf);
+ ast_mutex_unlock(&table->lock);
+ ast_free(sql);
+ return -1;
+ }
+ ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, escapebuf);
while ((newparam = va_arg(ap, const char *))) {
newval = va_arg(ap, const char *);
+
+ /* If the column is not within the table, then skip it */
+ AST_LIST_TRAVERSE(&table->columns, column, list) {
+ if (strcmp(column->name, newparam) == 0) {
+ break;
+ }
+ }
+
+ if (!column) {
+ ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
+ continue;
+ }
PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
va_end(ap);
+ ast_mutex_unlock(&table->lock);
+ ast_free(sql);
return -1;
}
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam,
- escapebuf);
+ ast_str_append(&sql, 0, ", %s = '%s'", newparam, escapebuf);
}
va_end(ap);
+ ast_mutex_unlock(&table->lock);
PQescapeStringConn(pgsqlConn, escapebuf, lookup, (sizeof(escapebuf) - 1) / 2, &pgerror);
if (pgerror) {
ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
va_end(ap);
- return -1;
- }
-
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield,
- escapebuf);
-
- ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", sql);
+ ast_free(sql);
+ return -1;
+ }
+
+ ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, escapebuf);
+
+ ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", sql->str);
/* We now have our complete statement; Lets connect to the server and execute it. */
ast_mutex_lock(&pgsql_lock);
if (!pgsql_reconnect(database)) {
ast_mutex_unlock(&pgsql_lock);
- return -1;
- }
-
- if (!(result = PQexec(pgsqlConn, sql))) {
+ ast_free(sql);
+ return -1;
+ }
+
+ if (!(result = PQexec(pgsqlConn, sql->str))) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
- ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
+ ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
ast_mutex_unlock(&pgsql_lock);
+ ast_free(sql);
return -1;
} else {
ExecStatusType result_status = PQresultStatus(result);
@@ -445,18 +608,20 @@
&& result_status != PGRES_NONFATAL_ERROR) {
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
- ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql);
+ ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql->str);
ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
PQresultErrorMessage(result), PQresStatus(result_status));
ast_mutex_unlock(&pgsql_lock);
+ ast_free(sql);
return -1;
}
}
numrows = atoi(PQcmdTuples(result));
ast_mutex_unlock(&pgsql_lock);
-
- ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, table);
+ ast_free(sql);
+
+ ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
[... 240 lines stripped ...]
More information about the asterisk-commits
mailing list