wal_decoding: Allow walsender's to connect to a specific database
authorAndres Freund <[email protected]>
Wed, 4 Dec 2013 15:37:38 +0000 (16:37 +0100)
committerAndres Freund <[email protected]>
Sun, 8 Dec 2013 18:20:37 +0000 (19:20 +0100)
Extend the existing 'replication' parameter to not only allow a boolean value
but also "database". If the latter is specified we connect to the database
specified in 'dbname'.

This is useful for future walsender commands which need database interaction,
e.g. changeset extraction.

doc/src/sgml/protocol.sgml
src/backend/postmaster/postmaster.c
src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
src/backend/replication/walsender.c
src/backend/utils/init/postinit.c
src/bin/pg_basebackup/pg_basebackup.c
src/bin/pg_basebackup/pg_receivexlog.c
src/bin/pg_basebackup/receivelog.c
src/include/replication/walsender.h

index 0b2e60eeb13182d34362f1b3376474084cfdc690..2ea14e5d142385cb40f830b01123fd5fd6e6079b 100644 (file)
 
 <para>
 To initiate streaming replication, the frontend sends the
-<literal>replication</> parameter in the startup message. This tells the
-backend to go into walsender mode, wherein a small set of replication commands
-can be issued instead of SQL statements. Only the simple query protocol can be
-used in walsender mode.
+<literal>replication</> parameter in the startup message. A boolean value
+of <literal>true</> tells the backend to go into walsender mode, wherein a
+small set of replication commands can be issued instead of SQL statements. Only
+the simple query protocol can be used in walsender mode.
+Passing a <literal>database</> as the value instructs walsender to connect to
+the database specified in the <literal>dbname</> paramter which will in future
+allow some additional commands to the ones specified below to be run.
 
 The commands accepted in walsender mode are:
 
@@ -1314,7 +1317,7 @@ The commands accepted in walsender mode are:
     <listitem>
      <para>
       Requests the server to identify itself. Server replies with a result
-      set of a single row, containing three fields:
+      set of a single row, containing four fields:
      </para>
 
      <para>
@@ -1356,6 +1359,17 @@ The commands accepted in walsender mode are:
       </listitem>
       </varlistentry>
 
+      <varlistentry>
+      <term>
+       dbname
+      </term>
+      <listitem>
+      <para>
+       Database connected to or NULL.
+      </para>
+      </listitem>
+      </varlistentry>
+
       </variablelist>
      </para>
     </listitem>
index 048a1894e59efa003f515dd29e3440518db6c8e6..5f436dee2d6b49979e2abbe4320f4ae455ea5659 100644 (file)
@@ -1899,10 +1899,21 @@ retry1:
                port->cmdline_options = pstrdup(valptr);
            else if (strcmp(nameptr, "replication") == 0)
            {
-               if (!parse_bool(valptr, &am_walsender))
+               /*
+                * Due to backward compatibility concerns replication is a
+                * bybrid beast which allows the value to be either a boolean
+                * or the string 'database'. The latter connects to a specific
+                * database which is e.g. required for changeset extraction.
+                */
+               if (strcmp(valptr, "database") == 0)
+               {
+                   am_walsender = true;
+                   am_db_walsender = true;
+               }
+               else if (!parse_bool(valptr, &am_walsender))
                    ereport(FATAL,
                            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                            errmsg("invalid value for boolean option \"replication\"")));
