Newer
Older
* Asterisk -- An open source telephony toolkit.
Rodrigo Ramírez Norambuena
committed
* Copyright (C) 1999 - 2017, Digium, Inc.
* 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
*
* 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
*
* \brief PostgreSQL plugin for Asterisk RealTime Architecture
*
* \author Mark Spencer <markster@digium.com>
* \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
Kevin P. Fleming
committed
/*** MODULEINFO
<depend>pgsql</depend>
<support_level>extended</support_level>
Kevin P. Fleming
committed
***/
Kevin P. Fleming
committed
#include "asterisk.h"
Russell Bryant
committed
#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);
Russell Bryant
committed
#define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
Russell Bryant
committed
static PGconn *pgsqlConn = NULL;
static int version;
#define has_schema_support (version > 70300 ? 1 : 0)
Rodrigo Ramírez Norambuena
committed
#define USE_BACKSLASH_AS_STRING (version >= 90100 ? 1 : 0)
Russell Bryant
committed
#define MAX_DB_OPTION_SIZE 64
Russell Bryant
committed
Tilghman Lesher
committed
struct columns {
char *name;
char *type;
int len;
unsigned int notnull:1;
unsigned int hasdefault:1;
AST_LIST_ENTRY(columns) list;
};
struct tables {
ast_rwlock_t lock;
Tilghman Lesher
committed
AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
AST_LIST_ENTRY(tables) list;
char name[0];
};
static AST_LIST_HEAD_STATIC(psql_tables, tables);
Russell Bryant
committed
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] = "";
Matthew Jordan
committed
static char dbappname[MAX_DB_OPTION_SIZE] = "";
Russell Bryant
committed
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);
static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
Tilghman Lesher
committed
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[] = {
Jason Parker
committed
AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"),
Tilghman Lesher
committed
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); \
} while (0)
Tilghman Lesher
committed
static void destroy_table(struct tables *table)
{
struct columns *column;
ast_rwlock_wrlock(&table->lock);
Tilghman Lesher
committed
while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
ast_free(column);
}
ast_rwlock_unlock(&table->lock);
ast_rwlock_destroy(&table->lock);
Tilghman Lesher
committed
ast_free(table);
}
Mark Murawki
committed
/*! \brief Helper function for pgsql_exec. For running querys, 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
*
Mark Murawki
committed
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
*/
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
*
Mark Murawki
committed
* 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);
* }
Mark Murawki
committed
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
*/
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)
Tilghman Lesher
committed
{
struct columns *column;
struct tables *table;
struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
Jonathan Rose
committed
RAII_VAR(PGresult *, result, NULL, PQclear);
int exec_result;
Tilghman Lesher
committed
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)) {
Tilghman Lesher
committed
ast_debug(1, "Found table in cache; now locking\n");
ast_rwlock_rdlock(&table->lock);
Tilghman Lesher
committed
ast_debug(1, "Lock cached table; now returning\n");
AST_LIST_UNLOCK(&psql_tables);
return table;
}
}
if (database == NULL) {
AST_LIST_UNLOCK(&psql_tables);
return NULL;
}
ast_debug(1, "Table '%s' not found in cache, querying now\n", orig_tablename);
Tilghman Lesher
committed
/* Not found, scan the table */
if (has_schema_support) {
Rodrigo Ramírez Norambuena
committed
char *schemaname, *tablename, *tmp_schemaname, *tmp_tablename;
if (strchr(orig_tablename, '.')) {
Rodrigo Ramírez Norambuena
committed
tmp_schemaname = ast_strdupa(orig_tablename);
tmp_tablename = strchr(tmp_schemaname, '.');
*tmp_tablename++ = '\0';
} else {
Rodrigo Ramírez Norambuena
committed
tmp_schemaname = "";
tmp_tablename = ast_strdupa(orig_tablename);
}
Rodrigo Ramírez Norambuena
committed
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, d.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 {
Rodrigo Ramírez Norambuena
committed
char *tablename;
tablename = ast_alloca(strlen(orig_tablename) * 2 + 1);
PQescapeStringConn(pgsqlConn, tablename, orig_tablename, strlen(orig_tablename), NULL);
Rodrigo Ramírez Norambuena
committed
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);
}
ast_mutex_lock(&pgsql_lock);
Mark Murawki
committed
exec_result = pgsql_exec(database, orig_tablename, ast_str_buffer(sql), &result);
ast_mutex_unlock(&pgsql_lock);
Tilghman Lesher
committed
ast_debug(1, "Query of table structure complete. Now retrieving results.\n");
Mark Murawki
committed
if (exec_result != 0) {
ast_log(LOG_ERROR, "Failed to query database columns for table %s\n", orig_tablename);
Tilghman Lesher
committed
AST_LIST_UNLOCK(&psql_tables);
return NULL;
}
if (!(table = ast_calloc(1, sizeof(*table) + strlen(orig_tablename) + 1))) {
Tilghman Lesher
committed
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_rwlock_init(&table->lock);
Tilghman Lesher
committed
AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
Tilghman Lesher
committed
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);
Tilghman Lesher
committed
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);
column->len -= 4;
} else {
Tilghman Lesher
committed
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);
ast_rwlock_rdlock(&table->lock);
Tilghman Lesher
committed
AST_LIST_UNLOCK(&psql_tables);
return table;
}
#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"))
Rodrigo Ramírez Norambuena
committed
#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)
Jonathan Rose
committed
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);
char *stringp;
char *chunk;
char *op;
const struct ast_variable *field = fields;
Russell Bryant
committed
struct ast_variable *var = NULL, *prev = NULL;
/*
* 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 NULL;
}
/*
* 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 */
Russell Bryant
committed
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
Russell Bryant
committed
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);
if (pgresult) {
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, ' ')) {
Russell Bryant
committed
op = " =";
Russell Bryant
committed
op = "";
if (IS_SQL_LIKE_CLAUSE(field->name)) {
escape = ESCAPE_CLAUSE;
}
}
ESCAPE_STRING(escapebuf, field->value);
if (pgresult) {
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. */
Mark Murawki
committed
if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
ast_mutex_unlock(&pgsql_lock);
return NULL;
Mark Murawki
committed
}
Russell Bryant
committed
ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
Russell Bryant
committed
if ((num_rows = PQntuples(result)) > 0) {
int i = 0;
int rowIndex = 0;
int numFields = PQnfields(result);
char **fieldnames = NULL;
ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
Russell Bryant
committed
if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
ast_mutex_unlock(&pgsql_lock);
Russell Bryant
committed
return NULL;
}
for (i = 0; i < numFields; i++)
fieldnames[i] = PQfname(result, i);
for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
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)))) {
Russell Bryant
committed
if (prev) {
Steve Murphy
committed
prev->next = ast_variable_new(fieldnames[i], chunk, "");
Russell Bryant
committed
if (prev->next) {
prev = prev->next;
}
} else {
Steve Murphy
committed
prev = var = ast_variable_new(fieldnames[i], chunk, "");
Russell Bryant
committed
}
}
}
}
Tilghman Lesher
committed
ast_free(fieldnames);
Russell Bryant
committed
} else {
ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
ast_mutex_unlock(&pgsql_lock);
return var;
}
static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, const struct ast_variable *fields)
Jonathan Rose
committed
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;
Russell Bryant
committed
struct ast_variable *var = NULL;
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;
Russell Bryant
committed
if (!table) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
return NULL;
}
Russell Bryant
committed
if (!(cfg = ast_config_new()))
return NULL;
/*
* 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 */
Russell Bryant
committed
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
Russell Bryant
committed
if (pgsqlConn) {
PQfinish(pgsqlConn);
pgsqlConn = NULL;
}
ast_mutex_unlock(&pgsql_lock);
return NULL;
}
initfield = ast_strdupa(field->name);
Russell Bryant
committed
if ((op = strchr(initfield, ' '))) {
*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, ' ')) {
Russell Bryant
committed
op = " =";
Russell Bryant
committed
op = "";
if (IS_SQL_LIKE_CLAUSE(field->name)) {
escape = ESCAPE_CLAUSE;
}
}
ESCAPE_STRING(escapebuf, field->value);
if (pgresult) {
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", table, field->name, op, ast_str_buffer(escapebuf), escape);
while ((field = field->next)) {
escape = "";
if (!strchr(field->name, ' ')) {
Russell Bryant
committed
op = " =";
Russell Bryant
committed
op = "";
if (IS_SQL_LIKE_CLAUSE(field->name)) {
escape = ESCAPE_CLAUSE;
}
}
ESCAPE_STRING(escapebuf, field->value);
if (pgresult) {
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);
Russell Bryant
committed
if (initfield) {
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);
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_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
Russell Bryant
committed
if ((num_rows = PQntuples(result)) > 0) {
int numFields = PQnfields(result);
int i = 0;
int rowIndex = 0;
char **fieldnames = NULL;
ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
Russell Bryant
committed
if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
ast_mutex_unlock(&pgsql_lock);
Russell Bryant
committed
return NULL;
Russell Bryant
committed
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) {
Russell Bryant
committed
continue;
Russell Bryant
committed
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)))) {
Russell Bryant
committed
if (initfield && !strcmp(initfield, fieldnames[i])) {
ast_category_rename(cat, chunk);
}
Steve Murphy
committed
var = ast_variable_new(fieldnames[i], chunk, "");
Russell Bryant
committed
ast_variable_append(cat, var);
}
}
}
ast_category_append(cfg, cat);
}
Tilghman Lesher
committed
ast_free(fieldnames);
ast_debug(1, "PostgreSQL RealTime: Could not find any rows in table %s.\n", table);
ast_mutex_unlock(&pgsql_lock);
return cfg;
}
Tilghman Lesher
committed
static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
const char *lookup, const struct ast_variable *fields)
Jonathan Rose
committed
RAII_VAR(PGresult *, result, NULL, PQclear);
int numrows = 0, pgresult;
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);
Tilghman Lesher
committed
struct tables *table;
struct columns *column = NULL;
/*
* Ignore database from the extconfig.conf since it was
* configured by res_pgsql.conf.
*/
database = dbname;
Tilghman Lesher
committed
if (!tablename) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
Tilghman Lesher
committed
return -1;
}
Mark Murawki
committed
if (!(table = find_table(database, tablename))) {
Tilghman Lesher
committed
ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
Russell Bryant
committed
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);
release_table(table);
/* Get the first parameter and first value in our list of passed paramater/value pairs */
Russell Bryant
committed
ast_log(LOG_WARNING,
"PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
Russell Bryant
committed
if (pgsqlConn) {
PQfinish(pgsqlConn);
pgsqlConn = NULL;
}
ast_mutex_unlock(&pgsql_lock);
release_table(table);
Tilghman Lesher
committed
return -1;
}
/* Check that the column exists in the table */
AST_LIST_TRAVERSE(&table->columns, column, list) {
if (strcmp(column->name, field->name) == 0) {
Tilghman Lesher
committed
break;
}
}
if (!column) {
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);
release_table(table);
Russell Bryant
committed
return -1;
}
/* 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);
if (pgresult) {
ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
ast_mutex_unlock(&pgsql_lock);
release_table(table);
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);
Tilghman Lesher
committed
continue;
}
ESCAPE_STRING(escapebuf, field->value);
if (pgresult) {
ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
ast_mutex_unlock(&pgsql_lock);
release_table(table);
ast_str_append(&sql, 0, ", %s = '%s'", field->name, ast_str_buffer(escapebuf));
release_table(table);
ESCAPE_STRING(escapebuf, lookup);
if (pgresult) {
Mark Murawki
committed
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. */
Mark Murawki
committed
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);
Tilghman Lesher
committed
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.)
Russell Bryant
committed
*/
Russell Bryant
committed
if (numrows >= 0)
return (int) numrows;
return -1;
}
static int update2_pgsql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
{
Jonathan Rose
committed
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;
}
Mark Murawki
committed
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);
release_table(table);
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);
release_table(table);
return -1;
}
ESCAPE_STRING(escapebuf, field->value);
if (pgresult) {
ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
ast_mutex_unlock(&pgsql_lock);
release_table(table);
return -1;
}
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);
continue;
}
ESCAPE_STRING(escapebuf, field->value);
if (pgresult) {
ast_log(LOG_ERROR, "PostgreSQL RealTime: detected invalid input: '%s'\n", field->value);
ast_mutex_unlock(&pgsql_lock);
release_table(table);
return -1;
}
ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", field->name, ast_str_buffer(escapebuf));
}
release_table(table);
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. */
Mark Murawki
committed
if (pgsql_exec(database, tablename, ast_str_buffer(sql), &result) != 0) {
ast_mutex_unlock(&pgsql_lock);
Mark Murawki
committed
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.)
*/
if (numrows >= 0) {
return (int) numrows;
}
return -1;
}
Tilghman Lesher
committed
static int store_pgsql(const char *database, const char *table, const struct ast_variable *fields)
Tilghman Lesher
committed
{
Jonathan Rose
committed
RAII_VAR(PGresult *, result, NULL, PQclear);
Mark Michelson
committed
int numrows;
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);
Tilghman Lesher
committed
int pgresult;
const struct ast_variable *field = fields;
Tilghman Lesher
committed
/*
* Ignore database from the extconfig.conf since it was
* configured by res_pgsql.conf.
*/
database = dbname;
Tilghman Lesher
committed
if (!table) {
ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
Tilghman Lesher
committed
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);
return -1;
}