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