+                            errmsg("invalid value for option \"replication\", legal values are false, 0, true, 1 or database")));
            }
            else
            {
@@ -1983,8 +1994,12 @@ retry1:
    if (strlen(port->user_name) >= NAMEDATALEN)
        port->user_name[NAMEDATALEN - 1] = '\0';
 
-   /* Walsender is not related to a particular database */
-   if (am_walsender)
+   /*
+    * Generic walsender, e.g. for streaming replication, is not connected to a
+    * particular database. But walsenders used for logical replication need to
+    * connect to a specific database.
+    */
+   if (am_walsender && !am_db_walsender)
        port->database_name[0] = '\0';
 
    /*
index 6bc0aa1c12cdd53a14ce045ae2614dceddccc2bf..d31292f0ea6f6b6b70a189cd5956b5796949d623 100644 (file)
@@ -130,7 +130,7 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
                        "the primary server: %s",
                        PQerrorMessage(streamConn))));
    }
-   if (PQnfields(res) != 3 || PQntuples(res) != 1)
+   if (PQnfields(res) < 3 || PQntuples(res) != 1)
    {
        int         ntuples = PQntuples(res);
        int         nfields = PQnfields(res);
@@ -138,7 +138,7 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
        PQclear(res);
        ereport(ERROR,
                (errmsg("invalid response from primary server"),
-                errdetail("Expected 1 tuple with 3 fields, got %d tuples with %d fields.",
+                errdetail("Expected 1 tuple with 3 or more fields, got %d tuples with %d fields.",
                           ntuples, nfields)));
    }
    primary_sysid = PQgetvalue(res, 0, 0);
index afd559dae46a567f4685bc6c103d07be93e1d809..e2a42c6e08a0fef1b539819dd865123e3c08c3d7 100644 (file)
 #include "access/timeline.h"
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "access/xact.h"
+
 #include "catalog/pg_type.h"
+#include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -89,9 +92,10 @@ WalSndCtlData *WalSndCtl = NULL;
 WalSnd    *MyWalSnd = NULL;
 
 /* Global state */
-bool       am_walsender = false;       /* Am I a walsender process ? */
+bool       am_walsender = false;       /* Am I a walsender process? */
 bool       am_cascading_walsender = false;     /* Am I cascading WAL to
-                                                * another standby ? */
+                                                * another standby? */
+bool       am_db_walsender = false;        /* connect to database? */
 
 /* User-settable parameters for walsender */
 int            max_wal_senders = 0;    /* the maximum number of concurrent walsenders */
@@ -243,10 +247,12 @@ IdentifySystem(void)
    char        tli[11];
    char        xpos[MAXFNAMELEN];
    XLogRecPtr  logptr;
+   char       *dbname = NULL;
 
    /*
-    * Reply with a result set with one row, three columns. First col is
-    * system ID, second is timeline ID, and third is current xlog location.
+    * Reply with a result set with one row, four columns. First col is system
+    * ID, second is timeline ID, third is current xlog location and the fourth
+    * contains the database name if we are connected to one.
     */
 
    snprintf(sysid, sizeof(sysid), UINT64_FORMAT,
@@ -265,9 +271,23 @@ IdentifySystem(void)
 
    snprintf(xpos, sizeof(xpos), "%X/%X", (uint32) (logptr >> 32), (uint32) logptr);
 
+   if (MyDatabaseId != InvalidOid)
+   {
+       MemoryContext cur = CurrentMemoryContext;
+
+       /* syscache access needs a transaction env. */
+       StartTransactionCommand();
+       /* make dbname live outside TX context */
+       MemoryContextSwitchTo(cur);
+       dbname = get_database_name(MyDatabaseId);
+       CommitTransactionCommand();
+       /* CommitTransactionCommand switches to TopMemoryContext */
+       MemoryContextSwitchTo(cur);
+   }
+
    /* Send a RowDescription message */
    pq_beginmessage(&buf, 'T');
-   pq_sendint(&buf, 3, 2);     /* 3 fields */
+   pq_sendint(&buf, 4, 2);     /* 4 fields */
 
    /* first field */
    pq_sendstring(&buf, "systemid");    /* col name */
@@ -288,24 +308,43 @@ IdentifySystem(void)
    pq_sendint(&buf, 0, 2);     /* format code */
 
    /* third field */
-   pq_sendstring(&buf, "xlogpos");
-   pq_sendint(&buf, 0, 4);
-   pq_sendint(&buf, 0, 2);
-   pq_sendint(&buf, TEXTOID, 4);
-   pq_sendint(&buf, -1, 2);
-   pq_sendint(&buf, 0, 4);
-   pq_sendint(&buf, 0, 2);
+   pq_sendstring(&buf, "xlogpos"); /* col name */
+   pq_sendint(&buf, 0, 4);     /* table oid */
+   pq_sendint(&buf, 0, 2);     /* attnum */
+   pq_sendint(&buf, TEXTOID, 4);       /* type oid */
+   pq_sendint(&buf, -1, 2);        /* typlen */
+   pq_sendint(&buf, 0, 4);     /* typmod */
+   pq_sendint(&buf, 0, 2);     /* format code */
+
+   /* fourth field */
+   pq_sendstring(&buf, "dbname");  /* col name */
+   pq_sendint(&buf, 0, 4);     /* table oid */
+   pq_sendint(&buf, 0, 2);     /* attnum */
+   pq_sendint(&buf, TEXTOID, 4);       /* type oid */
+   pq_sendint(&buf, -1, 2);        /* typlen */
+   pq_sendint(&buf, 0, 4);     /* typmod */
+   pq_sendint(&buf, 0, 2);     /* format code */
    pq_endmessage(&buf);
 
    /* Send a DataRow message */
    pq_beginmessage(&buf, 'D');
-   pq_sendint(&buf, 3, 2);     /* # of columns */
+   pq_sendint(&buf, 4, 2);     /* # of columns */
    pq_sendint(&buf, strlen(sysid), 4); /* col1 len */
    pq_sendbytes(&buf, (char *) &sysid, strlen(sysid));
    pq_sendint(&buf, strlen(tli), 4);   /* col2 len */
    pq_sendbytes(&buf, (char *) tli, strlen(tli));
    pq_sendint(&buf, strlen(xpos), 4);  /* col3 len */
    pq_sendbytes(&buf, (char *) xpos, strlen(xpos));
+   /* send NULL if not connected to a database */
+   if (dbname)
+   {
+       pq_sendint(&buf, strlen(dbname), 4);    /* col4 len */
+       pq_sendbytes(&buf, (char *) dbname, strlen(dbname));
+   }
+   else
+   {
+       pq_sendint(&buf, -1, 4);    /* col4 len */
+   }
 
    pq_endmessage(&buf);
 }
index 2c7f0f1764113602504386f4972371a97e8fa332..98fa3b9fea340a799ff216924eb6712c1849f4f6 100644 (file)
@@ -725,7 +725,12 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
            ereport(FATAL,
                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                     errmsg("must be superuser or replication role to start walsender")));
