UNREGISTER instead of REGISTER in unload_module().
[asterisk/asterisk.git] / res / res_config_pgsql.c
index e228a41..e4bffd3 100644 (file)
@@ -19,7 +19,7 @@
  * \author Mark Spencer <markster@digium.com>
  * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
  *
- * \arg http://www.postgresql.org
+ * \extref PostgreSQL http://www.postgresql.org
  */
 
 /*** MODULEINFO
@@ -42,13 +42,37 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #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);
 
 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
 
-PGconn *pgsqlConn = NULL;
+static PGconn *pgsqlConn = NULL;
+static int version;
+#define has_schema_support     (version > 70300 ? 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_rwlock_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,23 +84,215 @@ 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);
+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"),
 };
 
-static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
+#define ESCAPE_STRING(buffer, stringname) \
+       do { \
+               int len; \
+               if ((len = strlen(stringname)) > (ast_str_size(buffer) - 1) / 2) { \
+                       ast_str_make_space(&buffer, len * 2 + 1); \
+               } \
+               PQescapeStringConn(pgsqlConn, ast_str_buffer(buffer), stringname, len, &pgresult); \
+       } while (0)
+
+static void destroy_table(struct tables *table)
+{
+       struct columns *column;
+       ast_rwlock_wrlock(&table->lock);
+       while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
+               ast_free(column);
+       }
+       ast_rwlock_unlock(&table->lock);
+       ast_rwlock_destroy(&table->lock);
+       ast_free(table);
+}
+
+static struct tables *find_table(const char *orig_tablename)
+{
+       struct columns *column;
+       struct tables *table;
+       struct ast_str *sql = ast_str_thread_get(&findtable_buf, 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, orig_tablename)) {
+                       ast_debug(1, "Found table in cache; now locking\n");
+                       ast_rwlock_rdlock(&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", orig_tablename);
+
+       /* Not found, scan the table */
+       if (has_schema_support) {
+               char *schemaname, *tablename;
+               if (strchr(orig_tablename, '.')) {
+                       schemaname = ast_strdupa(orig_tablename);
+                       tablename = strchr(schemaname, '.');
+                       *tablename++ = '\0';
+               } else {
+                       schemaname = "";
+                       tablename = ast_strdupa(orig_tablename);
+               }
+
+               /* Escape special characters in schemaname */
+               if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
+                       char *tmp = schemaname, *ptr;
+
+                       ptr = schemaname = alloca(strlen(tmp) * 2 + 1);
+                       for (; *tmp; tmp++) {
+                               if (strchr("\\'", *tmp)) {
+                                       *ptr++ = *tmp;
+                               }
+                               *ptr++ = *tmp;
+                       }
+                       *ptr = '\0';
+               }
+               /* Escape special characters in tablename */
+               if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
+                       char *tmp = tablename, *ptr;
+
+                       ptr = tablename = alloca(strlen(tmp) * 2 + 1);
+                       for (; *tmp; tmp++) {
+                               if (strchr("\\'", *tmp)) {
+                                       *ptr++ = *tmp;
+                               }
+                               *ptr++ = *tmp;
+                       }
+                       *ptr = '\0';
+               }
+
+               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 {
+               /* Escape special characters in tablename */
+               if (strchr(orig_tablename, '\\') || strchr(orig_tablename, '\'')) {
+                       const char *tmp = orig_tablename;
+                       char *ptr;
+
+                       orig_tablename = ptr = alloca(strlen(tmp) * 2 + 1);
+                       for (; *tmp; tmp++) {
+                               if (strchr("\\'", *tmp)) {
+                                       *ptr++ = *tmp;
+                               }
+                               *ptr++ = *tmp;
+                       }
+                       *ptr = '\0';
+               }
+
+               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", orig_tablename);
+       }
+
+       result = PQexec(pgsqlConn, ast_str_buffer(sql));
+       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(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_rwlock_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", 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);
+                       sscanf(flen, "%30d", &column->len);
+                       column->len -= 4;
+               } else {
+                       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);
+       }
+       PQclear(result);
+
+       AST_LIST_INSERT_TAIL(&psql_tables, table, list);
+       ast_rwlock_rdlock(&table->lock);
+       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;
+}
+
+static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, va_list ap)
 {
        PGresult *result = NULL;
-       int num_rows = 0, pgerror;
-       char sql[256], escapebuf[513];
+       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 char *newparam, *newval;
        struct ast_variable *var = NULL, *prev = NULL;
 
-       if (!table) {
+       if (!tablename) {
                ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
                return NULL;
        }
@@ -90,7 +306,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
+               }
                return NULL;
        }
 
