Skip to content
Snippets Groups Projects
res_config_pgsql.c 53.2 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * Asterisk -- An open source telephony toolkit.
    
    Jason Parker's avatar
    Jason Parker committed
     * Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
    
     * Mark Spencer <markster@digium.com>  - Asterisk Author
     * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
     *
    
    Jason Parker's avatar
    Jason Parker committed
     * res_config_pgsql.c <PostgreSQL plugin for RealTime configuration engine>
    
     *
     * v1.0   - (07-11-05) - Initial version based on res_config_mysql v2.0
     */
    
    /*! \file
     *
    
    Jason Parker's avatar
    Jason Parker committed
     * \brief PostgreSQL plugin for Asterisk RealTime Architecture
    
     *
     * \author Mark Spencer <markster@digium.com>
    
    Jason Parker's avatar
    Jason Parker committed
     * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
    
     * PostgreSQL http://www.postgresql.org
    
    	<support_level>extended</support_level>
    
    #include <libpq-fe.h>			/* PostgreSQL */
    
    
    #include "asterisk/file.h"
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    #include "asterisk/config.h"
    #include "asterisk/module.h"
    #include "asterisk/lock.h"
    #include "asterisk/utils.h"
    #include "asterisk/cli.h"
    
    AST_MUTEX_DEFINE_STATIC(pgsql_lock);
    
    AST_THREADSTORAGE(sql_buf);
    AST_THREADSTORAGE(findtable_buf);
    AST_THREADSTORAGE(where_buf);
    AST_THREADSTORAGE(escapebuf_buf);
    
    AST_THREADSTORAGE(semibuf_buf);
    
    #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
    
    static PGconn *pgsqlConn = NULL;
    
    static int version;
    #define has_schema_support	(version > 70300 ? 1 : 0)
    
    #define USE_BACKSLASH_AS_STRING	(version >= 90100 ? 1 : 0)
    
    #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_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] = "";
    static char dbname[MAX_DB_OPTION_SIZE] = "";
    
    static char dbappname[MAX_DB_OPTION_SIZE] = "";
    
    static char dbsock[MAX_DB_OPTION_SIZE] = "";
    static int dbport = 5432;
    static time_t connect_time = 0;
    
    static int parse_config(int reload);
    
    static int pgsql_reconnect(const char *database);
    
    Jason Parker's avatar
    Jason Parker committed
    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);
    
    
    static 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"),
    
    #define ESCAPE_STRING(buffer, stringname) \
    	do { \
    
    		int len = strlen(stringname); \
    		struct ast_str *semi = ast_str_thread_get(&semibuf_buf, len * 3 + 1); \
    		const char *chunk = stringname; \
    		ast_str_reset(semi); \
    		for (; *chunk; chunk++) { \
    			if (strchr(";^", *chunk)) { \
    				ast_str_append(&semi, 0, "^%02hhX", *chunk); \
    			} else { \
    				ast_str_append(&semi, 0, "%c", *chunk); \
    			} \
    		} \
    		if (ast_str_strlen(semi) > (ast_str_size(buffer) - 1) / 2) { \
    			ast_str_make_space(&buffer, ast_str_strlen(semi) * 2 + 1); \
    
    		PQescapeStringConn(pgsqlConn, ast_str_buffer(buffer), ast_str_buffer(semi), ast_str_size(buffer), &pgresult); \
    
    static void destroy_table(struct tables *table)
    {
    	struct columns *column;
    
    	while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
    		ast_free(column);
    	}
    
    	ast_rwlock_unlock(&table->lock);
    	ast_rwlock_destroy(&table->lock);
    
    Josh Soref's avatar
    Josh Soref committed
    /*! \brief Helper function for pgsql_exec.  For running queries, use pgsql_exec()
    
     *
     *  Connect if not currently connected.  Run the given query.
     *
     *  \param database   database name we are connected to (used for error logging)
     *  \param tablename  table  name we are connected to (used for error logging)
     *  \param sql        sql query string to execute
     *  \param result     pointer for where to store the result handle
     *
     *  \return -1 on fatal query error
     *  \return -2 on query failure that resulted in disconnection
     *  \return 0 on success
     *
    
     *  \note see pgsql_exec for full example
    
     */
    static int _pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
    {
    	ExecStatusType result_status;
    
    	if (!pgsqlConn) {
    		ast_debug(1, "PostgreSQL connection not defined, connecting\n");
    
    		if (pgsql_reconnect(database) != 1) {
    			ast_log(LOG_NOTICE, "reconnect failed\n");
    			*result = NULL;
    			return -1;
    		}
    
    		ast_debug(1, "PostgreSQL connection successful\n");
    	}
    
    	*result = PQexec(pgsqlConn, sql);
    	result_status = PQresultStatus(*result);
    	if (result_status != PGRES_COMMAND_OK
    		&& result_status != PGRES_TUPLES_OK
    		&& result_status != PGRES_NONFATAL_ERROR) {
    
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: Failed to query '%s@%s'.\n", tablename, database);
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: Query Failed: %s\n", sql);
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
    			PQresultErrorMessage(*result),
    			PQresStatus(result_status));
    
    		/* we may have tried to run a command on a disconnected/disconnecting handle */
    		/* are we no longer connected to the database... if not try again */
    		if (PQstatus(pgsqlConn) != CONNECTION_OK) {
    			PQfinish(pgsqlConn);
    			pgsqlConn = NULL;
    			return -2;
    		}
    
    		/* connection still okay, which means the query is just plain bad */
    		return -1;
    	}
    
    	ast_debug(1, "PostgreSQL query successful: %s\n", sql);
    	return 0;
    }
    
    /*! \brief Do a postgres query, with reconnection support
     *
     *  Connect if not currently connected.  Run the given query
     *  and if we're disconnected afterwards, reconnect and query again.
     *
     *  \param database   database name we are connected to (used for error logging)
     *  \param tablename  table  name we are connected to (used for error logging)
     *  \param sql        sql query string to execute
     *  \param result     pointer for where to store the result handle
     *
     *  \return -1 on query failure
     *  \return 0 on success
     *
    
     *	int i, rows;
     *	PGresult *result;
     *	char *field_name, *field_type, *field_len, *field_notnull, *field_default;
     *
     *	pgsql_exec("db", "table", "SELECT 1", &result)
     *
     *	rows = PQntuples(result);
     *	for (i = 0; i < rows; i++) {
     *		field_name    = PQgetvalue(result, i, 0);
     *		field_type    = PQgetvalue(result, i, 1);
     *		field_len     = PQgetvalue(result, i, 2);
     *		field_notnull = PQgetvalue(result, i, 3);
     *		field_default = PQgetvalue(result, i, 4);
     *	}
    
     *  \endcode
    
     */
    static int pgsql_exec(const char *database, const char *tablename, const char *sql, PGresult **result)
    {
    	int attempts = 0;
    	int res;
    
    	/* Try the query, note failure if any */
    	/* On first failure, reconnect and try again (_pgsql_exec handles reconnect) */
    	/* On second failure, treat as fatal query error */
    
    	while (attempts++ < 2) {
    		ast_debug(1, "PostgreSQL query attempt %d\n", attempts);
    		res = _pgsql_exec(database, tablename, sql, result);
    
    		if (res == 0) {
    			if (attempts > 1) {
    				ast_log(LOG_NOTICE, "PostgreSQL RealTime: Query finally succeeded: %s\n", sql);
    			}
    
    			return 0;
    		}
    
    		if (res == -1) {
    			return -1; /* Still connected to db, but could not process query (fatal error) */
    		}
    
    		/* res == -2 (query on a disconnected handle) */
    		ast_debug(1, "PostgreSQL query attempt %d failed, trying again\n", attempts);
    	}
    
    	return -1;
    }
    
    static struct tables *find_table(const char *database, const char *orig_tablename)
    
    	struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
    
    	RAII_VAR(PGresult *, result, NULL, PQclear);
    	int exec_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, orig_tablename)) {
    
    			ast_debug(1, "Found table in cache; now locking\n");
    
    			ast_debug(1, "Lock cached table; now returning\n");
    			AST_LIST_UNLOCK(&psql_tables);
    			return table;
    		}
    	}
    
    
    		AST_LIST_UNLOCK(&psql_tables);
    
    	ast_debug(1, "Table '%s' not found in cache, querying now\n", orig_tablename);
    
    		char *schemaname, *tablename, *tmp_schemaname, *tmp_tablename;
    
    			tmp_schemaname = ast_strdupa(orig_tablename);
    			tmp_tablename = strchr(tmp_schemaname, '.');
    			*tmp_tablename++ = '\0';
    
    			tmp_schemaname = "";
    			tmp_tablename = ast_strdupa(orig_tablename);
    
    		tablename = ast_alloca(strlen(tmp_tablename) * 2 + 1);
    		PQescapeStringConn(pgsqlConn, tablename, tmp_tablename, strlen(tmp_tablename), NULL);
    		schemaname = ast_alloca(strlen(tmp_schemaname) * 2 + 1);
    		PQescapeStringConn(pgsqlConn, schemaname, tmp_schemaname, strlen(tmp_schemaname), NULL);
    
    		ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, pg_catalog.pg_get_expr(d.adbin, d.adrelid) adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
    
    			tablename,
    			ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
    	} else {
    
    		char *tablename;
    		tablename = ast_alloca(strlen(orig_tablename) * 2 + 1);
    		PQescapeStringConn(pgsqlConn, tablename, orig_tablename, strlen(orig_tablename), NULL);
    
    		ast_str_set(&sql, 0, "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod 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);
    
    	exec_result = pgsql_exec(database, orig_tablename, ast_str_buffer(sql), &result);
    
    	ast_debug(1, "Query of table structure complete.  Now retrieving results.\n");
    
    	if (exec_result != 0) {
    		ast_log(LOG_ERROR, "Failed to query database columns for table %s\n", orig_tablename);
    
    	if (!(table = ast_calloc(1, sizeof(*table) + strlen(orig_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, orig_tablename); /* SAFE */
    
    	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", orig_tablename, fname);
    
    			destroy_table(table);
    			AST_LIST_UNLOCK(&psql_tables);
    			return NULL;
    		}
    
    
    		if (strcmp(flen, "-1") == 0) {
    			/* Some types, like chars, have the length stored in a different field */
    			flen = PQgetvalue(result, i, 5);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			sscanf(flen, "%30d", &column->len);
    
    			column->len -= 4;
    		} else {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			sscanf(flen, "%30d", &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);
    	}
    
    	AST_LIST_INSERT_TAIL(&psql_tables, table, list);
    
    #define release_table(table) ast_rwlock_unlock(&(table)->lock);
    
    static struct columns *find_column(struct tables *t, const char *colname)
    {
    	struct columns *column;
    
    	/* Check that the column exists in the table */
    	AST_LIST_TRAVERSE(&t->columns, column, list) {
    		if (strcmp(column->name, colname) == 0) {
    			return column;
    		}
    	}
    	return NULL;
    }
    
    
    #define IS_SQL_LIKE_CLAUSE(x) ((x) && ast_ends_with(x, " LIKE"))
    
    #define ESCAPE_CLAUSE (USE_BACKSLASH_AS_STRING ? " ESCAPE '\\'" : " ESCAPE '\\\\'")
    
    static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, const struct ast_variable *fields)
    
    	RAII_VAR(PGresult *, result, NULL, PQclear);
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
    	struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
    
    	char *stringp;
    	char *chunk;
    	char *op;
    
    	char *escape = "";
    
    	const struct ast_variable *field = fields;
    
    	struct ast_variable *var = NULL, *prev = NULL;
    
    	/*
    	 * Ignore database from the extconfig.conf since it was
    	 * configured by res_pgsql.conf.
    	 */
    	database = dbname;
    
    
    Jason Parker's avatar
    Jason Parker committed
    		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
    
    	/*
    	 * Must connect to the server before anything else as ESCAPE_STRING()
    	 * uses pgsqlConn
    	 */
    	ast_mutex_lock(&pgsql_lock);
    	if (!pgsql_reconnect(database)) {
    		ast_mutex_unlock(&pgsql_lock);
    		return NULL;
    	}
    
    
    	/* Get the first parameter and first value in our list of passed paramater/value pairs */
    
    	if (!field) {
    
    Jason Parker's avatar
    Jason Parker committed
    				"PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
    
    		if (pgsqlConn) {
    			PQfinish(pgsqlConn);
    			pgsqlConn = NULL;
    
    		ast_mutex_unlock(&pgsql_lock);
    
    		return NULL;
    	}
    
    	/* Create the first part of the query using the first parameter/value pairs we just extracted
    	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
    
    	if (!strchr(field->name, ' ')) {
    		op = " =";
    	} else {
    		op = "";
    		if (IS_SQL_LIKE_CLAUSE(field->name)) {
    			escape = ESCAPE_CLAUSE;
    		}
    	}
    
    	ESCAPE_STRING(escapebuf, field->value);
    
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    		ast_mutex_unlock(&pgsql_lock);
    
    	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'%s", tablename, field->name, op, ast_str_buffer(escapebuf), escape);
    
    	while ((field = field->next)) {
    
    		escape = "";
    		if (!strchr(field->name, ' ')) {
    
    			if (IS_SQL_LIKE_CLAUSE(field->name)) {
    				escape = ESCAPE_CLAUSE;
    			}
    		}
    
    		ESCAPE_STRING(escapebuf, field->value);
    
    			ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    			ast_mutex_unlock(&pgsql_lock);
    
    		ast_str_append(&sql, 0, " AND %s%s '%s'%s", field->name, op, ast_str_buffer(escapebuf), escape);
    
    
    	/* We now have our complete statement; Lets connect to the server and execute it. */
    
            if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
    
    		ast_mutex_unlock(&pgsql_lock);
    		return NULL;
    
    	ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
    
    		int i = 0;
    		int numFields = PQnfields(result);
    		char **fieldnames = NULL;
    
    
    		ast_debug(1, "PostgreSQL RealTime: Found a row.\n");
    
    		if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
    
    			ast_mutex_unlock(&pgsql_lock);
    
    			return NULL;
    		}
    		for (i = 0; i < numFields; i++)
    			fieldnames[i] = PQfname(result, i);
    
    		for (i = 0; i < numFields; i++) {
    			stringp = PQgetvalue(result, 0, i);
    			while (stringp) {
    				chunk = strsep(&stringp, ";");
    				if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
    					if (prev) {
    						prev->next = ast_variable_new(fieldnames[i], chunk, "");
    						if (prev->next) {
    							prev = prev->next;
    
    					} else {
    						prev = var = ast_variable_new(fieldnames[i], chunk, "");
    
    		ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
    
    	ast_mutex_unlock(&pgsql_lock);
    
    static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, const struct ast_variable *fields)
    
    	RAII_VAR(PGresult *, result, NULL, PQclear);
    
    	int num_rows = 0, pgresult;
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
    	struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
    
    	const struct ast_variable *field = fields;
    
    	const char *initfield = NULL;
    	char *stringp;
    	char *chunk;
    	char *op;
    
    	char *escape = "";
    
    	struct ast_config *cfg = NULL;
    	struct ast_category *cat = NULL;
    
    
    	/*
    	 * Ignore database from the extconfig.conf since it was
    	 * configured by res_pgsql.conf.
    	 */
    	database = dbname;
    
    
    Jason Parker's avatar
    Jason Parker committed
    		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
    
    	if (!(cfg = ast_config_new()))
    
    	/*
    	 * Must connect to the server before anything else as ESCAPE_STRING()
    	 * uses pgsqlConn
    	 */
    	ast_mutex_lock(&pgsql_lock);
    	if (!pgsql_reconnect(database)) {
    		ast_mutex_unlock(&pgsql_lock);
    		return NULL;
    	}
    
    
    	/* Get the first parameter and first value in our list of passed paramater/value pairs */
    
    	if (!field) {
    
    Jason Parker's avatar
    Jason Parker committed
    				"PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
    
    		if (pgsqlConn) {
    			PQfinish(pgsqlConn);
    			pgsqlConn = NULL;
    
    		ast_mutex_unlock(&pgsql_lock);
    
    		ast_config_destroy(cfg);
    
    	initfield = ast_strdupa(field->name);
    
    		*op = '\0';
    	}
    
    	/* Create the first part of the query using the first parameter/value pairs we just extracted
    	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
    
    
    	if (!strchr(field->name, ' ')) {
    
    		escape = "";
    	} else {
    
    		if (IS_SQL_LIKE_CLAUSE(field->name)) {
    			escape = ESCAPE_CLAUSE;
    		}
    	}
    
    	ESCAPE_STRING(escapebuf, field->value);
    
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    		ast_mutex_unlock(&pgsql_lock);
    
    		ast_config_destroy(cfg);
    
    	ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'%s", table, field->name, op, ast_str_buffer(escapebuf), escape);
    
    	while ((field = field->next)) {
    
    		escape = "";
    		if (!strchr(field->name, ' ')) {
    
    			escape = "";
    		} else {
    
    			if (IS_SQL_LIKE_CLAUSE(field->name)) {
    				escape = ESCAPE_CLAUSE;
    			}
    		}
    
    		ESCAPE_STRING(escapebuf, field->value);
    
    			ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    			ast_mutex_unlock(&pgsql_lock);
    
    			ast_config_destroy(cfg);
    
    		ast_str_append(&sql, 0, " AND %s%s '%s'%s", field->name, op, ast_str_buffer(escapebuf), escape);
    
    		ast_str_append(&sql, 0, " ORDER BY %s", initfield);
    
    	}
    
    	/* We now have our complete statement; Lets connect to the server and execute it. */
    
    	if (pgsql_exec(database, table, ast_str_buffer(sql), &result) != 0) {
    
    		ast_mutex_unlock(&pgsql_lock);
    
    		ast_config_destroy(cfg);
    
    		return NULL;
    	} else {
    		ExecStatusType result_status = PQresultStatus(result);
    		if (result_status != PGRES_COMMAND_OK
    			&& result_status != PGRES_TUPLES_OK
    			&& result_status != PGRES_NONFATAL_ERROR) {
    			ast_log(LOG_WARNING,
    					"PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
    			ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
    			ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
    						PQresultErrorMessage(result), PQresStatus(result_status));
    			ast_mutex_unlock(&pgsql_lock);
    
    			ast_config_destroy(cfg);
    
    	ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
    
    	if ((num_rows = PQntuples(result)) > 0) {
    		int numFields = PQnfields(result);
    		int i = 0;
    		int rowIndex = 0;
    		char **fieldnames = NULL;
    
    
    Jason Parker's avatar
    Jason Parker committed
    		ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
    
    		if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
    
    			ast_mutex_unlock(&pgsql_lock);
    
    			ast_config_destroy(cfg);
    
    		for (i = 0; i < numFields; i++)
    			fieldnames[i] = PQfname(result, i);
    
    		for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
    			var = NULL;
    
    			cat = ast_category_new_anonymous();
    			if (!cat) {
    
    			for (i = 0; i < numFields; i++) {
    				stringp = PQgetvalue(result, rowIndex, i);
    				while (stringp) {
    					chunk = strsep(&stringp, ";");
    
    					if (chunk && !ast_strlen_zero(ast_realtime_decode_chunk(ast_strip(chunk)))) {
    
    						if (initfield && !strcmp(initfield, fieldnames[i])) {
    							ast_category_rename(cat, chunk);
    						}
    
    						var = ast_variable_new(fieldnames[i], chunk, "");
    
    						ast_variable_append(cat, var);
    					}
    				}
    			}
    			ast_category_append(cfg, cat);
    		}
    
    		ast_debug(1, "PostgreSQL RealTime: Could not find any rows in table %s.\n", table);
    
    	ast_mutex_unlock(&pgsql_lock);
    
    static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
    
    						const char *lookup, const struct ast_variable *fields)
    
    	RAII_VAR(PGresult *, result, NULL, PQclear);
    
    	const struct ast_variable *field = fields;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
    	struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
    
    	struct tables *table;
    	struct columns *column = NULL;
    
    	/*
    	 * Ignore database from the extconfig.conf since it was
    	 * configured by res_pgsql.conf.
    	 */
    	database = dbname;
    
    
    Jason Parker's avatar
    Jason Parker committed
    		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
    
    	if (!(table = find_table(database, tablename))) {
    
    		ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
    
    	/*
    	 * Must connect to the server before anything else as ESCAPE_STRING()
    	 * uses pgsqlConn
    	 */
    	ast_mutex_lock(&pgsql_lock);
    	if (!pgsql_reconnect(database)) {
    		ast_mutex_unlock(&pgsql_lock);
    
    	/* Get the first parameter and first value in our list of passed paramater/value pairs */
    
    	if (!field) {
    
    Jason Parker's avatar
    Jason Parker committed
    				"PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
    
    		if (pgsqlConn) {
    			PQfinish(pgsqlConn);
    			pgsqlConn = NULL;
    
    		ast_mutex_unlock(&pgsql_lock);
    
    		return -1;
    	}
    
    	/* Check that the column exists in the table */
    	AST_LIST_TRAVERSE(&table->columns, column, list) {
    
    		if (strcmp(column->name, field->name) == 0) {
    
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", field->name, tablename);
    
    		ast_mutex_unlock(&pgsql_lock);
    
    	}
    
    	/* Create the first part of the query using the first parameter/value pairs we just extracted
    	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
    
    
    	ESCAPE_STRING(escapebuf, field->value);
    
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    		ast_mutex_unlock(&pgsql_lock);
    
    	ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, field->name, ast_str_buffer(escapebuf));
    
    	while ((field = field->next)) {
    		if (!find_column(table, field->name)) {
    			ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename);
    
    		ESCAPE_STRING(escapebuf, field->value);
    
    			ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    			ast_mutex_unlock(&pgsql_lock);
    
    		ast_str_append(&sql, 0, ", %s = '%s'", field->name, ast_str_buffer(escapebuf));
    
    	ESCAPE_STRING(escapebuf, lookup);
    	if (pgresult) {
    
    		ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", lookup);
    
    		ast_mutex_unlock(&pgsql_lock);
    
    	ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(escapebuf));
    
    	ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
    
    
    	/* We now have our complete statement; Lets connect to the server and execute it. */
    
    	if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
    
    		ast_mutex_unlock(&pgsql_lock);
    		return -1;
    
    	} else {
    		ExecStatusType result_status = PQresultStatus(result);
    		if (result_status != PGRES_COMMAND_OK
    			&& result_status != PGRES_TUPLES_OK
    			&& 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", ast_str_buffer(sql));
    			ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
    						PQresultErrorMessage(result), PQresStatus(result_status));
    			ast_mutex_unlock(&pgsql_lock);
    			return -1;
    		}
    
    	}
    
    	numrows = atoi(PQcmdTuples(result));
    	ast_mutex_unlock(&pgsql_lock);
    
    
    	ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
    
    
    	/* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
    	 * An integer greater than zero indicates the number of rows affected
    	 * Zero indicates that no records were updated
    	 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
    
    static int update2_pgsql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
    
    	RAII_VAR(PGresult *, result, NULL, PQclear);
    
    	int numrows = 0, pgresult, first = 1;
    	struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
    
    	const struct ast_variable *field;
    
    	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
    	struct ast_str *where = ast_str_thread_get(&where_buf, 100);
    	struct tables *table;
    
    
    	/*
    	 * Ignore database from the extconfig.conf since it was
    	 * configured by res_pgsql.conf.
    	 */
    	database = dbname;
    
    
    	if (!tablename) {
    		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
    		return -1;
    	}
    
    	if (!escapebuf || !sql || !where) {
    		/* Memory error, already handled */
    		return -1;
    	}
    
    
    	if (!(table = find_table(database, tablename))) {
    
    		ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
    		return -1;
    	}
    
    
    	/*
    	 * Must connect to the server before anything else as ESCAPE_STRING()
    	 * uses pgsqlConn
    	 */
    	ast_mutex_lock(&pgsql_lock);
    	if (!pgsql_reconnect(database)) {
    		ast_mutex_unlock(&pgsql_lock);
    
    	ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
    	ast_str_set(&where, 0, " WHERE");
    
    	for (field = lookup_fields; field; field = field->next) {
    		if (!find_column(table, field->name)) {
    			ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", field->name, tablename, database);
    
    			ast_mutex_unlock(&pgsql_lock);
    
    		ESCAPE_STRING(escapebuf, field->value);
    
    			ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    			ast_mutex_unlock(&pgsql_lock);
    
    		ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", field->name, ast_str_buffer(escapebuf));
    
    		first = 0;
    	}
    
    	if (first) {
    		ast_log(LOG_WARNING,
    				"PostgreSQL RealTime: Realtime update requires at least 1 parameter and 1 value to search on.\n");
    		if (pgsqlConn) {
    			PQfinish(pgsqlConn);
    			pgsqlConn = NULL;
    		}
    
    		ast_mutex_unlock(&pgsql_lock);
    
    		release_table(table);
    		return -1;
    	}
    
    	/* Now retrieve the columns to update */
    	first = 1;
    
    	for (field = update_fields; field; field = field->next) {
    
    		/* If the column is not within the table, then skip it */
    
    		if (!find_column(table, field->name)) {
    			ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", field->name, tablename, database);
    
    		ESCAPE_STRING(escapebuf, field->value);
    
    			ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
    
    			ast_mutex_unlock(&pgsql_lock);
    
    		ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", field->name, ast_str_buffer(escapebuf));
    
    	ast_str_append(&sql, 0, "%s", ast_str_buffer(where));
    
    	ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
    
    	/* We now have our complete statement; Lets connect to the server and execute it. */
    
            if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
    
    
    	numrows = atoi(PQcmdTuples(result));
    	ast_mutex_unlock(&pgsql_lock);
    
    	ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
    
    	/* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
    	 * An integer greater than zero indicates the number of rows affected
    	 * Zero indicates that no records were updated
    	 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
    	 */
    
    	if (numrows >= 0) {
    		return (int) numrows;
    	}
    
    	return -1;
    }
    
    static int store_pgsql(const char *database, const char *table, const struct ast_variable *fields)
    
    	RAII_VAR(PGresult *, result, NULL, PQclear);
    
    	struct ast_str *buf = ast_str_thread_get(&escapebuf_buf, 256);
    	struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256);
    	struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256);
    
    	const struct ast_variable *field = fields;
    
    	/*
    	 * Ignore database from the extconfig.conf since it was
    	 * configured by res_pgsql.conf.
    	 */
    	database = dbname;
    
    
    Jason Parker's avatar
    Jason Parker committed
    		ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
    
    	/*
    	 * Must connect to the server before anything else as ESCAPE_STRING()
    	 * uses pgsqlConn
    	 */
    	ast_mutex_lock(&pgsql_lock);
    	if (!pgsql_reconnect(database)) {
    		ast_mutex_unlock(&pgsql_lock);
    		return -1;
    	}
    
    
    	/* Get the first parameter and first value in our list of passed paramater/value pairs */