+   }
 
+   if (am_walsender &&
+       (in_dbname == NULL || in_dbname[0] == '\0') &&
+       dboid == InvalidOid)
+   {
        /* process any options passed in the startup packet */
        if (MyProcPort != NULL)
            process_startup_options(MyProcPort, am_superuser);
index 6706c0c7a90b701586d7eca8a4e7354aac9074df..d4413841c863eb69bd6229a00afada93abe5b66a 100644 (file)
@@ -1367,11 +1367,11 @@ BaseBackup(void)
                progname, "IDENTIFY_SYSTEM", PQerrorMessage(conn));
        disconnect_and_exit(1);
    }
-   if (PQntuples(res) != 1 || PQnfields(res) != 3)
+   if (PQntuples(res) != 1 || PQnfields(res) < 3)
    {
        fprintf(stderr,
-               _("%s: could not identify system: got %d rows and %d fields, expected %d rows and %d fields\n"),
-               progname, PQntuples(res), PQnfields(res), 1, 3);
+               _("%s: could not identify system: got %d rows and %d fields, expected 1 row and 3 or more fields\n"),
+               progname, PQntuples(res), PQnfields(res));
        disconnect_and_exit(1);
    }
    sysidentifier = pg_strdup(PQgetvalue(res, 0, 0));
index 252a7e08d6727e9e3aa08cf1fc3eb7e925638104..b038aed9d60a9b243aa7848514cf99d50450b785 100644 (file)
@@ -267,11 +267,11 @@ StreamLog(void)
                progname, "IDENTIFY_SYSTEM", PQerrorMessage(conn));
        disconnect_and_exit(1);
    }
-   if (PQntuples(res) != 1 || PQnfields(res) != 3)
+   if (PQntuples(res) != 1 || PQnfields(res) < 3)
    {
        fprintf(stderr,
-               _("%s: could not identify system: got %d rows and %d fields, expected %d rows and %d fields\n"),
-               progname, PQntuples(res), PQnfields(res), 1, 3);
+               _("%s: could not identify system: got %d rows and %d fields, expected 1 row and 3 or more fields\n"),
+               progname, PQntuples(res), PQnfields(res));
        disconnect_and_exit(1);
    }
    servertli = atoi(PQgetvalue(res, 0, 1));
index aca1a9e8b1a764da47b58b5650f2b4c811b41ad5..7c85d306b206fda94c2c92fc70b5f7e67282f6e1 100644 (file)
@@ -533,11 +533,11 @@ ReceiveXlogStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
            PQclear(res);
            return false;
        }
-       if (PQnfields(res) != 3 || PQntuples(res) != 1)
+       if (PQntuples(res) != 1 || PQnfields(res) < 3)
        {
            fprintf(stderr,
-                   _("%s: could not identify system: got %d rows and %d fields, expected %d rows and %d fields\n"),
-                   progname, PQntuples(res), PQnfields(res), 1, 3);
+                   _("%s: could not identify system: got %d rows and %d fields, expected 1 row and 3 or more fields\n"),
+                   progname, PQntuples(res), PQnfields(res));
            PQclear(res);
            return false;
        }
index 2cc7ddfa37345f8db564c86ebcd2f599e13c4a27..5097235812f304c954f6b1cbe497eecddbe4c3b7 100644 (file)
@@ -19,6 +19,7 @@
 /* global state */
 extern bool am_walsender;
 extern bool am_cascading_walsender;
+extern bool am_db_walsender;
 extern bool wake_wal_senders;
 
 /* user-settable parameters */