@@ -98,15 +314,14 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
           If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
        op = strchr(newparam, ' ') ? "" : " =";
 
-       PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-       if (pgerror) {
+       ESCAPE_STRING(escapebuf, newval);
+       if (pgresult) {
                ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
                va_end(ap);
                return NULL;
        }
 
-       snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
-                        escapebuf);
+       ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, newparam, op, ast_str_buffer(escapebuf));
        while ((newparam = va_arg(ap, const char *))) {
                newval = va_arg(ap, const char *);
                if (!strchr(newparam, ' '))
@@ -114,15 +329,14 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                else
                        op = "";
 
-               PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-               if (pgerror) {
+               ESCAPE_STRING(escapebuf, newval);
+               if (pgresult) {
                        ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
                        va_end(ap);
                        return NULL;
                }
 
-               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
-                                op, escapebuf);
+               ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf));
        }
        va_end(ap);
 
@@ -133,10 +347,10 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                return NULL;
        }
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
                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);
+                               "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
+               ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
                ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
                ast_mutex_unlock(&pgsql_lock);
                return NULL;
@@ -146,8 +360,8 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                        && 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", sql);
+                                       "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, 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);
@@ -155,7 +369,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                }
        }
 
-       ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql);
+       ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
 
        if ((num_rows = PQntuples(result)) > 0) {
                int i = 0;
@@ -192,7 +406,7 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                }
                ast_free(fieldnames);
        } else {
-               ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s.\n", table);
+               ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
        }
 
        ast_mutex_unlock(&pgsql_lock);
@@ -204,8 +418,9 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
 {
        PGresult *result = NULL;
-       int num_rows = 0, pgerror;
-       char sql[256], escapebuf[513];
+       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 char *initfield = NULL;
        char *stringp;
        char *chunk;
@@ -232,7 +447,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
+               }
                return NULL;
        }
 
@@ -249,15 +464,14 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
        else
                op = "";
 
-       PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-       if (pgerror) {
+       ESCAPE_STRING(escapebuf, newval);
+       if (pgresult) {
                ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
                va_end(ap);
                return NULL;
        }
 
-       snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
-                        escapebuf);
+       ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(escapebuf));
        while ((newparam = va_arg(ap, const char *))) {
                newval = va_arg(ap, const char *);
                if (!strchr(newparam, ' '))
@@ -265,19 +479,18 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                else
                        op = "";
 
-               PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-               if (pgerror) {
+               ESCAPE_STRING(escapebuf, newval);
+               if (pgresult) {
                        ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
                        va_end(ap);
                        return NULL;
                }
 
-               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
-                                op, escapebuf);
+               ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf));
        }
 
        if (initfield) {
-               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
+               ast_str_append(&sql, 0, " ORDER BY %s", initfield);
        }
 
        va_end(ap);
@@ -289,10 +502,10 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                return NULL;
        }
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
                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);
+                               "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\n", PQerrorMessage(pgsqlConn));
                ast_mutex_unlock(&pgsql_lock);
                return NULL;
@@ -302,8 +515,8 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                        && 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", sql);
+                                       "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);
@@ -311,7 +524,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                }
        }
 
-       ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql);
+       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);
@@ -360,19 +573,27 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
        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];
+       int numrows = 0, pgresult;
        const char *newparam, *newval;
+       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;
 
-       if (!table) {
+       if (!tablename) {
                ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
                return -1;
        }
 
+       if (!(table = find_table(tablename))) {
+               ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
+               return -1;
+       }
+
        /* Get the first parameter and first value in our list of passed paramater/value pairs */
        newparam = va_arg(ap, const char *);
        newval = va_arg(ap, const char *);
@@ -382,47 +603,67 @@ static int update_pgsql(const char *database, const char *table, const char *key
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
+               }
+               release_table(table);
+               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);
+               release_table(table);
                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 */
 
