]> granicus.if.org Git - pgbouncer/commitdiff
Server parameter tracking.
authorMarko Kreen <markokr@gmail.com>
Thu, 2 Aug 2007 11:59:19 +0000 (11:59 +0000)
committerMarko Kreen <markokr@gmail.com>
Thu, 2 Aug 2007 11:59:19 +0000 (11:59 +0000)
12 files changed:
NEWS
doc/todo.txt
src/admin.c
src/bouncer.h
src/client.c
src/janitor.c
src/main.c
src/objects.c
src/proto.c
src/server.c
src/varcache.c [new file with mode: 0644]
src/varcache.h [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 17f038d9f3e0949dd5b408897ff9c1d8e00228dd..1b6b310a5bc3e9d327325d0862bd5e168f0af1d0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,8 +8,10 @@
     - Accept custom unix socket location in host=
     - Accept quoted values: password=' asd''foo'
   * server_reset_query, to be sent immidiately after release
-  * Cancel pkt sent for idle connection does not drop it anymore,
-    just ReadyForQuery is re-sent.
+  * keep track of following server variables:
+    client_encoding, datestyle, timezone, standard_conforming_strings
+  * Cancel pkt sent for idle connection does not drop it anymore.
+  * Cancel with ^C from psql works for SUSPEND / PAUSE.
   * Print FD limits on startup.
   * More debug log messages include socket info.
   * When suspending, try to hit packet boundary ASAP.
index c38e1ee40a1f2ec2d7ec05976c37042aa970943c..05eec62f788ef4050fd4ec8d3b49544501a91133 100644 (file)
@@ -1,15 +1,17 @@
 = PgBouncer TODO list =
 
-== Small stuff ==
+== High-prio ==
 
  * suspend_timeout - drop stalled conns
+ * create manpage
+ * report existing pidfile to console
+ * before loading users, disable all existing
 
 == Low-prio ==
 
- * create manpage
  * drop_on_error/keep_on_error - if released conn is in error state,
  then issue rollback and keep it
- * report existing pidfile to console
+ * keep stats about error counts
 
 == Make -R less scary ==
 
    * if tcp - try binding
    * if unix - try connect()
 
-== Suspicious items ==
+== Just ideas ==
 
- * keep track of requested client_encoding (+others) and update if needed?
-   * original values in welcome pkt
-   * static: integer_datetimes, server_encoding, server_version,
-   * dynamic?: session_authorization, is_superuser?, standard_conforming_strings, TimeZone
- * keep stats about error counts
- * before loading users, disable all existing
  * auth_conn - access to pg_shadow, so auth_file is not needed
+ * possibility to specify failover databases
index 4c51129f8a58a3aadb7aeba3c2f01afe27e2c733..6677c9d20781b63b1eb82f712a9c2f41b0eeae0d 100644 (file)
@@ -951,15 +951,15 @@ void admin_setup(void)
        create_auth_cache();
 
        /* prepare welcome */
-       pktbuf_static(&msg, db->welcome_msg, sizeof(db->welcome_msg));
+       pktbuf_static(&msg, pool->welcome_msg, sizeof(pool->welcome_msg));
        pktbuf_write_AuthenticationOk(&msg);
        pktbuf_write_ParameterStatus(&msg, "server_version", "8.0/bouncer");
        pktbuf_write_ParameterStatus(&msg, "client_encoding", "UNICODE");
-       pktbuf_write_ParameterStatus(&msg, "server_encoding", "UNICODE");
+       pktbuf_write_ParameterStatus(&msg, "server_encoding", "SQL_ASCII");
        pktbuf_write_ParameterStatus(&msg, "is_superuser", "on");
 
-       db->welcome_msg_len = pktbuf_written(&msg);
-       db->welcome_msg_ready = 1;
+       pool->welcome_msg_len = pktbuf_written(&msg);
+       pool->welcome_msg_ready = 1;
 
        pktbuf_static(&msg, db->startup_params, sizeof(db->startup_params));
        pktbuf_put_string(&msg, "database");
index bf5e529de76cbb9d77d28bce17ef211c5a3258bf..691cf34434c1ee3aced72e65260144ff0c1341b7 100644 (file)
@@ -70,6 +70,7 @@ typedef enum SocketState SocketState;
 #include "mbuf.h"
 #include "sbuf.h"
 #include "pktbuf.h"
+#include "varcache.h"
 
 #include "admin.h"
 #include "loader.h"
@@ -149,10 +150,17 @@ struct PgPool {
        PgStats         newer_stats;
        PgStats         older_stats;
 
+       /* database info to be sent to client */
+       uint8           welcome_msg[256];
+       unsigned        welcome_msg_len;
+
+       VarCache        orig_vars;
+
        /* if last connect failed, there should be delay before next */
        usec_t          last_connect_time;
        unsigned        last_connect_failed:1;
        unsigned        admin:1;
+       unsigned        welcome_msg_ready:1;
 };
 
 #define pool_server_count(pool) ( \
@@ -177,11 +185,6 @@ struct PgDatabase {
        List                    head;
        char                    name[MAX_DBNAME];
 
-       /* database info to be sent to client */
-       uint8                   welcome_msg[512];
-       unsigned                welcome_msg_len;
-       unsigned                welcome_msg_ready:1;
-
        unsigned                db_paused:1;
 
        /* key/val pairs (without user) for startup msg to be sent to server */
@@ -221,6 +224,8 @@ struct PgSocket {
        unsigned        wait_for_response:1;
        /* this (server) socket must be closed ASAP */
        unsigned        close_needed:1;
+       /* setting client vars */
+       unsigned        setting_vars:1;
 
        usec_t          connect_time;   /* when connection was made */
        usec_t          request_time;   /* last activity time */
@@ -231,6 +236,8 @@ struct PgSocket {
        PgUser *        auth_user;
        PgAddr          addr;
 
+       VarCache        vars;
+
        SBuf            sbuf;           /* stream buffer, must be last */
 };
 
@@ -288,6 +295,8 @@ extern int cf_log_connections;
 extern int cf_log_disconnections;
 extern int cf_log_pooler_errors;
 
+extern int cf_disable_varcache;
+
 extern ConfElem bouncer_params[];
 
 
index 89aa21ca52825b4aca53e1e7b1c8532bd54163c1..6af2544fe6bb02b42537371cf11846c4c8749e3c 100644 (file)
@@ -111,6 +111,11 @@ static bool decide_startup_pool(PgSocket *client, MBuf *pkt)
                        dbname = val;
                else if (strcmp(key, "user") == 0)
                        username = val;
+               else {
+                       /* remember requested parameters */
+                       if (varcache_set(&client->vars, key, val, true))
+                               slog_debug(client, "got var: %s=%s", key, val);
+               }
        }
        if (!username) {
                disconnect_client(client, true, "No username supplied");
index 6fb8fcf117962577edd97411f1adfb387f2eab85..cfddef7129c234635d9d01100d1f6df1e7e442d0 100644 (file)
@@ -158,7 +158,7 @@ static void per_loop_activate(PgPool *pool)
                if (!statlist_empty(&pool->idle_server_list)) {
 
                        /* db not fully initialized after reboot */
-                       if (client->wait_for_welcome && !pool->db->welcome_msg_ready) {
+                       if (client->wait_for_welcome && !pool->welcome_msg_ready) {
                                launch_new_connection(pool);
                                continue;
                        }
index e6b2af2883edf8c78c38256f7caa5d36e7d8e8d1..7cae3b50d8bcdcaa97276dc7e9eb44cb5b067090 100644 (file)
@@ -101,6 +101,8 @@ int cf_log_connections = 1;
 int cf_log_disconnections = 1;
 int cf_log_pooler_errors = 1;
 
+int cf_disable_varcache = 0;
+
 /*
  * config file description
  */
@@ -143,6 +145,7 @@ ConfElem bouncer_params[] = {
 {"log_connections",    true, CF_INT, &cf_log_connections},
 {"log_disconnections", true, CF_INT, &cf_log_disconnections},
 {"log_pooler_errors",  true, CF_INT, &cf_log_pooler_errors},
+{"disable_varcache",   true, CF_INT, &cf_disable_varcache},
 {NULL},
 };
 
index 4e3387a518f929bb34af1ff1df27622187db847b..8f307510c940f17e1ff61d8808dbbd0624097eb6 100644 (file)
@@ -91,6 +91,9 @@ static void clean_socket(PgSocket *sk)
        sk->query_start = 0;
 
        sk->auth_user = NULL;
+
+       varcache_clean(&sk->vars);
+       sk->setting_vars = 0;
 }
 
 /* allocate & fill client socket */
@@ -513,6 +516,7 @@ bool find_server(PgSocket *client)
        PgPool *pool = client->pool;
        PgSocket *server;
        bool res;
+       bool varchange = false;
 
        Assert(client->state == CL_ACTIVE);
 
@@ -520,9 +524,9 @@ bool find_server(PgSocket *client)
                return true;
 
        /* try to get idle server, if allowed */
-       if (cf_pause_mode == P_PAUSE)
+       if (cf_pause_mode == P_PAUSE) {
                server = NULL;
-       else {
+       else {
                while (1) {
                        server = first_socket(&pool->idle_server_list);
                        if (!server || server->ready)
@@ -530,14 +534,28 @@ bool find_server(PgSocket *client)
                        disconnect_server(server, true, "idle server got dirty");
                }
        }
+       Assert(!server || server->state == SV_IDLE);
+
+       /* send var changes */
+       if (server && !cf_disable_varcache) {
+               res = varcache_apply(server, client, &varchange);
+               if (!res) {
+                       disconnect_server(server, true, "var change failed");
+                       server = NULL;
+               }
+       }
 
        /* link or send to waiters list */
        if (server) {
-               Assert(server->state == SV_IDLE);
                client->link = server;
                server->link = client;
                change_server_state(server, SV_ACTIVE);
-               res = true;
+               if (varchange) {
+                       sbuf_pause(&client->sbuf);
+                       res = false; /* don't process client data yet */
+                       server->setting_vars = 1;
+               } else
+                       res = true;
        } else {
                pause_client(client);
                Assert(client->state == CL_WAITING);
@@ -737,7 +755,7 @@ void launch_new_connection(PgPool *pool)
 
        /* is it allowed to add servers? */
        total = pool_server_count(pool);
-       if (total >= pool->db->pool_size && pool->db->welcome_msg_ready) {
+       if (total >= pool->db->pool_size && pool->welcome_msg_ready) {
                log_debug("launch_new_connection: pool full (%d >= %d)",
                                total, pool->db->pool_size);
                return;
index dfc22eb1111247d56931eb1e734bfb3490d8bf19..3ac9f569a28d339320673ef810665467f1b51db1 100644 (file)
@@ -124,21 +124,21 @@ void log_server_error(const char *note, MBuf *pkt)
 bool add_welcome_parameter(PgSocket *server,
                           unsigned pkt_type, unsigned pkt_len, MBuf *pkt)
 {
-       PgDatabase *db = server->pool->db;
+       PgPool *pool = server->pool;
        PktBuf msg;
        const char *key, *val;
 
-       if (db->welcome_msg_ready)
+       if (pool->welcome_msg_ready)
                return true;
 
        /* incomplete startup msg from server? */
        if (pkt_len - 5 > mbuf_avail(pkt))
                return false;
 
-       pktbuf_static(&msg, db->welcome_msg + db->welcome_msg_len,
-                     sizeof(db->welcome_msg) - db->welcome_msg_len);
+       pktbuf_static(&msg, pool->welcome_msg + pool->welcome_msg_len,
+                     sizeof(pool->welcome_msg) - pool->welcome_msg_len);
 
-       if (db->welcome_msg_len == 0)
+       if (pool->welcome_msg_len == 0)
                pktbuf_write_AuthenticationOk(&msg);
 
        key = mbuf_get_string(pkt);
@@ -147,9 +147,16 @@ bool add_welcome_parameter(PgSocket *server,
                slog_error(server, "broken ParameterStatus packet");
                return false;
        }
+
        slog_debug(server, "S: param: %s = %s", key, val);
-       pktbuf_write_ParameterStatus(&msg, key, val);
-       db->welcome_msg_len += pktbuf_written(&msg);
+       if (varcache_set(&pool->orig_vars, key, val, true)) {
+               slog_debug(server, "interesting var: %s=%s", key, val);
+               varcache_set(&server->vars, key, val, true);
+       } else {
+               slog_debug(server, "uninteresting var: %s=%s", key, val);
+               pktbuf_write_ParameterStatus(&msg, key, val);
+               pool->welcome_msg_len += pktbuf_written(&msg);
+       }
 
        return true;
 }
@@ -157,10 +164,10 @@ bool add_welcome_parameter(PgSocket *server,
 /* all parameters processed */
 void finish_welcome_msg(PgSocket *server)
 {
-       PgDatabase *db = server->pool->db;
-       if (db->welcome_msg_ready)
+       PgPool *pool = server->pool;
+       if (pool->welcome_msg_ready)
                return;
-       db->welcome_msg_ready = 1;
+       pool->welcome_msg_ready = 1;
 }
 
 bool welcome_client(PgSocket *client)
@@ -168,14 +175,20 @@ bool welcome_client(PgSocket *client)
        int res;
        uint8 buf[1024];
        PktBuf msg;
-       PgDatabase *db = client->pool->db;
+       PgPool *pool = client->pool;
 
        slog_noise(client, "P: welcome_client");
-       if (!db->welcome_msg_ready)
+       if (!pool->welcome_msg_ready)
                return false;
 
+       varcache_print(&client->vars, "welcome/client");
+       varcache_print(&client->pool->orig_vars, "welcome/pool");
+
        pktbuf_static(&msg, buf, sizeof(buf));
-       pktbuf_put_bytes(&msg, db->welcome_msg, db->welcome_msg_len);
+       pktbuf_put_bytes(&msg, pool->welcome_msg, pool->welcome_msg_len);
+
+       varcache_fill_unset(&pool->orig_vars, client);
+       varcache_add_params(&msg, &client->vars);
 
        /* give each client its own cancel key */
        get_random_bytes(client->cancel_key, 8);
index 53b881f01ef539e95cf68cc845fd5cd0066b9992..1e8fd94692935ea8ec1b71a70dee78aa22f02e16 100644 (file)
 
 #include "bouncer.h"
 
+static void check_parameters(PgSocket *server, MBuf *pkt, unsigned pkt_len)
+{
+       const char *key, *val;
+       PgSocket *client = server->link;
+
+       /* incomplete startup msg from server? */
+       if (pkt_len - 5 > mbuf_avail(pkt))
+               return;
+
+       key = mbuf_get_string(pkt);
+       val = mbuf_get_string(pkt);
+       if (!key || !val) {
+               slog_error(server, "broken ParameterStatus packet");
+               return;
+       }
+       slog_debug(server, "S: param: %s = %s", key, val);
+
+       varcache_set(&server->vars, key, val, true);
+
+       if (client) {
+               slog_debug(client, "setting client var: %s='%s'", key, val);
+               varcache_set(&client->vars, key, val, true);
+       }
+
+       return;
+}
+
 /* process packets on server auth phase */
 static bool handle_server_startup(PgSocket *server, MBuf *pkt)
 {
@@ -47,6 +74,7 @@ static bool handle_server_startup(PgSocket *server, MBuf *pkt)
                slog_error(server, "unknown pkt from server: '%c'", pkt_type);
                disconnect_server(server, true, "unknown pkt from server");
                break;
+
        case 'E':               /* ErrorResponse */
                log_server_error("S: login failed", pkt);
                disconnect_server(server, true, "login failed");
@@ -59,9 +87,11 @@ static bool handle_server_startup(PgSocket *server, MBuf *pkt)
                if (!res)
                        disconnect_server(server, false, "failed to answer authreq");
                break;
+
        case 'S':               /* ParameterStatus */
                res = add_welcome_parameter(server, pkt_type, pkt_len, pkt);
                break;
+
        case 'Z':               /* ReadyForQuery */
                /* login ok */
                slog_debug(server, "server login ok, start accepting queries");
@@ -83,6 +113,7 @@ static bool handle_server_startup(PgSocket *server, MBuf *pkt)
                        memcpy(server->cancel_key, mbuf_get_bytes(pkt, 8), 8);
                res = true;
                break;
+
        case 'N':               /* NoticeResponse */
                slog_noise(server, "skipping pkt: %c", pkt_type);
                res = true;
@@ -135,6 +166,11 @@ static bool handle_server_work(PgSocket *server, MBuf *pkt)
                                          "Long transactions not allowed");
                        return false;
                }
+               break;
+
+       case 'S':               /* ParameterStatus */
+               check_parameters(server, pkt, pkt_len);
+               break;
 
        /*
         * 'E' and 'N' packets currently set ->ready to 0.  Correct would
@@ -147,13 +183,24 @@ static bool handle_server_work(PgSocket *server, MBuf *pkt)
         * it later.
         */
        case 'E':               /* ErrorResponse */
+               if (server->setting_vars) {
+                       /*
+                        * the SET and user query will be different TX
+                        * so we cannot report SET error to user.
+                        */
+                       log_server_error("varcache_apply failed", pkt);
+
+                       /*
+                        * client probably gave invalid values in startup pkt.
+                        *
+                        * no reason to keep such guys.
+                        */
+                       disconnect_client(server->link, true, "invalid server parameter");
+               }
        case 'N':               /* NoticeResponse */
+               break;
 
-       /*
-        * chat packets, but server (and thus pooler)
-        * is allowed to buffer them until Sync or Flush
-        * is sent by client.
-        */
+       /* chat packets */
        case '2':               /* BindComplete */
        case '3':               /* CloseComplete */
        case 'c':               /* CopyDone(F/B) */
@@ -172,32 +219,36 @@ static bool handle_server_work(PgSocket *server, MBuf *pkt)
        case 'd':               /* CopyData(F/B) */
        case 'D':               /* DataRow */
        case 't':               /* ParameterDescription */
-       case 'S':               /* ParameterStatus */
        case 'T':               /* RowDescription */
-
-               if (client) {
-                       sbuf_prepare_send(sbuf, &client->sbuf, pkt_len);
-               } else {
-                       if (server->state != SV_TESTED)
-                               slog_warning(server,
-                                            "got packet '%c' from server when not linked",
-                                            pkt_type);
-                       sbuf_prepare_skip(sbuf, pkt_len);
-               }
                break;
        }
        server->ready = ready;
-
-       /* update stats */
        server->pool->stats.server_bytes += pkt_len;
-       if (server->ready && client) {
-               usec_t total;
-               Assert(client->query_start != 0);
-               
-               total = get_cached_time() - client->query_start;
-               client->query_start = 0;
-               server->pool->stats.query_time += total;
-               slog_debug(client, "query time: %d us", (int)total);
+
+       if (server->setting_vars) {
+               Assert(client);
+               sbuf_prepare_skip(sbuf, pkt_len);
+               if (ready) {
+                       server->setting_vars = 0;
+                       sbuf_continue(&client->sbuf);
+               }
+       } else if (client) {
+               sbuf_prepare_send(sbuf, &client->sbuf, pkt_len);
+               if (ready) {
+                       usec_t total;
+                       Assert(client->query_start != 0);
+                       
+                       total = get_cached_time() - client->query_start;
+                       client->query_start = 0;
+                       server->pool->stats.query_time += total;
+                       slog_debug(client, "query time: %d us", (int)total);
+               }
+       } else {
+               if (server->state != SV_TESTED)
+                       slog_warning(server,
+                                    "got packet '%c' from server when not linked",
+                                    pkt_type);
+               sbuf_prepare_skip(sbuf, pkt_len);
        }
 
        return true;
diff --git a/src/varcache.c b/src/varcache.c
new file mode 100644 (file)
index 0000000..2ffd1ea
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * PgBouncer - Lightweight connection pooler for PostgreSQL.
+ * 
+ * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Operations with server config parameters.
+ */
+
+#include "bouncer.h"
+
+struct var_lookup {
+       const char *name;
+       int offset;
+       int len;
+};
+
+static const struct var_lookup lookup [] = {
+{"client_encoding", offsetof(VarCache, client_encoding), VAR_ENCODING_LEN },
+{"datestyle", offsetof(VarCache, datestyle), VAR_DATESTYLE_LEN },
+{"timezone", offsetof(VarCache, timezone), VAR_TIMEZONE_LEN },
+{"standard_conforming_strings", offsetof(VarCache, std_strings), VAR_STDSTR_LEN },
+{NULL},
+};
+
+static char *get_value(VarCache *cache, const struct var_lookup *lk)
+{
+       return (char *)(cache) + lk->offset;
+}
+
+bool varcache_set(VarCache *cache,
+                 const char *key, const char *value,
+                 bool overwrite)
+{
+       int vlen;
+       char *pos;
+       const struct var_lookup *lk;
+
+       for (lk = lookup; lk->name; lk++) {
+               if (strcasecmp(lk->name, key) != 0)
+                       continue;
+
+               pos = get_value(cache, lk);
+
+               if (!overwrite && *pos)
+                       break;
+
+               vlen = strlcpy(pos, value, lk->len);
+               if (vlen >= lk->len)
+                       log_warning("varcache_set(%s) overflow", key);
+               else
+                       log_debug("varcache_set: %s=%s", key, pos);
+               return true;
+       }
+       return false;
+}
+
+static int apply_var(PktBuf *pkt, const char *key,
+                     const char *cval, const char *sval)
+{
+       char buf[128];
+       int len;
+
+       if (strcasecmp(cval, sval) == 0)
+               return 0;
+
+       len = snprintf(buf, sizeof(buf), "SET %s='%s';", key, cval);
+       if (len < sizeof(buf)) {
+               pktbuf_put_bytes(pkt, buf, len);
+               return 1;
+       } else {
+               log_warning("got too long value, skipping");
+               return 0;
+       }
+}
+
+bool varcache_apply(PgSocket *server, PgSocket *client, bool *changes_p)
+{
+       PktBuf pkt;
+       uint8 buf[1024];
+       int changes = 0;
+       const char *cval, *sval;
+       const struct var_lookup *lk;
+       uint8 *debug_sql;
+
+       
+       pktbuf_static(&pkt, buf, sizeof(buf));
+       pktbuf_start_packet(&pkt, 'Q');
+
+       debug_sql = pkt.buf + pkt.write_pos;
+
+       for (lk = lookup; lk->name; lk++) {
+               sval = get_value(&server->vars, lk);
+               cval = get_value(&client->vars, lk);
+               changes += apply_var(&pkt, lk->name, cval, sval);
+       }
+       *changes_p = changes > 0;
+       if (!changes)
+               return true;
+
+       pktbuf_put_char(&pkt, 0);
+       pktbuf_finish_packet(&pkt);
+
+       slog_debug(server, "varcache_apply: %s", debug_sql);
+       return pktbuf_send_immidiate(&pkt, server);
+}
+
+void varcache_fill_unset(VarCache *src, PgSocket *dst)
+{
+       char *srcval, *dstval;
+       const struct var_lookup *lk;
+       for (lk = lookup; lk->name; lk++) {
+               srcval = get_value(src, lk);
+               dstval = get_value(&dst->vars, lk);
+               if (*dstval)
+                       continue;
+
+               /* empty val, copy */
+               slog_debug(dst, "varcache_fill_unset: %s = %s", lk->name, srcval);
+               strlcpy(dstval, srcval, lk->len);
+       }
+}
+
+void varcache_clean(VarCache *cache)
+{
+       cache->client_encoding[0] = 0;
+       cache->datestyle[0] = 0;
+       cache->timezone[0] = 0;
+       cache->std_strings[0] = 0;
+}
+
+void varcache_add_params(PktBuf *pkt, VarCache *vars)
+{
+       char *val;
+       const struct var_lookup *lk;
+       for (lk = lookup; lk->name; lk++) {
+               val = get_value(vars, lk);
+               if (*val)
+                       pktbuf_write_ParameterStatus(pkt, lk->name, val);
+               else
+                       log_error("varcache_add_params: empty param: %s", lk->name);
+       }
+}
+
+
+void varcache_print(VarCache *vars, const char *desc)
+{
+       char *val;
+       const struct var_lookup *lk;
+       for (lk = lookup; lk->name; lk++) {
+               val = get_value(vars, lk);
+               log_debug("%s: %s='%s'", desc, lk->name, val);
+       }
+}
+
+
+
+
diff --git a/src/varcache.h b/src/varcache.h
new file mode 100644 (file)
index 0000000..0a0c6a6
--- /dev/null
@@ -0,0 +1,22 @@
+
+#define VAR_ENCODING_LEN       16
+#define VAR_DATESTYLE_LEN      16
+#define VAR_TIMEZONE_LEN       36
+#define VAR_STDSTR_LEN         4
+
+typedef struct VarCache VarCache;
+
+struct VarCache {
+       char client_encoding[VAR_ENCODING_LEN];
+       char datestyle[VAR_DATESTYLE_LEN];
+       char timezone[VAR_TIMEZONE_LEN];
+       char std_strings[VAR_STDSTR_LEN];
+};
+
+bool varcache_set(VarCache *cache, const char *key, const char *value, bool overwrite);
+bool varcache_apply(PgSocket *server, PgSocket *client, bool *changes_p);
+void varcache_fill_unset(VarCache *src, PgSocket *dst);
+void varcache_clean(VarCache *cache);
+void varcache_add_params(PktBuf *pkt, VarCache *vars);
+void varcache_print(VarCache *vars, const char *desc);
+