Tue Aug 24 2010 19:41:32

Asterisk developer's documentation


res_config_pgsql.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- A telephony toolkit for Linux.
00003  *
00004  * Copyright (C) 1999-2010, Digium, Inc.
00005  *
00006  * Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
00007  * Mark Spencer <markster@digium.com>  - Asterisk Author
00008  * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
00009  *
00010  * res_config_pgsql.c <PostgreSQL plugin for RealTime configuration engine>
00011  *
00012  * v1.0   - (07-11-05) - Initial version based on res_config_mysql v2.0
00013  */
00014 
00015 /*! \file
00016  *
00017  * \brief PostgreSQL plugin for Asterisk RealTime Architecture
00018  *
00019  * \author Mark Spencer <markster@digium.com>
00020  * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor
00021  *
00022  * \arg http://www.postgresql.org
00023  */
00024 
00025 /*** MODULEINFO
00026    <depend>pgsql</depend>
00027  ***/
00028 
00029 #include "asterisk.h"
00030 
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 277777 $")
00032 
00033 #include <libpq-fe.h>         /* PostgreSQL */
00034 
00035 #include "asterisk/file.h"
00036 #include "asterisk/channel.h"
00037 #include "asterisk/pbx.h"
00038 #include "asterisk/config.h"
00039 #include "asterisk/module.h"
00040 #include "asterisk/lock.h"
00041 #include "asterisk/utils.h"
00042 #include "asterisk/cli.h"
00043 
00044 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
00045 AST_THREADSTORAGE(sql_buf);
00046 AST_THREADSTORAGE(findtable_buf);
00047 AST_THREADSTORAGE(where_buf);
00048 AST_THREADSTORAGE(escapebuf_buf);
00049 AST_THREADSTORAGE(semibuf_buf);
00050 
00051 #define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
00052 
00053 PGconn *pgsqlConn = NULL;
00054 static int version;
00055 #define has_schema_support (version > 70300 ? 1 : 0)
00056 
00057 #define MAX_DB_OPTION_SIZE 64
00058 
00059 struct columns {
00060    char *name;
00061    char *type;
00062    int len;
00063    unsigned int notnull:1;
00064    unsigned int hasdefault:1;
00065    AST_LIST_ENTRY(columns) list;
00066 };
00067 
00068 struct tables {
00069    ast_rwlock_t lock;
00070    AST_LIST_HEAD_NOLOCK(psql_columns, columns) columns;
00071    AST_LIST_ENTRY(tables) list;
00072    char name[0];
00073 };
00074 
00075 static AST_LIST_HEAD_STATIC(psql_tables, tables);
00076 
00077 static char dbhost[MAX_DB_OPTION_SIZE] = "";
00078 static char dbuser[MAX_DB_OPTION_SIZE] = "";
00079 static char dbpass[MAX_DB_OPTION_SIZE] = "";
00080 static char dbname[MAX_DB_OPTION_SIZE] = "";
00081 static char dbsock[MAX_DB_OPTION_SIZE] = "";
00082 static int dbport = 5432;
00083 static time_t connect_time = 0;
00084 
00085 static int parse_config(int reload);
00086 static int pgsql_reconnect(const char *database);
00087 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00088 static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00089 
00090 enum { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR } requirements;
00091 
00092 static struct ast_cli_entry cli_realtime[] = {
00093    AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"),
00094    AST_CLI_DEFINE(handle_cli_realtime_pgsql_cache, "Shows cached tables within the PostgreSQL realtime driver"),
00095 };
00096 
00097 #define ESCAPE_STRING(buffer, stringname) \
00098    do { \
00099       int len = strlen(stringname); \
00100       struct ast_str *semi = ast_str_thread_get(&semibuf_buf, len * 3 + 1); \
00101       const char *chunk = stringname; \
00102       ast_str_reset(semi); \
00103       for (; *chunk; chunk++) { \
00104          if (strchr(";^", *chunk)) { \
00105             ast_str_append(&semi, 0, "^%02hhX", *chunk); \
00106          } else { \
00107             ast_str_append(&semi, 0, "%c", *chunk); \
00108          } \
00109       } \
00110       if (ast_str_strlen(semi) > (ast_str_size(buffer) - 1) / 2) { \
00111          ast_str_make_space(&buffer, ast_str_strlen(semi) * 2 + 1); \
00112       } \
00113       PQescapeStringConn(pgsqlConn, ast_str_buffer(buffer), ast_str_buffer(semi), ast_str_size(buffer), &pgresult); \
00114    } while (0)
00115 
00116 static void destroy_table(struct tables *table)
00117 {
00118    struct columns *column;
00119    ast_rwlock_wrlock(&table->lock);
00120    while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
00121       ast_free(column);
00122    }
00123    ast_rwlock_unlock(&table->lock);
00124    ast_rwlock_destroy(&table->lock);
00125    ast_free(table);
00126 }
00127 
00128 static struct tables *find_table(const char *orig_tablename)
00129 {
00130    struct columns *column;
00131    struct tables *table;
00132    struct ast_str *sql = ast_str_thread_get(&findtable_buf, 330);
00133    char *pgerror;
00134    PGresult *result;
00135    char *fname, *ftype, *flen, *fnotnull, *fdef;
00136    int i, rows;
00137 
00138    AST_LIST_LOCK(&psql_tables);
00139    AST_LIST_TRAVERSE(&psql_tables, table, list) {
00140       if (!strcasecmp(table->name, orig_tablename)) {
00141          ast_debug(1, "Found table in cache; now locking\n");
00142          ast_rwlock_rdlock(&table->lock);
00143          ast_debug(1, "Lock cached table; now returning\n");
00144          AST_LIST_UNLOCK(&psql_tables);
00145          return table;
00146       }
00147    }
00148 
00149    ast_debug(1, "Table '%s' not found in cache, querying now\n", orig_tablename);
00150 
00151    /* Not found, scan the table */
00152    if (has_schema_support) {
00153       char *schemaname, *tablename;
00154       if (strchr(orig_tablename, '.')) {
00155          schemaname = ast_strdupa(orig_tablename);
00156          tablename = strchr(schemaname, '.');
00157          *tablename++ = '\0';
00158       } else {
00159          schemaname = "";
00160          tablename = ast_strdupa(orig_tablename);
00161       }
00162 
00163       /* Escape special characters in schemaname */
00164       if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
00165          char *tmp = schemaname, *ptr;
00166 
00167          ptr = schemaname = alloca(strlen(tmp) * 2 + 1);
00168          for (; *tmp; tmp++) {
00169             if (strchr("\\'", *tmp)) {
00170                *ptr++ = *tmp;
00171             }
00172             *ptr++ = *tmp;
00173          }
00174          *ptr = '\0';
00175       }
00176       /* Escape special characters in tablename */
00177       if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
00178          char *tmp = tablename, *ptr;
00179 
00180          ptr = tablename = alloca(strlen(tmp) * 2 + 1);
00181          for (; *tmp; tmp++) {
00182             if (strchr("\\'", *tmp)) {
00183                *ptr++ = *tmp;
00184             }
00185             *ptr++ = *tmp;
00186          }
00187          *ptr = '\0';
00188       }
00189 
00190       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",
00191          tablename,
00192          ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
00193    } else {
00194       /* Escape special characters in tablename */
00195       if (strchr(orig_tablename, '\\') || strchr(orig_tablename, '\'')) {
00196          const char *tmp = orig_tablename;
00197          char *ptr;
00198 
00199          orig_tablename = ptr = alloca(strlen(tmp) * 2 + 1);
00200          for (; *tmp; tmp++) {
00201             if (strchr("\\'", *tmp)) {
00202                *ptr++ = *tmp;
00203             }
00204             *ptr++ = *tmp;
00205          }
00206          *ptr = '\0';
00207       }
00208 
00209       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);
00210    }
00211 
00212    result = PQexec(pgsqlConn, ast_str_buffer(sql));
00213    ast_debug(1, "Query of table structure complete.  Now retrieving results.\n");
00214    if (PQresultStatus(result) != PGRES_TUPLES_OK) {
00215       pgerror = PQresultErrorMessage(result);
00216       ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
00217       PQclear(result);
00218       AST_LIST_UNLOCK(&psql_tables);
00219       return NULL;
00220    }
00221 
00222    if (!(table = ast_calloc(1, sizeof(*table) + strlen(orig_tablename) + 1))) {
00223       ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
00224       AST_LIST_UNLOCK(&psql_tables);
00225       return NULL;
00226    }
00227    strcpy(table->name, orig_tablename); /* SAFE */
00228    ast_rwlock_init(&table->lock);
00229    AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
00230 
00231    rows = PQntuples(result);
00232    for (i = 0; i < rows; i++) {
00233       fname = PQgetvalue(result, i, 0);
00234       ftype = PQgetvalue(result, i, 1);
00235       flen = PQgetvalue(result, i, 2);
00236       fnotnull = PQgetvalue(result, i, 3);
00237       fdef = PQgetvalue(result, i, 4);
00238       ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
00239 
00240       if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + 2))) {
00241          ast_log(LOG_ERROR, "Unable to allocate column element for %s, %s\n", orig_tablename, fname);
00242          destroy_table(table);
00243          AST_LIST_UNLOCK(&psql_tables);
00244          return NULL;
00245       }
00246 
00247       if (strcmp(flen, "-1") == 0) {
00248          /* Some types, like chars, have the length stored in a different field */
00249          flen = PQgetvalue(result, i, 5);
00250          sscanf(flen, "%30d", &column->len);
00251          column->len -= 4;
00252       } else {
00253          sscanf(flen, "%30d", &column->len);
00254       }
00255       column->name = (char *)column + sizeof(*column);
00256       column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
00257       strcpy(column->name, fname);
00258       strcpy(column->type, ftype);
00259       if (*fnotnull == 't') {
00260          column->notnull = 1;
00261       } else {
00262          column->notnull = 0;
00263       }
00264       if (!ast_strlen_zero(fdef)) {
00265          column->hasdefault = 1;
00266       } else {
00267          column->hasdefault = 0;
00268       }
00269       AST_LIST_INSERT_TAIL(&table->columns, column, list);
00270    }
00271    PQclear(result);
00272 
00273    AST_LIST_INSERT_TAIL(&psql_tables, table, list);
00274    ast_rwlock_rdlock(&table->lock);
00275    AST_LIST_UNLOCK(&psql_tables);
00276    return table;
00277 }
00278 
00279 #define release_table(table) ast_rwlock_unlock(&(table)->lock);
00280 
00281 static struct columns *find_column(struct tables *t, const char *colname)
00282 {
00283    struct columns *column;
00284 
00285    /* Check that the column exists in the table */
00286    AST_LIST_TRAVERSE(&t->columns, column, list) {
00287       if (strcmp(column->name, colname) == 0) {
00288          return column;
00289       }
00290    }
00291    return NULL;
00292 }
00293 
00294 static char *decode_chunk(char *chunk)
00295 {
00296    char *orig = chunk;
00297    for (; *chunk; chunk++) {
00298       if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
00299          sscanf(chunk + 1, "%02hhd", chunk);
00300          memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
00301       }
00302    }
00303    return orig;
00304 }
00305 
00306 static struct ast_variable *realtime_pgsql(const char *database, const char *tablename, va_list ap)
00307 {
00308    PGresult *result = NULL;
00309    int num_rows = 0, pgresult;
00310    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00311    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
00312    char *stringp;
00313    char *chunk;
00314    char *op;
00315    const char *newparam, *newval;
00316    struct ast_variable *var = NULL, *prev = NULL;
00317 
00318    if (!tablename) {
00319       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00320       return NULL;
00321    }
00322 
00323    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00324    newparam = va_arg(ap, const char *);
00325    newval = va_arg(ap, const char *);
00326    if (!newparam || !newval) {
00327       ast_log(LOG_WARNING,
00328             "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
00329       if (pgsqlConn) {
00330          PQfinish(pgsqlConn);
00331          pgsqlConn = NULL;
00332       }
00333       return NULL;
00334    }
00335 
00336    /* Create the first part of the query using the first parameter/value pairs we just extracted
00337       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00338    op = strchr(newparam, ' ') ? "" : " =";
00339 
00340    ESCAPE_STRING(escapebuf, newval);
00341    if (pgresult) {
00342       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00343       va_end(ap);
00344       return NULL;
00345    }
00346 
00347    ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", tablename, newparam, op, ast_str_buffer(escapebuf));
00348    while ((newparam = va_arg(ap, const char *))) {
00349       newval = va_arg(ap, const char *);
00350       if (!strchr(newparam, ' '))
00351          op = " =";
00352       else
00353          op = "";
00354 
00355       ESCAPE_STRING(escapebuf, newval);
00356       if (pgresult) {
00357          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00358          va_end(ap);
00359          return NULL;
00360       }
00361 
00362       ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf));
00363    }
00364    va_end(ap);
00365 
00366    /* We now have our complete statement; Lets connect to the server and execute it. */
00367    ast_mutex_lock(&pgsql_lock);
00368    if (!pgsql_reconnect(database)) {
00369       ast_mutex_unlock(&pgsql_lock);
00370       return NULL;
00371    }
00372 
00373    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00374       ast_log(LOG_WARNING,
00375             "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
00376       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00377       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00378       ast_mutex_unlock(&pgsql_lock);
00379       return NULL;
00380    } else {
00381       ExecStatusType result_status = PQresultStatus(result);
00382       if (result_status != PGRES_COMMAND_OK
00383          && result_status != PGRES_TUPLES_OK
00384          && result_status != PGRES_NONFATAL_ERROR) {
00385          ast_log(LOG_WARNING,
00386                "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", tablename, database);
00387          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00388          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00389                   PQresultErrorMessage(result), PQresStatus(result_status));
00390          ast_mutex_unlock(&pgsql_lock);
00391          return NULL;
00392       }
00393    }
00394 
00395    ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
00396 
00397    if ((num_rows = PQntuples(result)) > 0) {
00398       int i = 0;
00399       int rowIndex = 0;
00400       int numFields = PQnfields(result);
00401       char **fieldnames = NULL;
00402 
00403       ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
00404 
00405       if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
00406          ast_mutex_unlock(&pgsql_lock);
00407          PQclear(result);
00408          return NULL;
00409       }
00410       for (i = 0; i < numFields; i++)
00411          fieldnames[i] = PQfname(result, i);
00412       for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
00413          for (i = 0; i < numFields; i++) {
00414             stringp = PQgetvalue(result, rowIndex, i);
00415             while (stringp) {
00416                chunk = strsep(&stringp, ";");
00417                if (chunk && !ast_strlen_zero(decode_chunk(ast_strip(chunk)))) {
00418                   if (prev) {
00419                      prev->next = ast_variable_new(fieldnames[i], chunk, "");
00420                      if (prev->next) {
00421                         prev = prev->next;
00422                      }
00423                   } else {
00424                      prev = var = ast_variable_new(fieldnames[i], chunk, "");
00425                   }
00426                }
00427             }
00428          }
00429       }
00430       ast_free(fieldnames);
00431    } else {
00432       ast_debug(1, "Postgresql RealTime: Could not find any rows in table %s@%s.\n", tablename, database);
00433    }
00434 
00435    ast_mutex_unlock(&pgsql_lock);
00436    PQclear(result);
00437 
00438    return var;
00439 }
00440 
00441 static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
00442 {
00443    PGresult *result = NULL;
00444    int num_rows = 0, pgresult;
00445    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00446    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
00447    const char *initfield = NULL;
00448    char *stringp;
00449    char *chunk;
00450    char *op;
00451    const char *newparam, *newval;
00452    struct ast_variable *var = NULL;
00453    struct ast_config *cfg = NULL;
00454    struct ast_category *cat = NULL;
00455 
00456    if (!table) {
00457       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00458       return NULL;
00459    }
00460 
00461    if (!(cfg = ast_config_new()))
00462       return NULL;
00463 
00464    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00465    newparam = va_arg(ap, const char *);
00466    newval = va_arg(ap, const char *);
00467    if (!newparam || !newval) {
00468       ast_log(LOG_WARNING,
00469             "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
00470       if (pgsqlConn) {
00471          PQfinish(pgsqlConn);
00472          pgsqlConn = NULL;
00473       }
00474       return NULL;
00475    }
00476 
00477    initfield = ast_strdupa(newparam);
00478    if ((op = strchr(initfield, ' '))) {
00479       *op = '\0';
00480    }
00481 
00482    /* Create the first part of the query using the first parameter/value pairs we just extracted
00483       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00484 
00485    if (!strchr(newparam, ' '))
00486       op = " =";
00487    else
00488       op = "";
00489 
00490    ESCAPE_STRING(escapebuf, newval);
00491    if (pgresult) {
00492       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00493       va_end(ap);
00494       return NULL;
00495    }
00496 
00497    ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, ast_str_buffer(escapebuf));
00498    while ((newparam = va_arg(ap, const char *))) {
00499       newval = va_arg(ap, const char *);
00500       if (!strchr(newparam, ' '))
00501          op = " =";
00502       else
00503          op = "";
00504 
00505       ESCAPE_STRING(escapebuf, newval);
00506       if (pgresult) {
00507          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00508          va_end(ap);
00509          return NULL;
00510       }
00511 
00512       ast_str_append(&sql, 0, " AND %s%s '%s'", newparam, op, ast_str_buffer(escapebuf));
00513    }
00514 
00515    if (initfield) {
00516       ast_str_append(&sql, 0, " ORDER BY %s", initfield);
00517    }
00518 
00519    va_end(ap);
00520 
00521    /* We now have our complete statement; Lets connect to the server and execute it. */
00522    ast_mutex_lock(&pgsql_lock);
00523    if (!pgsql_reconnect(database)) {
00524       ast_mutex_unlock(&pgsql_lock);
00525       return NULL;
00526    }
00527 
00528    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00529       ast_log(LOG_WARNING,
00530             "PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
00531       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00532       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00533       ast_mutex_unlock(&pgsql_lock);
00534       return NULL;
00535    } else {
00536       ExecStatusType result_status = PQresultStatus(result);
00537       if (result_status != PGRES_COMMAND_OK
00538          && result_status != PGRES_TUPLES_OK
00539          && result_status != PGRES_NONFATAL_ERROR) {
00540          ast_log(LOG_WARNING,
00541                "PostgreSQL RealTime: Failed to query %s@%s. Check debug for more info.\n", table, database);
00542          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00543          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00544                   PQresultErrorMessage(result), PQresStatus(result_status));
00545          ast_mutex_unlock(&pgsql_lock);
00546          return NULL;
00547       }
00548    }
00549 
00550    ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, ast_str_buffer(sql));
00551 
00552    if ((num_rows = PQntuples(result)) > 0) {
00553       int numFields = PQnfields(result);
00554       int i = 0;
00555       int rowIndex = 0;
00556       char **fieldnames = NULL;
00557 
00558       ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows);
00559 
00560       if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
00561          ast_mutex_unlock(&pgsql_lock);
00562          PQclear(result);
00563          return NULL;
00564       }
00565       for (i = 0; i < numFields; i++)
00566          fieldnames[i] = PQfname(result, i);
00567 
00568       for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
00569          var = NULL;
00570          if (!(cat = ast_category_new("","",99999)))
00571             continue;
00572          for (i = 0; i < numFields; i++) {
00573             stringp = PQgetvalue(result, rowIndex, i);
00574             while (stringp) {
00575                chunk = strsep(&stringp, ";");
00576                if (chunk && !ast_strlen_zero(decode_chunk(ast_strip(chunk)))) {
00577                   if (initfield && !strcmp(initfield, fieldnames[i])) {
00578                      ast_category_rename(cat, chunk);
00579                   }
00580                   var = ast_variable_new(fieldnames[i], chunk, "");
00581                   ast_variable_append(cat, var);
00582                }
00583             }
00584          }
00585          ast_category_append(cfg, cat);
00586       }
00587       ast_free(fieldnames);
00588    } else {
00589       ast_debug(1, "PostgreSQL RealTime: Could not find any rows in table %s.\n", table);
00590    }
00591 
00592    ast_mutex_unlock(&pgsql_lock);
00593    PQclear(result);
00594 
00595    return cfg;
00596 }
00597 
00598 static int update_pgsql(const char *database, const char *tablename, const char *keyfield,
00599                   const char *lookup, va_list ap)
00600 {
00601    PGresult *result = NULL;
00602    int numrows = 0, pgresult;
00603    const char *newparam, *newval;
00604    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00605    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 100);
00606    struct tables *table;
00607    struct columns *column = NULL;
00608 
00609    if (!tablename) {
00610       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00611       return -1;
00612    }
00613 
00614    if (!(table = find_table(tablename))) {
00615       ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
00616       return -1;
00617    }
00618 
00619    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00620    newparam = va_arg(ap, const char *);
00621    newval = va_arg(ap, const char *);
00622    if (!newparam || !newval) {
00623       ast_log(LOG_WARNING,
00624             "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
00625       if (pgsqlConn) {
00626          PQfinish(pgsqlConn);
00627          pgsqlConn = NULL;
00628       }
00629       release_table(table);
00630       return -1;
00631    }
00632 
00633    /* Check that the column exists in the table */
00634    AST_LIST_TRAVERSE(&table->columns, column, list) {
00635       if (strcmp(column->name, newparam) == 0) {
00636          break;
00637       }
00638    }
00639 
00640    if (!column) {
00641       ast_log(LOG_ERROR, "PostgreSQL RealTime: Updating on column '%s', but that column does not exist within the table '%s'!\n", newparam, tablename);
00642       release_table(table);
00643       return -1;
00644    }
00645 
00646    /* Create the first part of the query using the first parameter/value pairs we just extracted
00647       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00648 
00649    ESCAPE_STRING(escapebuf, newval);
00650    if (pgresult) {
00651       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00652       va_end(ap);
00653       release_table(table);
00654       return -1;
00655    }
00656    ast_str_set(&sql, 0, "UPDATE %s SET %s = '%s'", tablename, newparam, ast_str_buffer(escapebuf));
00657 
00658    while ((newparam = va_arg(ap, const char *))) {
00659       newval = va_arg(ap, const char *);
00660 
00661       if (!find_column(table, newparam)) {
00662          ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s', but column does not exist!\n", newparam, tablename);
00663          continue;
00664       }
00665 
00666       ESCAPE_STRING(escapebuf, newval);
00667       if (pgresult) {
00668          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00669          va_end(ap);
00670          release_table(table);
00671          return -1;
00672       }
00673 
00674       ast_str_append(&sql, 0, ", %s = '%s'", newparam, ast_str_buffer(escapebuf));
00675    }
00676    va_end(ap);
00677    release_table(table);
00678 
00679    ESCAPE_STRING(escapebuf, lookup);
00680    if (pgresult) {
00681       ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
00682       va_end(ap);
00683       return -1;
00684    }
00685 
00686    ast_str_append(&sql, 0, " WHERE %s = '%s'", keyfield, ast_str_buffer(escapebuf));
00687 
00688    ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
00689 
00690    /* We now have our complete statement; Lets connect to the server and execute it. */
00691    ast_mutex_lock(&pgsql_lock);
00692    if (!pgsql_reconnect(database)) {
00693       ast_mutex_unlock(&pgsql_lock);
00694       return -1;
00695    }
00696 
00697    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00698       ast_log(LOG_WARNING,
00699             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00700       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00701       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00702       ast_mutex_unlock(&pgsql_lock);
00703       return -1;
00704    } else {
00705       ExecStatusType result_status = PQresultStatus(result);
00706       if (result_status != PGRES_COMMAND_OK
00707          && result_status != PGRES_TUPLES_OK
00708          && result_status != PGRES_NONFATAL_ERROR) {
00709          ast_log(LOG_WARNING,
00710                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00711          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00712          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00713                   PQresultErrorMessage(result), PQresStatus(result_status));
00714          ast_mutex_unlock(&pgsql_lock);
00715          return -1;
00716       }
00717    }
00718 
00719    numrows = atoi(PQcmdTuples(result));
00720    ast_mutex_unlock(&pgsql_lock);
00721 
00722    ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
00723 
00724    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
00725     * An integer greater than zero indicates the number of rows affected
00726     * Zero indicates that no records were updated
00727     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
00728     */
00729 
00730    if (numrows >= 0)
00731       return (int) numrows;
00732 
00733    return -1;
00734 }
00735 
00736 static int update2_pgsql(const char *database, const char *tablename, va_list ap)
00737 {
00738    PGresult *result = NULL;
00739    int numrows = 0, pgresult, first = 1;
00740    struct ast_str *escapebuf = ast_str_thread_get(&escapebuf_buf, 16);
00741    const char *newparam, *newval;
00742    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
00743    struct ast_str *where = ast_str_thread_get(&where_buf, 100);
00744    struct tables *table;
00745 
00746    if (!tablename) {
00747       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00748       return -1;
00749    }
00750 
00751    if (!escapebuf || !sql || !where) {
00752       /* Memory error, already handled */
00753       return -1;
00754    }
00755 
00756    if (!(table = find_table(tablename))) {
00757       ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
00758       return -1;
00759    }
00760 
00761    ast_str_set(&sql, 0, "UPDATE %s SET ", tablename);
00762    ast_str_set(&where, 0, "WHERE");
00763 
00764    while ((newparam = va_arg(ap, const char *))) {
00765       if (!find_column(table, newparam)) {
00766          ast_log(LOG_ERROR, "Attempted to update based on criteria column '%s' (%s@%s), but that column does not exist!\n", newparam, tablename, database);
00767          release_table(table);
00768          return -1;
00769       }
00770 
00771       newval = va_arg(ap, const char *);
00772       ESCAPE_STRING(escapebuf, newval);
00773       if (pgresult) {
00774          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00775          release_table(table);
00776          ast_free(sql);
00777          return -1;
00778       }
00779       ast_str_append(&where, 0, "%s %s='%s'", first ? "" : " AND", newparam, ast_str_buffer(escapebuf));
00780       first = 0;
00781    }
00782 
00783    if (first) {
00784       ast_log(LOG_WARNING,
00785             "PostgreSQL RealTime: Realtime update requires at least 1 parameter and 1 value to search on.\n");
00786       if (pgsqlConn) {
00787          PQfinish(pgsqlConn);
00788          pgsqlConn = NULL;
00789       }
00790       release_table(table);
00791       return -1;
00792    }
00793 
00794    /* Now retrieve the columns to update */
00795    first = 1;
00796    while ((newparam = va_arg(ap, const char *))) {
00797       newval = va_arg(ap, const char *);
00798 
00799       /* If the column is not within the table, then skip it */
00800       if (!find_column(table, newparam)) {
00801          ast_log(LOG_NOTICE, "Attempted to update column '%s' in table '%s@%s', but column does not exist!\n", newparam, tablename, database);
00802          continue;
00803       }
00804 
00805       ESCAPE_STRING(escapebuf, newval);
00806       if (pgresult) {
00807          ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
00808          release_table(table);
00809          ast_free(sql);
00810          return -1;
00811       }
00812 
00813       ast_str_append(&sql, 0, "%s %s='%s'", first ? "" : ",", newparam, ast_str_buffer(escapebuf));
00814    }
00815    release_table(table);
00816 
00817    ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
00818 
00819    ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
00820 
00821    /* We now have our complete statement; connect to the server and execute it. */
00822    ast_mutex_lock(&pgsql_lock);
00823    if (!pgsql_reconnect(database)) {
00824       ast_mutex_unlock(&pgsql_lock);
00825       return -1;
00826    }
00827 
00828    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
00829       ast_log(LOG_WARNING,
00830             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00831       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00832       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00833       ast_mutex_unlock(&pgsql_lock);
00834       return -1;
00835    } else {
00836       ExecStatusType result_status = PQresultStatus(result);
00837       if (result_status != PGRES_COMMAND_OK
00838          && result_status != PGRES_TUPLES_OK
00839          && result_status != PGRES_NONFATAL_ERROR) {
00840          ast_log(LOG_WARNING,
00841                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00842          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
00843          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00844                   PQresultErrorMessage(result), PQresStatus(result_status));
00845          ast_mutex_unlock(&pgsql_lock);
00846          return -1;
00847       }
00848    }
00849 
00850    numrows = atoi(PQcmdTuples(result));
00851    ast_mutex_unlock(&pgsql_lock);
00852 
00853    ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, tablename);
00854 
00855    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
00856     * An integer greater than zero indicates the number of rows affected
00857     * Zero indicates that no records were updated
00858     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
00859     */
00860 
00861    if (numrows >= 0) {
00862       return (int) numrows;
00863    }
00864 
00865    return -1;
00866 }
00867 
00868 static int store_pgsql(const char *database, const char *table, va_list ap)
00869 {
00870    PGresult *result = NULL;
00871    Oid insertid;
00872    struct ast_str *buf = ast_str_thread_get(&escapebuf_buf, 256);
00873    struct ast_str *sql1 = ast_str_thread_get(&sql_buf, 256);
00874    struct ast_str *sql2 = ast_str_thread_get(&where_buf, 256);
00875    int pgresult;
00876    const char *newparam, *newval;
00877 
00878    if (!table) {
00879       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00880       return -1;
00881    }
00882 
00883    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00884    newparam = va_arg(ap, const char *);
00885    newval = va_arg(ap, const char *);
00886    if (!newparam || !newval) {
00887       ast_log(LOG_WARNING,
00888             "PostgreSQL RealTime: Realtime storage requires at least 1 parameter and 1 value to store.\n");
00889       if (pgsqlConn) {
00890          PQfinish(pgsqlConn);
00891          pgsqlConn = NULL;
00892       }
00893       return -1;
00894    }
00895 
00896    /* Must connect to the server before anything else, as the escape function requires the connection handle.. */
00897    ast_mutex_lock(&pgsql_lock);
00898    if (!pgsql_reconnect(database)) {
00899       ast_mutex_unlock(&pgsql_lock);
00900       return -1;
00901    }
00902 
00903    /* Create the first part of the query using the first parameter/value pairs we just extracted
00904       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00905    ESCAPE_STRING(buf, newparam);
00906    ast_str_set(&sql1, 0, "INSERT INTO %s (%s", table, ast_str_buffer(buf));
00907    ESCAPE_STRING(buf, newval);
00908    ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
00909    while ((newparam = va_arg(ap, const char *))) {
00910       newval = va_arg(ap, const char *);
00911       ESCAPE_STRING(buf, newparam);
00912       ast_str_append(&sql1, 0, ", %s", ast_str_buffer(buf));
00913       ESCAPE_STRING(buf, newval);
00914       ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
00915    }
00916    va_end(ap);
00917    ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
00918 
00919    ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql1));
00920 
00921    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql1)))) {
00922       ast_log(LOG_WARNING,
00923             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00924       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql1));
00925       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
00926       ast_mutex_unlock(&pgsql_lock);
00927       return -1;
00928    } else {
00929       ExecStatusType result_status = PQresultStatus(result);
00930       if (result_status != PGRES_COMMAND_OK
00931          && result_status != PGRES_TUPLES_OK
00932          && result_status != PGRES_NONFATAL_ERROR) {
00933          ast_log(LOG_WARNING,
00934                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
00935          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql1));
00936          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
00937                   PQresultErrorMessage(result), PQresStatus(result_status));
00938          ast_mutex_unlock(&pgsql_lock);
00939          return -1;
00940       }
00941    }
00942 
00943    insertid = PQoidValue(result);
00944    ast_mutex_unlock(&pgsql_lock);
00945 
00946    ast_debug(1, "PostgreSQL RealTime: row inserted on table: %s, id: %u\n", table, insertid);
00947 
00948    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
00949     * An integer greater than zero indicates the number of rows affected
00950     * Zero indicates that no records were updated
00951     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
00952     */
00953 
00954    if (insertid >= 0)
00955       return (int) insertid;
00956 
00957    return -1;
00958 }
00959 
00960 static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
00961 {
00962    PGresult *result = NULL;
00963    int numrows = 0;
00964    int pgresult;
00965    struct ast_str *sql = ast_str_thread_get(&sql_buf, 256);
00966    struct ast_str *buf1 = ast_str_thread_get(&where_buf, 60), *buf2 = ast_str_thread_get(&escapebuf_buf, 60);
00967    const char *newparam, *newval;
00968 
00969    if (!table) {
00970       ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n");
00971       return -1;
00972    }
00973 
00974    /* Get the first parameter and first value in our list of passed paramater/value pairs */
00975    /*newparam = va_arg(ap, const char *);
00976    newval = va_arg(ap, const char *);
00977    if (!newparam || !newval) {*/
00978    if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup))  {
00979       ast_log(LOG_WARNING,
00980             "PostgreSQL RealTime: Realtime destroy requires at least 1 parameter and 1 value to search on.\n");
00981       if (pgsqlConn) {
00982          PQfinish(pgsqlConn);
00983          pgsqlConn = NULL;
00984       };
00985       return -1;
00986    }
00987 
00988    /* Must connect to the server before anything else, as the escape function requires the connection handle.. */
00989    ast_mutex_lock(&pgsql_lock);
00990    if (!pgsql_reconnect(database)) {
00991       ast_mutex_unlock(&pgsql_lock);
00992       return -1;
00993    }
00994 
00995 
00996    /* Create the first part of the query using the first parameter/value pairs we just extracted
00997       If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
00998 
00999    ESCAPE_STRING(buf1, keyfield);
01000    ESCAPE_STRING(buf2, lookup);
01001    ast_str_set(&sql, 0, "DELETE FROM %s WHERE %s = '%s'", table, ast_str_buffer(buf1), ast_str_buffer(buf2));
01002    while ((newparam = va_arg(ap, const char *))) {
01003       newval = va_arg(ap, const char *);
01004       ESCAPE_STRING(buf1, newparam);
01005       ESCAPE_STRING(buf2, newval);
01006       ast_str_append(&sql, 0, " AND %s = '%s'", ast_str_buffer(buf1), ast_str_buffer(buf2));
01007    }
01008    va_end(ap);
01009 
01010    ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
01011 
01012    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
01013       ast_log(LOG_WARNING,
01014             "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
01015       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01016       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
01017       ast_mutex_unlock(&pgsql_lock);
01018       return -1;
01019    } else {
01020       ExecStatusType result_status = PQresultStatus(result);
01021       if (result_status != PGRES_COMMAND_OK
01022          && result_status != PGRES_TUPLES_OK
01023          && result_status != PGRES_NONFATAL_ERROR) {
01024          ast_log(LOG_WARNING,
01025                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
01026          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01027          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
01028                   PQresultErrorMessage(result), PQresStatus(result_status));
01029          ast_mutex_unlock(&pgsql_lock);
01030          return -1;
01031       }
01032    }
01033 
01034    numrows = atoi(PQcmdTuples(result));
01035    ast_mutex_unlock(&pgsql_lock);
01036 
01037    ast_debug(1, "PostgreSQL RealTime: Deleted %d rows on table: %s\n", numrows, table);
01038 
01039    /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
01040     * An integer greater than zero indicates the number of rows affected
01041     * Zero indicates that no records were updated
01042     * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
01043     */
01044 
01045    if (numrows >= 0)
01046       return (int) numrows;
01047 
01048    return -1;
01049 }
01050 
01051 
01052 static struct ast_config *config_pgsql(const char *database, const char *table,
01053                               const char *file, struct ast_config *cfg,
01054                               struct ast_flags flags, const char *suggested_incl, const char *who_asked)
01055 {
01056    PGresult *result = NULL;
01057    long num_rows;
01058    struct ast_variable *new_v;
01059    struct ast_category *cur_cat = NULL;
01060    struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
01061    char last[80] = "";
01062    int last_cat_metric = 0;
01063 
01064    last[0] = '\0';
01065 
01066    if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
01067       ast_log(LOG_WARNING, "PostgreSQL RealTime: Cannot configure myself.\n");
01068       return NULL;
01069    }
01070 
01071    ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s "
01072          "WHERE filename='%s' and commented=0"
01073          "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ", table, file);
01074 
01075    ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
01076 
01077    /* We now have our complete statement; Lets connect to the server and execute it. */
01078    ast_mutex_lock(&pgsql_lock);
01079    if (!pgsql_reconnect(database)) {
01080       ast_mutex_unlock(&pgsql_lock);
01081       return NULL;
01082    }
01083 
01084    if (!(result = PQexec(pgsqlConn, ast_str_buffer(sql)))) {
01085       ast_log(LOG_WARNING,
01086             "PostgreSQL RealTime: Failed to query '%s@%s'. Check debug for more info.\n", table, database);
01087       ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01088       ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn));
01089       ast_mutex_unlock(&pgsql_lock);
01090       return NULL;
01091    } else {
01092       ExecStatusType result_status = PQresultStatus(result);
01093       if (result_status != PGRES_COMMAND_OK
01094          && result_status != PGRES_TUPLES_OK
01095          && result_status != PGRES_NONFATAL_ERROR) {
01096          ast_log(LOG_WARNING,
01097                "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n");
01098          ast_debug(1, "PostgreSQL RealTime: Query: %s\n", ast_str_buffer(sql));
01099          ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n",
01100                   PQresultErrorMessage(result), PQresStatus(result_status));
01101          ast_mutex_unlock(&pgsql_lock);
01102          return NULL;
01103       }
01104    }
01105 
01106    if ((num_rows = PQntuples(result)) > 0) {
01107       int rowIndex = 0;
01108 
01109       ast_debug(1, "PostgreSQL RealTime: Found %ld rows.\n", num_rows);
01110 
01111       for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
01112          char *field_category = PQgetvalue(result, rowIndex, 0);
01113          char *field_var_name = PQgetvalue(result, rowIndex, 1);
01114          char *field_var_val = PQgetvalue(result, rowIndex, 2);
01115          char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
01116          if (!strcmp(field_var_name, "#include")) {
01117             if (!ast_config_internal_load(field_var_val, cfg, flags, "", who_asked)) {
01118                PQclear(result);
01119                ast_mutex_unlock(&pgsql_lock);
01120                return NULL;
01121             }
01122             continue;
01123          }
01124 
01125          if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
01126             cur_cat = ast_category_new(field_category, "", 99999);
01127             if (!cur_cat)
01128                break;
01129             strcpy(last, field_category);
01130             last_cat_metric = atoi(field_cat_metric);
01131             ast_category_append(cfg, cur_cat);
01132          }
01133          new_v = ast_variable_new(field_var_name, field_var_val, "");
01134          ast_variable_append(cur_cat, new_v);
01135       }
01136    } else {
01137       ast_log(LOG_WARNING,
01138             "PostgreSQL RealTime: Could not find config '%s' in database.\n", file);
01139    }
01140 
01141    PQclear(result);
01142    ast_mutex_unlock(&pgsql_lock);
01143 
01144    return cfg;
01145 }
01146 
01147 static int require_pgsql(const char *database, const char *tablename, va_list ap)
01148 {
01149    struct columns *column;
01150    struct tables *table = find_table(tablename);
01151    char *elm;
01152    int type, size, res = 0;
01153 
01154    if (!table) {
01155       ast_log(LOG_WARNING, "Table %s not found in database.  This table should exist if you're using realtime.\n", tablename);
01156       return -1;
01157    }
01158 
01159    while ((elm = va_arg(ap, char *))) {
01160       type = va_arg(ap, require_type);
01161       size = va_arg(ap, int);
01162       AST_LIST_TRAVERSE(&table->columns, column, list) {
01163          if (strcmp(column->name, elm) == 0) {
01164             /* Char can hold anything, as long as it is large enough */
01165             if ((strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0 || strcmp(column->type, "bpchar") == 0)) {
01166                if ((size > column->len) && column->len != -1) {
01167                   ast_log(LOG_WARNING, "Column '%s' should be at least %d long, but is only %d long.\n", column->name, size, column->len);
01168                   res = -1;
01169                }
01170             } else if (strncmp(column->type, "int", 3) == 0) {
01171                int typesize = atoi(column->type + 3);
01172                /* Integers can hold only other integers */
01173                if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
01174                   type == RQ_INTEGER4 || type == RQ_UINTEGER4 ||
01175                   type == RQ_INTEGER3 || type == RQ_UINTEGER3 ||
01176                   type == RQ_UINTEGER2) && typesize == 2) {
01177                   ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
01178                   res = -1;
01179                } else if ((type == RQ_INTEGER8 || type == RQ_UINTEGER8 ||
01180                   type == RQ_UINTEGER4) && typesize == 4) {
01181                   ast_log(LOG_WARNING, "Column '%s' may not be large enough for the required data length: %d\n", column->name, size);
01182                   res = -1;
01183                } else if (type == RQ_CHAR || type == RQ_DATETIME || type == RQ_FLOAT || type == RQ_DATE) {
01184                   ast_log(LOG_WARNING, "Column '%s' is of the incorrect type: (need %s(%d) but saw %s)\n",
01185                      column->name,
01186                         type == RQ_CHAR ? "char" :
01187                         type == RQ_DATETIME ? "datetime" :
01188                         type == RQ_DATE ? "date" :
01189                         type == RQ_FLOAT ? "float" :
01190                         "a rather stiff drink ",
01191                      size, column->type);
01192                   res = -1;
01193                }
01194             } else if (strncmp(column->type, "float", 5) == 0 && !ast_rq_is_int(type) && type != RQ_FLOAT) {
01195                ast_log(LOG_WARNING, "Column %s cannot be a %s\n", column->name, column->type);
01196                res = -1;
01197             } else { /* There are other types that no module implements yet */
01198                ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
01199                res = -1;
01200             }
01201             break;
01202          }
01203       }
01204 
01205       if (!column) {
01206          if (requirements == RQ_WARN) {
01207             ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
01208          } else {
01209             struct ast_str *sql = ast_str_create(100);
01210             char fieldtype[15];
01211             PGresult *result;
01212 
01213             if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
01214                /* Size is minimum length; make it at least 50% greater,
01215                 * just to be sure, because PostgreSQL doesn't support
01216                 * resizing columns. */
01217                snprintf(fieldtype, sizeof(fieldtype), "CHAR(%d)",
01218                   size < 15 ? size * 2 :
01219                   (size * 3 / 2 > 255) ? 255 : size * 3 / 2);
01220             } else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) {
01221                snprintf(fieldtype, sizeof(fieldtype), "INT2");
01222             } else if (type == RQ_UINTEGER2 || type == RQ_INTEGER3 || type == RQ_UINTEGER3 || type == RQ_INTEGER4) {
01223                snprintf(fieldtype, sizeof(fieldtype), "INT4");
01224             } else if (type == RQ_UINTEGER4 || type == RQ_INTEGER8) {
01225                snprintf(fieldtype, sizeof(fieldtype), "INT8");
01226             } else if (type == RQ_UINTEGER8) {
01227                /* No such type on PostgreSQL */
01228                snprintf(fieldtype, sizeof(fieldtype), "CHAR(20)");
01229             } else if (type == RQ_FLOAT) {
01230                snprintf(fieldtype, sizeof(fieldtype), "FLOAT8");
01231             } else if (type == RQ_DATE) {
01232                snprintf(fieldtype, sizeof(fieldtype), "DATE");
01233             } else if (type == RQ_DATETIME) {
01234                snprintf(fieldtype, sizeof(fieldtype), "TIMESTAMP");
01235             } else {
01236                ast_log(LOG_ERROR, "Unrecognized request type %d\n", type);
01237                ast_free(sql);
01238                continue;
01239             }
01240             ast_str_set(&sql, 0, "ALTER TABLE %s ADD COLUMN %s %s", tablename, elm, fieldtype);
01241             ast_debug(1, "About to lock pgsql_lock (running alter on table '%s' to add column '%s')\n", tablename, elm);
01242 
01243             ast_mutex_lock(&pgsql_lock);
01244             if (!pgsql_reconnect(database)) {
01245                ast_mutex_unlock(&pgsql_lock);
01246                ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
01247                ast_free(sql);
01248                continue;
01249             }
01250 
01251             ast_debug(1, "About to run ALTER query on table '%s' to add column '%s'\n", tablename, elm);
01252             result = PQexec(pgsqlConn, ast_str_buffer(sql));
01253             ast_debug(1, "Finished running ALTER query on table '%s'\n", tablename);
01254             if (PQresultStatus(result) != PGRES_COMMAND_OK) {
01255                ast_log(LOG_ERROR, "Unable to add column: %s\n", ast_str_buffer(sql));
01256             }
01257             PQclear(result);
01258             ast_mutex_unlock(&pgsql_lock);
01259 
01260             ast_free(sql);
01261          }
01262       }
01263    }
01264    release_table(table);
01265    return res;
01266 }
01267 
01268 static int unload_pgsql(const char *database, const char *tablename)
01269 {
01270    struct tables *cur;
01271    ast_debug(2, "About to lock table cache list\n");
01272    AST_LIST_LOCK(&psql_tables);
01273    ast_debug(2, "About to traverse table cache list\n");
01274    AST_LIST_TRAVERSE_SAFE_BEGIN(&psql_tables, cur, list) {
01275       if (strcmp(cur->name, tablename) == 0) {
01276          ast_debug(2, "About to remove matching cache entry\n");
01277          AST_LIST_REMOVE_CURRENT(list);
01278          ast_debug(2, "About to destroy matching cache entry\n");
01279          destroy_table(cur);
01280          ast_debug(1, "Cache entry '%s@%s' destroyed\n", tablename, database);
01281          break;
01282       }
01283    }
01284    AST_LIST_TRAVERSE_SAFE_END
01285    AST_LIST_UNLOCK(&psql_tables);
01286    ast_debug(2, "About to return\n");
01287    return cur ? 0 : -1;
01288 }
01289 
01290 static struct ast_config_engine pgsql_engine = {
01291    .name = "pgsql",
01292    .load_func = config_pgsql,
01293    .realtime_func = realtime_pgsql,
01294    .realtime_multi_func = realtime_multi_pgsql,
01295    .store_func = store_pgsql,
01296    .destroy_func = destroy_pgsql,
01297    .update_func = update_pgsql,
01298    .update2_func = update2_pgsql,
01299    .require_func = require_pgsql,
01300    .unload_func = unload_pgsql,
01301 };
01302 
01303 static int load_module(void)
01304 {
01305    if(!parse_config(0))
01306       return AST_MODULE_LOAD_DECLINE;
01307 
01308    ast_config_engine_register(&pgsql_engine);
01309    ast_verb(1, "PostgreSQL RealTime driver loaded.\n");
01310    ast_cli_register_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
01311 
01312    return 0;
01313 }
01314 
01315 static int unload_module(void)
01316 {
01317    struct tables *table;
01318    /* Acquire control before doing anything to the module itself. */
01319    ast_mutex_lock(&pgsql_lock);
01320 
01321    if (pgsqlConn) {
01322       PQfinish(pgsqlConn);
01323       pgsqlConn = NULL;
01324    }
01325    ast_cli_unregister_multiple(cli_realtime, ARRAY_LEN(cli_realtime));
01326    ast_config_engine_deregister(&pgsql_engine);
01327    ast_verb(1, "PostgreSQL RealTime unloaded.\n");
01328 
01329    /* Destroy cached table info */
01330    AST_LIST_LOCK(&psql_tables);
01331    while ((table = AST_LIST_REMOVE_HEAD(&psql_tables, list))) {
01332       destroy_table(table);
01333    }
01334    AST_LIST_UNLOCK(&psql_tables);
01335 
01336    /* Unlock so something else can destroy the lock. */
01337    ast_mutex_unlock(&pgsql_lock);
01338 
01339    return 0;
01340 }
01341 
01342 static int reload(void)
01343 {
01344    parse_config(1);
01345 
01346    return 0;
01347 }
01348 
01349 static int parse_config(int is_reload)
01350 {
01351    struct ast_config *config;
01352    const char *s;
01353    struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01354 
01355    config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags);
01356    if (config == CONFIG_STATUS_FILEUNCHANGED) {
01357       return 0;
01358    }
01359 
01360    if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
01361       ast_log(LOG_WARNING, "Unable to load config %s\n", RES_CONFIG_PGSQL_CONF);
01362       return 0;
01363    }
01364 
01365    ast_mutex_lock(&pgsql_lock);
01366 
01367    if (pgsqlConn) {
01368       PQfinish(pgsqlConn);
01369       pgsqlConn = NULL;
01370    }
01371 
01372    if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
01373       ast_log(LOG_WARNING,
01374             "PostgreSQL RealTime: No database user found, using 'asterisk' as default.\n");
01375       strcpy(dbuser, "asterisk");
01376    } else {
01377       ast_copy_string(dbuser, s, sizeof(dbuser));
01378    }
01379 
01380    if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
01381       ast_log(LOG_WARNING,
01382             "PostgreSQL RealTime: No database password found, using 'asterisk' as default.\n");
01383       strcpy(dbpass, "asterisk");
01384    } else {
01385       ast_copy_string(dbpass, s, sizeof(dbpass));
01386    }
01387 
01388    if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
01389       ast_log(LOG_WARNING,
01390             "PostgreSQL RealTime: No database host found, using localhost via socket.\n");
01391       dbhost[0] = '\0';
01392    } else {
01393       ast_copy_string(dbhost, s, sizeof(dbhost));
01394    }
01395 
01396    if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
01397       ast_log(LOG_WARNING,
01398             "PostgreSQL RealTime: No database name found, using 'asterisk' as default.\n");
01399       strcpy(dbname, "asterisk");
01400    } else {
01401       ast_copy_string(dbname, s, sizeof(dbname));
01402    }
01403 
01404    if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
01405       ast_log(LOG_WARNING,
01406             "PostgreSQL RealTime: No database port found, using 5432 as default.\n");
01407       dbport = 5432;
01408    } else {
01409       dbport = atoi(s);
01410    }
01411 
01412    if (!ast_strlen_zero(dbhost)) {
01413       /* No socket needed */
01414    } else if (!(s = ast_variable_retrieve(config, "general", "dbsock"))) {
01415       ast_log(LOG_WARNING,
01416             "PostgreSQL RealTime: No database socket found, using '/tmp/.s.PGSQL.%d' as default.\n", dbport);
01417       strcpy(dbsock, "/tmp");
01418    } else {
01419       ast_copy_string(dbsock, s, sizeof(dbsock));
01420    }
01421 
01422    if (!(s = ast_variable_retrieve(config, "general", "requirements"))) {
01423       ast_log(LOG_WARNING,
01424             "PostgreSQL RealTime: no requirements setting found, using 'warn' as default.\n");
01425       requirements = RQ_WARN;
01426    } else if (!strcasecmp(s, "createclose")) {
01427       requirements = RQ_CREATECLOSE;
01428    } else if (!strcasecmp(s, "createchar")) {
01429       requirements = RQ_CREATECHAR;
01430    }
01431 
01432    ast_config_destroy(config);
01433 
01434    if (option_debug) {
01435       if (!ast_strlen_zero(dbhost)) {
01436          ast_debug(1, "PostgreSQL RealTime Host: %s\n", dbhost);
01437          ast_debug(1, "PostgreSQL RealTime Port: %i\n", dbport);
01438       } else {
01439          ast_debug(1, "PostgreSQL RealTime Socket: %s\n", dbsock);
01440       }
01441       ast_debug(1, "PostgreSQL RealTime User: %s\n", dbuser);
01442       ast_debug(1, "PostgreSQL RealTime Password: %s\n", dbpass);
01443       ast_debug(1, "PostgreSQL RealTime DBName: %s\n", dbname);
01444    }
01445 
01446    if (!pgsql_reconnect(NULL)) {
01447       ast_log(LOG_WARNING,
01448             "PostgreSQL RealTime: Couldn't establish connection. Check debug.\n");
01449       ast_debug(1, "PostgreSQL RealTime: Cannot Connect: %s\n", PQerrorMessage(pgsqlConn));
01450    }
01451 
01452    ast_verb(2, "PostgreSQL RealTime reloaded.\n");
01453 
01454    /* Done reloading. Release lock so others can now use driver. */
01455    ast_mutex_unlock(&pgsql_lock);
01456 
01457    return 1;
01458 }
01459 
01460 static int pgsql_reconnect(const char *database)
01461 {
01462    char my_database[50];
01463 
01464    ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
01465 
01466    /* mutex lock should have been locked before calling this function. */
01467 
01468    if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
01469       PQfinish(pgsqlConn);
01470       pgsqlConn = NULL;
01471    }
01472 
01473    /* DB password can legitimately be 0-length */
01474    if ((!pgsqlConn) && (!ast_strlen_zero(dbhost) || !ast_strlen_zero(dbsock)) && !ast_strlen_zero(dbuser) && !ast_strlen_zero(my_database)) {
01475       struct ast_str *connInfo = ast_str_create(32);
01476 
01477       ast_str_set(&connInfo, 0, "host=%s port=%d dbname=%s user=%s",
01478          S_OR(dbhost, dbsock), dbport, my_database, dbuser);
01479       if (!ast_strlen_zero(dbpass))
01480          ast_str_append(&connInfo, 0, " password=%s", dbpass);
01481 
01482       ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
01483       pgsqlConn = PQconnectdb(ast_str_buffer(connInfo));
01484       ast_debug(1, "%u connInfo=%s\n", (unsigned int)ast_str_size(connInfo), ast_str_buffer(connInfo));
01485       ast_free(connInfo);
01486       connInfo = NULL;
01487 
01488       ast_debug(1, "pgsqlConn=%p\n", pgsqlConn);
01489       if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
01490          ast_debug(1, "PostgreSQL RealTime: Successfully connected to database.\n");
01491          connect_time = time(NULL);
01492          version = PQserverVersion(pgsqlConn);
01493          return 1;
01494       } else {
01495          ast_log(LOG_ERROR,
01496                "PostgreSQL RealTime: Failed to connect database %s on %s: %s\n",
01497                dbname, dbhost, PQresultErrorMessage(NULL));
01498          return 0;
01499       }
01500    } else {
01501       ast_debug(1, "PostgreSQL RealTime: One or more of the parameters in the config does not pass our validity checks.\n");
01502       return 1;
01503    }
01504 }
01505 
01506 static char *handle_cli_realtime_pgsql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01507 {
01508    struct tables *cur;
01509    int l, which;
01510    char *ret = NULL;
01511 
01512    switch (cmd) {
01513    case CLI_INIT:
01514       e->command = "realtime show pgsql cache";
01515       e->usage =
01516          "Usage: realtime show pgsql cache [<table>]\n"
01517          "       Shows table cache for the PostgreSQL RealTime driver\n";
01518       return NULL;
01519    case CLI_GENERATE:
01520       if (a->argc != 4) {
01521          return NULL;
01522       }
01523       l = strlen(a->word);
01524       which = 0;
01525       AST_LIST_LOCK(&psql_tables);
01526       AST_LIST_TRAVERSE(&psql_tables, cur, list) {
01527          if (!strncasecmp(a->word, cur->name, l) && ++which > a->n) {
01528             ret = ast_strdup(cur->name);
01529             break;
01530          }
01531       }
01532       AST_LIST_UNLOCK(&psql_tables);
01533       return ret;
01534    }
01535 
01536    if (a->argc == 4) {
01537       /* List of tables */
01538       AST_LIST_LOCK(&psql_tables);
01539       AST_LIST_TRAVERSE(&psql_tables, cur, list) {
01540          ast_cli(a->fd, "%s\n", cur->name);
01541       }
01542       AST_LIST_UNLOCK(&psql_tables);
01543    } else if (a->argc == 5) {
01544       /* List of columns */
01545       if ((cur = find_table(a->argv[4]))) {
01546          struct columns *col;
01547          ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[4]);
01548          ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s %-8.8s\n", "Name", "Type", "Len", "Nullable");
01549          AST_LIST_TRAVERSE(&cur->columns, col, list) {
01550             ast_cli(a->fd, "%-20.20s %-20.20s %3d %-8.8s\n", col->name, col->type, col->len, col->notnull ? "NOT NULL" : "");
01551          }
01552          release_table(cur);
01553       } else {
01554          ast_cli(a->fd, "No such table '%s'\n", a->argv[4]);
01555       }
01556    }
01557    return 0;
01558 }
01559 
01560 static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01561 {
01562    char status[256], credentials[100] = "";
01563    int ctimesec = time(NULL) - connect_time;
01564 
01565    switch (cmd) {
01566    case CLI_INIT:
01567       e->command = "realtime show pgsql status";
01568       e->usage =
01569          "Usage: realtime show pgsql status\n"
01570          "       Shows connection information for the PostgreSQL RealTime driver\n";
01571       return NULL;
01572    case CLI_GENERATE:
01573       return NULL;
01574    }
01575 
01576    if (a->argc != 4)
01577       return CLI_SHOWUSAGE;
01578 
01579    if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
01580       if (!ast_strlen_zero(dbhost))
01581          snprintf(status, sizeof(status), "Connected to %s@%s, port %d", dbname, dbhost, dbport);
01582       else if (!ast_strlen_zero(dbsock))
01583          snprintf(status, sizeof(status), "Connected to %s on socket file %s", dbname, dbsock);
01584       else
01585          snprintf(status, sizeof(status), "Connected to %s@%s", dbname, dbhost);
01586 
01587       if (!ast_strlen_zero(dbuser))
01588          snprintf(credentials, sizeof(credentials), " with username %s", dbuser);
01589 
01590       if (ctimesec > 31536000)
01591          ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
01592                status, credentials, ctimesec / 31536000, (ctimesec % 31536000) / 86400,
01593                (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
01594       else if (ctimesec > 86400)
01595          ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
01596                credentials, ctimesec / 86400, (ctimesec % 86400) / 3600, (ctimesec % 3600) / 60,
01597                ctimesec % 60);
01598       else if (ctimesec > 3600)
01599          ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, credentials,
01600                ctimesec / 3600, (ctimesec % 3600) / 60, ctimesec % 60);
01601       else if (ctimesec > 60)
01602          ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, credentials, ctimesec / 60,
01603                ctimesec % 60);
01604       else
01605          ast_cli(a->fd, "%s%s for %d seconds.\n", status, credentials, ctimesec);
01606 
01607       return CLI_SUCCESS;
01608    } else {
01609       return CLI_FAILURE;
01610    }
01611 }
01612 
01613 /* needs usecount semantics defined */
01614 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "PostgreSQL RealTime Configuration Driver",
01615       .load = load_module,
01616       .unload = unload_module,
01617       .reload = reload
01618           );