-       PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-       if (pgerror) {
+       ESCAPE_STRING(escapebuf, newval);
+       if (pgresult) {
                ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
                va_end(ap);
+               release_table(table);
                return -1;
        }
-       snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, escapebuf);
+       ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(escapebuf));
 
        while ((newparam = va_arg(ap, const char *))) {
                newval = va_arg(ap, const char *);
 
-               PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
-               if (pgerror) {
+               if (!find_column(table, newparam)) {
+                       ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
+                       continue;
+               }
+
+               ESCAPE_STRING(escapebuf, newval);
+               if (pgresult) {
                        ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
                        va_end(ap);
+                       release_table(table);
                        return -1;
                }
 
-               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam,
-                                escapebuf);
+               ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(escapebuf));
        }
        va_end(ap);
+       release_table(table);
 
-       PQescapeStringConn(pgsqlConn, escapebuf, lookup, (sizeof(escapebuf) - 1) / 2, &pgerror);
-       if (pgerror) {
+       ESCAPE_STRING(escapebuf, lookup);
+       if (pgresult) {
                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_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(escapebuf));
 
-       ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", sql);
+       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. */
        ast_mutex_lock(&pgsql_lock);
@@ -431,10 +672,10 @@ static int update_pgsql(const char *database, const char *table, const char *key
                return -1;
        }
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
                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", ast_str_buffer(sql));
                ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
                ast_mutex_unlock(&pgsql_lock);
                return -1;
@@ -445,7 +686,7 @@ static int update_pgsql(const char *database, const char *table, const char *key
                        && 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", 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);
@@ -456,7 +697,7 @@ static int update_pgsql(const char *database, const char *table, const char *key
        numrows = atoi(PQcmdTuples(result));
        ast_mutex_unlock(&pgsql_lock);
 
-       ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, table);
+       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
@@ -470,14 +711,145 @@ static int update_pgsql(const char *database, const char *table, const char *key
        return -1;
 }
 
+static int update2_pgsql(const char *database, const char *tablename, va_list ap)
+{
+       PGresult *result = NULL;
+       int numrows = 0, pgresult, first = 1;
+       struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
+       const char *newparam, *newval;
+       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;
+
+       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(tablename))) {
+               ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
+               return -1;
+       }
+
+       ast_str_set(&sql, 0, "UPDATE %s SET ", tablename);
+       ast_str_set(&where, 0, "WHERE");
+
+       while ((newparam = va_arg(ap, const char *))) {
+               if (!find_column(table, newparam)) {
+                       ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", newparam, tablename, database);
+                       release_table(table);
+                       return -1;
+               }
+                       
+               newval = va_arg(ap, const char *);
+               ESCAPE_STRING(escapebuf, newval);
+               if (pgresult) {
+                       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+                       release_table(table);
+                       ast_free(sql);
+                       return -1;
+               }
+               ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, 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;
+               }
+               release_table(table);
+               return -1;
+       }
+
+       /* Now retrieve the columns to update */
+       first = 1;
+       while ((newparam = va_arg(ap, const char *))) {
+               newval = va_arg(ap, const char *);
+
+               /* If the column is not within the table, then skip it */
+               if (!find_column(table, newparam)) {
+                       ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", newparam, tablename, database);
+                       continue;
+               }
+
+               ESCAPE_STRING(escapebuf, newval);
+               if (pgresult) {
+                       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+                       release_table(table);
+                       ast_free(sql);
+                       return -1;
+               }
+
+               ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", newparam, 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; 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, ast_str_buffer(sql)))) {
+               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\n", PQerrorMessage(pgsqlConn));
+               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.)
+        */
+
+       if (numrows >= 0) {
+               return (int) numrows;
+       }
+
+       return -1;
+}
+
 static int store_pgsql(const char *database, const char *table, va_list ap)
 {
        PGresult *result = NULL;
        Oid insertid;
-       char sql[256];
-       char params[256];
-       char vals[256];
-       char buf[256];
+       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);
        int pgresult;
        const char *newparam, *newval;
 
@@ -495,7 +867,7 @@ static int store_pgsql(const char *database, const char *table, va_list ap)
                if (pgsqlConn) {
                        PQfinish(pgsqlConn);
                        pgsqlConn = NULL;
-               };
+               }
                return -1;
        }
 
@@ -508,26 +880,26 @@ static int store_pgsql(const char *database, const char *table, va_list ap)
 
        /* 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 */
-       PQescapeStringConn(pgsqlConn, buf, newparam, sizeof(newparam), &pgresult);
-       snprintf(params, sizeof(params), "%s", buf);
-       PQescapeStringConn(pgsqlConn, buf, newval, sizeof(newval), &pgresult);
-       snprintf(vals, sizeof(vals), "'%s'", buf);
+       ESCAPE_STRING(buf, newparam);
+       ast_str_set(&sql1, 0, "INSERT INTO %s (%s", table, ast_str_buffer(buf));
+       ESCAPE_STRING(buf, newval);
+       ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
        while ((newparam = va_arg(ap, const char *))) {
                newval = va_arg(ap, const char *);
-               PQescapeStringConn(pgsqlConn, buf, newparam, sizeof(newparam), &pgresult);
-               snprintf(params + strlen(params), sizeof(params) - strlen(params), ", %s", buf);
-               PQescapeStringConn(pgsqlConn, buf, newval, sizeof(newval), &pgresult);
-               snprintf(vals + strlen(vals), sizeof(vals) - strlen(vals), ", '%s'", buf);
+               ESCAPE_STRING(buf, newparam);
+               ast_str_append(&sql1, 0, ", %s", ast_str_buffer(buf));
+               ESCAPE_STRING(buf, newval);
+               ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
        }
        va_end(ap);
-       snprintf(sql, sizeof(sql), "INSERT INTO (%s) VALUES (%s)", params, vals);
+       ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
 
-       ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", sql);
+       ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql1));
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql1)))) {
                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", ast_str_buffer(sql1));
                ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
                ast_mutex_unlock(&pgsql_lock);
                return -1;
@@ -538,7 +910,7 @@ static int store_pgsql(const char *database, const char *table, va_list ap)
                        && 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", ast_str_buffer(sql1));
                        ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
                                                PQresultErrorMessage(result), PQresStatus(result_status));
                        ast_mutex_unlock(&pgsql_lock);
@@ -568,8 +940,8 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
        PGresult *result = NULL;
        int numrows = 0;
        int pgresult;
-       char sql[256];
-       char buf[256], buf2[256];
+       struct ast_str *sql = ast_str_thread_get(&sql_buf, 256);
+       struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60);
        const char *newparam, *newval;
 
        if (!table) {
@@ -602,23 +974,23 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
        /* 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 */
 
-       PQescapeStringConn(pgsqlConn, buf, keyfield, sizeof(keyfield), &pgresult);
-       PQescapeStringConn(pgsqlConn, buf2, lookup, sizeof(lookup), &pgresult);
-       snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE %s = '%s'", table, buf, buf2);
+       ESCAPE_STRING(buf1, keyfield);
+       ESCAPE_STRING(buf2, lookup);
+       ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, ast_str_buffer(buf1), ast_str_buffer(buf2));
        while ((newparam = va_arg(ap, const char *))) {
                newval = va_arg(ap, const char *);
-               PQescapeStringConn(pgsqlConn, buf, newparam, sizeof(newparam), &pgresult);
-               PQescapeStringConn(pgsqlConn, buf2, newval, sizeof(newval), &pgresult);
-               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s = '%s'", buf, buf2);
+               ESCAPE_STRING(buf1, newparam);
+               ESCAPE_STRING(buf2, newval);
+               ast_str_append(&sql, 0, " AND %s = '%s'", ast_str_buffer(buf1), ast_str_buffer(buf2));
        }
        va_end(ap);
 
-       ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", sql);
+       ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
 
-       if (!(result = PQexec(pgsqlConn, sql))) {
+       if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
                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", ast_str_buffer(sql));
                ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
                ast_mutex_unlock(&pgsql_lock);
                return -1;
@@ -629,7 +1001,7 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
                        && 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", 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);
@@ -663,9 +1035,7 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
        long num_rows;
        struct ast_variable *new_v;
        struct ast_category *cur_cat = NULL;
-       char sqlbuf[1024] = "";
-       char *sql = sqlbuf;
-       size_t sqlleft = sizeof(sqlbuf);
+       struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
        char last[80] = "";
        int last_cat_metric = 0;
 
@@ -676,11 +1046,11 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
                return NULL;
        }
 
-       ast_build_string(&sql, &sqlleft, "SELECT category, var_name, var_val, cat_metric FROM %s ", table);
-       ast_build_string(&sql, &sqlleft, "WHERE filename='%s' and commented=0", file);
-       ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
+       ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s "
+                       "WHERE filename='%s' and commented=0"
+                       "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ", table, file);
 
-       ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", sqlbuf);
+       ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
 
        /* We now have our complete statement; Lets connect to the server and execute it. */
        ast_mutex_lock(&pgsql_lock);
@@ -689,10 +1059,10 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
                return NULL;
        }
 
-       if (!(result = PQexec(pgsqlConn, sqlbuf))) {
+       if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
                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);
+                               "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\n", PQerrorMessage(pgsqlConn));
                ast_mutex_unlock(&pgsql_lock);
                return NULL;
@@ -703,7 +1073,7 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
                        && 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", 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);
@@ -752,6 +1122,149 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
        return cfg;
 }
 
+static int require_pgsql(const char *database, const char *tablename, va_list ap)
+{
+       struct columns *column;
+       struct tables *table = find_table(tablename);
+       char *elm;
+       int type, size, res = 0;
+
+       if (!table) {
+               ast_log(LOG_WARNING, "Table %s not found in database.  This table should exist if you're using realtime.\n", tablename);
+               return -1;
+       }
+
+       while ((elm = va_arg(ap, char *))) {
+               type = va_arg(ap, require_type);
+               size = va_arg(ap, int);
+               AST_LIST_TRAVERSE(&table->columns, column, list) {
+                       if (strcmp(column->name, elm) == 0) {
+                               /* Char can hold anything, as long as it is large enough */
+                               if ((strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0 || strcmp(column->type, "bpchar") == 0)) {
+                                       if ((size > column->len) && column->len != -1) {
+                                               ast_log(LOG_WARNING, "Column '%s' should be at least %d long, but is only %d long.\n", column->name, size, column->len);
+                                               res = -1;
+                                       }
+                               } else if (strncmp(column->type, "int", 3) == 0) {
+                                       int typesize = atoi(column->type + 3);
+                                       /* Integers can hold only other integers */
+                                       if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
+                                               type == RQ_INTEGER4 || type == RQ_UINTEGER4 ||
+                                               type == RQ_INTEGER3 || type == RQ_UINTEGER3 ||
+                                               type == RQ_UINTEGER2) && typesize == 2) {
+                                               ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
+                                               res = -1;
+                                       } else if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
+                                               type == RQ_UINTEGER4) && typesize == 4) {
+                                               ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
+                                               res = -1;
+                                       } else if (type == RQ_CHAR || type == RQ_DATETIME || type == RQ_FLOAT || type == RQ_DATE) {
+                                               ast_log(LOG_WARNING, "Column '%s' is of the incorrect type: (need %s(%d) but saw %s)\n",
+                                                       column->name,
+                                                               type == RQ_CHAR ? "char" :
+                                                               type == RQ_DATETIME ? "datetime" :
+                                                               type == RQ_DATE ? "date" :
+                                                               type == RQ_FLOAT ? "float" :
+                                                               "a rather stiff drink ",
+                                                       size, column->type);
+                                               res = -1;
+                                       }
+                               } else if (strncmp(column->type, "float", 5) == 0 && !ast_rq_is_int(type) && type != RQ_FLOAT) {
+                                       ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
+                                       res = -1;
+                               } else { /* There are other types that no module implements yet */
+                                       ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
+                                       res = -1;
+                               }
+                               break;
+                       }
+               }
+
+               if (!column) {
+                       if (requirements == RQ_WARN) {
+                               ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
+                       } else {
+                               struct ast_str *sql = ast_str_create(100);
+                               char fieldtype[15];
+                               PGresult *result;
+
+                               if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
+                                       /* Size is minimum length; make it at least 50% greater,
+                                        * just to be sure, because PostgreSQL doesn't support
+                                        * resizing columns. */
+                                       snprintf(fieldtype, sizeof(fieldtype), "CHAR(%d)",
+                                               size < 15 ? size * 2 :
+                                               (size * 3 / 2 > 255) ? 255 : size * 3 / 2);
+                               } else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) {
+                                       snprintf(fieldtype, sizeof(fieldtype), "INT2");
+                               } else if (type == RQ_UINTEGER2 || type == RQ_INTEGER3 || type == RQ_UINTEGER3 || type == RQ_INTEGER4) {
+                                       snprintf(fieldtype, sizeof(fieldtype), "INT4");
+                               } else if (type == RQ_UINTEGER4 || type == RQ_INTEGER8) {
+                                       snprintf(fieldtype, sizeof(fieldtype), "INT8");
+                               } else if (type == RQ_UINTEGER8) {
+                                       /* No such type on PostgreSQL */
+                                       snprintf(fieldtype, sizeof(fieldtype), "CHAR(20)");
+                               } else if (type == RQ_FLOAT) {
+                                       snprintf(fieldtype, sizeof(fieldtype), "FLOAT8");
+                               } else if (type == RQ_DATE) {
+                                       snprintf(fieldtype, sizeof(fieldtype), "DATE");
+                               } else if (type == RQ_DATETIME) {
+                                       snprintf(fieldtype, sizeof(fieldtype), "TIMESTAMP");
+                               } else {
+                                       ast_log(LOG_ERROR, "Unrecognized request type %d\n", type);
+                                       ast_free(sql);
+                                       continue;
+                               }
+                               ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, fieldtype);
+                               ast_debug(1, "About to lock pgsql_lock (running alter on table '%s' to add column '%s')\n", tablename, elm);
+
+                               ast_mutex_lock(&pgsql_lock);
+                               if (!pgsql_reconnect(database)) {
+                                       ast_mutex_unlock(&pgsql_lock);
+                                       ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
+                                       ast_free(sql);
+                                       continue;
+                               }
+
+                               ast_debug(1, "About to run ALTER query on table '%s' to add column '%s'\n", tablename, elm);
+                               result = PQexec(pgsqlConn, ast_str_buffer(sql));
+                               ast_debug(1, "Finished running ALTER query on table '%s'\n", tablename);
+                               if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+                                       ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
+                               }
+                               PQclear(result);
+                               ast_mutex_unlock(&pgsql_lock);
+
+                               ast_free(sql);
+                       }
+               }
+       }
+       release_table(table);
+       return res;
+}
+
+static int unload_pgsql(const char *database, const char *tablename)
+{
+       struct tables *cur;
+       ast_debug(2, "About to lock table cache list\n");
+       AST_LIST_LOCK(&psql_tables);
+       ast_debug(2, "About to traverse table cache list\n");
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&psql_tables, cur, list) {
+               if (strcmp(cur->name, tablename) == 0) {
+                       ast_debug(2, "About to remove matching cache entry\n");
+                       AST_LIST_REMOVE_CURRENT(list);
+                       ast_debug(2, "About to destroy matching cache entry\n");
+                       destroy_table(cur);
+                       ast_debug(1, "Cache entry '%s@%s' destroyed\n", tablename, database);
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END
+       AST_LIST_UNLOCK(&psql_tables);
+       ast_debug(2, "About to return\n");
+       return cur ? 0 : -1;
+}
+
 static struct ast_config_engine pgsql_engine = {
        .name = "pgsql",
        .load_func = config_pgsql,
@@ -759,7 +1272,10 @@ static struct ast_config_engine pgsql_engine = {
        .realtime_multi_func = realtime_multi_pgsql,
        .store_func = store_pgsql,
        .destroy_func = destroy_pgsql,
-       .update_func = update_pgsql
+       .update_func = update_pgsql,
+       .update2_func = update2_pgsql,
+       .require_func = require_pgsql,
+       .unload_func = unload_pgsql,
 };
 
 static int load_module(void)
@@ -769,13 +1285,14 @@ static int load_module(void)
 
        ast_config_engine_register(&pgsql_engine);
        ast_verb(1, "PostgreSQL RealTime driver loaded.\n");
-       ast_cli_register_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry));
+       ast_cli_register_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
 
        return 0;
 }
 
 static int unload_module(void)
 {
+       struct tables *table;
        /* Acquire control before doing anything to the module itself. */
        ast_mutex_lock(&pgsql_lock);
 
@@ -783,10 +1300,17 @@ static int unload_module(void)
                PQfinish(pgsqlConn);
                pgsqlConn = NULL;
        }
-       ast_cli_unregister_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry));
+       ast_cli_unregister_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
        ast_config_engine_deregister(&pgsql_engine);
        ast_verb(1, "PostgreSQL RealTime unloaded.\n");
 
+       /* Destroy cached table info */
+       AST_LIST_LOCK(&psql_tables);
+       while ((table = AST_LIST_REMOVE_HEAD(&psql_tables, list))) {
+               destroy_table(table);
+       }
+       AST_LIST_UNLOCK(&psql_tables);
+
        /* Unlock so something else can destroy the lock. */
        ast_mutex_unlock(&pgsql_lock);
 
@@ -800,16 +1324,18 @@ static int reload(void)
        return 0;
 }
 
-static int parse_config(int reload)
+static int parse_config(int is_reload)
 {
        struct ast_config *config;
        const char *s;
-       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+       struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 
-       if ((config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
+       config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags);
+       if (config == CONFIG_STATUS_FILEUNCHANGED) {
                return 0;
+       }
 
-       if (!config) {
+       if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
                ast_log(LOG_WARNING, "Unable to load config %s\n", RES_CONFIG_PGSQL_CONF);
                return 0;
        }
@@ -870,6 +1396,17 @@ static int parse_config(int reload)
        } else {
                ast_copy_string(dbsock, s, sizeof(dbsock));
        }
+
+       if (!(s = ast_variable_retrieve(config, "general", "requirements"))) {
+               ast_log(LOG_WARNING,
+                               "PostgreSQL RealTime: no requirements setting found, using 'warn' as default.\n");
+               requirements = RQ_WARN;
+       } else if (!strcasecmp(s, "createclose")) {
+               requirements = RQ_CREATECLOSE;
+       } else if (!strcasecmp(s, "createchar")) {
+               requirements = RQ_CREATECHAR;
+       }
+
        ast_config_destroy(config);
 
        if (option_debug) {
@@ -920,9 +1457,9 @@ static int pgsql_reconnect(const char *database)
                if (!ast_strlen_zero(dbpass))
                        ast_str_append(&connInfo, 0, " password=%s", dbpass);
 
-               ast_debug(1, "%u connInfo=%s\n", (unsigned int)connInfo->len, connInfo->str);
-               pgsqlConn = PQconnectdb(connInfo->str);
-               ast_debug(1, "%u connInfo=%s\n", (unsigned int)connInfo->len, connInfo->str);
+               ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
+               pgsqlConn = PQconnectdb(ast_str_buffer(connInfo));
+               ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
                ast_free(connInfo);
                connInfo = NULL;
 
@@ -930,6 +1467,7 @@ static int pgsql_reconnect(const char *database)
                if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
                        ast_debug(1, "PostgreSQL RealTime: Successfully connected to database.\n");
                        connect_time = time(NULL);
+                       version = PQserverVersion(pgsqlConn);
                        return 1;
                } else {
                        ast_log(LOG_ERROR,
@@ -943,52 +1481,106 @@ static int pgsql_reconnect(const char *database)
        }
 }
 
+static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct tables *cur;
+       int l, which;
+       char *ret = NULL;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "realtime show pgsql cache";
+               e->usage =
+                       "Usage: realtime show pgsql cache [<table>]\n"
+                       "       Shows table cache for the PostgreSQL RealTime driver\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->argc != 4) {
+                       return NULL;
+               }
+               l = strlen(a->word);
+               which = 0;
+               AST_LIST_LOCK(&psql_tables);
+               AST_LIST_TRAVERSE(&psql_tables, cur, list) {
+                       if (!strncasecmp(a->word, cur->name, l) && ++which > a->n) {
+                               ret = ast_strdup(cur->name);
+                               break;
+                       }
+               }
+               AST_LIST_UNLOCK(&psql_tables);
+               return ret;
+       }
+
+       if (a->argc == 4) {
+               /* List of tables */
+               AST_LIST_LOCK(&psql_tables);
+               AST_LIST_TRAVERSE(&psql_tables, cur, list) {
+                       ast_cli(a->fd, "%s\n", cur->name);
+               }
+               AST_LIST_UNLOCK(&psql_tables);
+       } else if (a->argc == 5) {
+               /* List of columns */
+               if ((cur = find_table(a->argv[4]))) {
+                       struct columns *col;
+                       ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[4]);
+                       ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s %-8.8s\n", "Name", "Type", "Len", "Nullable");
+                       AST_LIST_TRAVERSE(&cur->columns, col, list) {
+                               ast_cli(a->fd, "%-20.20s %-20.20s %3d %-8.8s\n", col->name, col->type, col->len, col->notnull ? "NOT NULL" : "");
+                       }
+                       release_table(cur);
+               } else {
+                       ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
+               }
+       }
+       return 0;
+}
+
 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       char status[256], status2[100] = "";
-       int ctime = time(NULL) - connect_time;
+       char status[256], credentials[100] = "";
+       int ctimesec = time(NULL) - connect_time;
 
        switch (cmd) {
        case CLI_INIT:
-               e->command = "realtime pgsql status";
+               e->command = "realtime show pgsql status";
                e->usage =
-                       "Usage: realtime pgsql status\n"
+                       "Usage: realtime show pgsql status\n"
                        "       Shows connection information for the PostgreSQL RealTime driver\n";
                return NULL;
        case CLI_GENERATE:
                return NULL;
        }
 
-       if (a->argc != 3)
+       if (a->argc != 4)
                return CLI_SHOWUSAGE;
 
        if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
                if (!ast_strlen_zero(dbhost))
-                       snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, dbport);
+                       snprintf(status, sizeof(status), "Connected to %s@%s, port %d", dbname, dbhost, dbport);
                else if (!ast_strlen_zero(dbsock))
-                       snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock);
+                       snprintf(status, sizeof(status), "Connected to %s on socket file %s", dbname, dbsock);
                else
-                       snprintf(status, 255, "Connected to %s@%s", dbname, dbhost);
+                       snprintf(status, sizeof(status), "Connected to %s@%s", dbname, dbhost);
 
                if (!ast_strlen_zero(dbuser))
-                       snprintf(status2, 99, " with username %s", dbuser);
+                       snprintf(credentials, sizeof(credentials), " with username %s", dbuser);
 
-               if (ctime > 31536000)
+               if (ctimesec > 31536000)
                        ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
-                                       status, status2, ctime / 31536000, (ctime % 31536000) / 86400,
-                                       (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
-               else if (ctime > 86400)
+                                       status, credentials, ctimesec / 31536000, (ctimesec % 31536000) / 86400,
+                                       (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
+               else if (ctimesec > 86400)
                        ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
-                                       status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60,
-                                       ctime % 60);
-               else if (ctime > 3600)
-                       ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2,
-                                       ctime / 3600, (ctime % 3600) / 60, ctime % 60);
-               else if (ctime > 60)
-                       ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60,
-                                       ctime % 60);
+                                       credentials, ctimesec / 86400, (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60,
+                                       ctimesec % 60);
+               else if (ctimesec > 3600)
+                       ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, credentials,
+                                       ctimesec / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
+               else if (ctimesec > 60)
+                       ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, credentials, ctimesec / 60,
+                                       ctimesec % 60);
                else
-                       ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
+                       ast_cli(a->fd, "%s%s for %d seconds.\n", status, credentials, ctimesec);
 
                return CLI_SUCCESS;
        } else {
@@ -997,7 +1589,7 @@ static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd,
 }
 
 /* needs usecount semantics defined */
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "PostgreSQL RealTime Configuration Driver",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "PostgreSQL RealTime Configuration Driver",
                .load = load_module,
                .unload = unload_module,
                .reload = reload