logical changeset generation v6

Started by Andres Freundover 12 years ago198 messages
#1Andres Freund
andres@2ndquadrant.com
9 attachment(s)

Hi!

Attached you can find the newest version of the logical changeset
generation patchset. Reduced by a couple of patches because the have
been committed last round. Hurray! and thanks.

The explanation of how to use the patch from last time:
http://archives.postgresql.org/message-id/20130614224817.GA19641%40awork2.anarazel.de
still holds true, so I am not going to repeat it here.

The individual patches are:
0001 wal_decoding: Allow walsender's to connect to a specific database
One logical decoding operation can only decode content from one
database at a time. Because of that the walsender needs to connect
to a specific database. The earlier "replication=on/off" parameter
now also has a valid parameter "database" which allows that.

0002 wal_decoding: Log xl_running_xact's at a higher frequency than checkpoints are done
Imo relatively unproblematic and even useful without changeset extraction.

0003 wal_decoding: Add information about a tables primary key to struct RelationData
Not much comments on this in the past. Kevin thinks we might want to
choose the best candidate key in a more elaborate manner.

0004 wal_decoding: Introduce wal decoding via catalog timetravel
The actual feature. Got cleaned up and shrunk since the last submission.

0005 wal_decoding: test_decoding: Add a simple decoding module in contrib
Example output plugin that's also used for testing.

0006 wal_decoding: pg_receivellog: Introduce pg_receivexlog equivalent for logical changes
Commandline utility to receive the changestream and manipulate slots.

0007 wal_decoding: test_logical_decoding: Add extension for easier testing of logical decoding
Allows to not only create and destroy logical slots which is part of
0005, but also receive the changestream via an SQL SRF.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

0001-Improve-regression-test-for-8410.patchtext/x-patch; charset=us-asciiDownload
>From 14f521d9e2e9efde8b19a1664b2cf2056a2e9520 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 31 Aug 2013 01:54:05 +0200
Subject: [PATCH] Improve regression test for #8410

The previous version of the query disregarded the result of the MergeAppend
instead of checking its results.
---
 src/test/regress/expected/inherit.out | 49 +++++++++++++++++------------------
 src/test/regress/sql/inherit.sql      | 16 ++++++------
 2 files changed, 32 insertions(+), 33 deletions(-)

diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 8520281..a2ef7ef 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1353,42 +1353,41 @@ ORDER BY x, y;
 -- exercise rescan code path via a repeatedly-evaluated subquery
 explain (costs off)
 SELECT
-    (SELECT g.i FROM (
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+    ARRAY(SELECT f.i FROM (
+        (SELECT d + g.i FROM generate_series(4, 30, 3) d ORDER BY 1)
         UNION ALL
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+        (SELECT d + g.i FROM generate_series(0, 30, 5) d ORDER BY 1)
     ) f(i)
-    ORDER BY f.i LIMIT 1)
+    ORDER BY f.i LIMIT 10)
 FROM generate_series(1, 3) g(i);
-                                     QUERY PLAN                                     
-------------------------------------------------------------------------------------
+                           QUERY PLAN                           
+----------------------------------------------------------------
  Function Scan on generate_series g
    SubPlan 1
      ->  Limit
-           ->  Result
-                 ->  Merge Append
-                       Sort Key: generate_series.generate_series
-                       ->  Sort
-                             Sort Key: generate_series.generate_series
-                             ->  Function Scan on generate_series
-                       ->  Sort
-                             Sort Key: generate_series_1.generate_series
-                             ->  Function Scan on generate_series generate_series_1
-(12 rows)
+           ->  Merge Append
+                 Sort Key: ((d.d + g.i))
+                 ->  Sort
+                       Sort Key: ((d.d + g.i))
+                       ->  Function Scan on generate_series d
+                 ->  Sort
+                       Sort Key: ((d_1.d + g.i))
+                       ->  Function Scan on generate_series d_1
+(11 rows)
 
 SELECT
-    (SELECT g.i FROM (
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+    ARRAY(SELECT f.i FROM (
+        (SELECT d + g.i FROM generate_series(4, 30, 3) d ORDER BY 1)
         UNION ALL
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+        (SELECT d + g.i FROM generate_series(0, 30, 5) d ORDER BY 1)
     ) f(i)
-    ORDER BY f.i LIMIT 1)
+    ORDER BY f.i LIMIT 10)
 FROM generate_series(1, 3) g(i);
- i 
----
- 1
- 2
- 3
+            array             
+------------------------------
+ {1,5,6,8,11,11,14,16,17,20}
+ {2,6,7,9,12,12,15,17,18,21}
+ {3,7,8,10,13,13,16,18,19,22}
 (3 rows)
 
 reset enable_seqscan;
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index e88a584..8637655 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -442,21 +442,21 @@ ORDER BY x, y;
 -- exercise rescan code path via a repeatedly-evaluated subquery
 explain (costs off)
 SELECT
-    (SELECT g.i FROM (
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+    ARRAY(SELECT f.i FROM (
+        (SELECT d + g.i FROM generate_series(4, 30, 3) d ORDER BY 1)
         UNION ALL
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+        (SELECT d + g.i FROM generate_series(0, 30, 5) d ORDER BY 1)
     ) f(i)
-    ORDER BY f.i LIMIT 1)
+    ORDER BY f.i LIMIT 10)
 FROM generate_series(1, 3) g(i);
 
 SELECT
-    (SELECT g.i FROM (
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+    ARRAY(SELECT f.i FROM (
+        (SELECT d + g.i FROM generate_series(4, 30, 3) d ORDER BY 1)
         UNION ALL
-        (SELECT * FROM generate_series(1, 2) ORDER BY 1)
+        (SELECT d + g.i FROM generate_series(0, 30, 5) d ORDER BY 1)
     ) f(i)
-    ORDER BY f.i LIMIT 1)
+    ORDER BY f.i LIMIT 10)
 FROM generate_series(1, 3) g(i);
 
 reset enable_seqscan;
-- 
1.8.2.rc2.4.g7799588.dirty

0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patchtext/x-patch; charset=us-asciiDownload
>From 078dcdd696604801c898decbe478e3c99fe257a6 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 1/8] wal_decoding: Allow walsender's to connect to a specific
 database

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                         | 24 +++++++++---
 src/backend/postmaster/postmaster.c                | 23 ++++++++++--
 .../libpqwalreceiver/libpqwalreceiver.c            |  4 +-
 src/backend/replication/walsender.c                | 43 +++++++++++++++++++---
 src/backend/utils/init/postinit.c                  |  5 +++
 src/bin/pg_basebackup/pg_basebackup.c              |  4 +-
 src/bin/pg_basebackup/pg_receivexlog.c             |  4 +-
 src/bin/pg_basebackup/receivelog.c                 |  4 +-
 src/include/replication/walsender.h                |  1 +
 9 files changed, 89 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 0b2e60e..2ea14e5 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1301,10 +1301,13 @@
 
 <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>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 01d2618..a31b01d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,10 +1894,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';
 
 	/*
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 6bc0aa1..ee0f1fe 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -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) != 4 || 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 4 fields, got %d tuples with %d fields.",
 						   ntuples, nfields)));
 	}
 	primary_sysid = PQgetvalue(res, 0, 0);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index afd559d..b00a91a 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -46,7 +46,10 @@
 #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 */
@@ -295,17 +315,28 @@ IdentifySystem(void)
 	pq_sendint(&buf, -1, 2);
 	pq_sendint(&buf, 0, 4);
 	pq_sendint(&buf, 0, 2);
+
+	/* fourth field */
+	pq_sendstring(&buf, "dbname");
+	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_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));
+	pq_sendint(&buf, strlen(dbname), 4);	/* col4 len */
+	pq_sendbytes(&buf, (char *) dbname, strlen(dbname));
 
 	pq_endmessage(&buf);
 }
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 2c7f0f1..56c352c 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -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);
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index a1e12a8..89e2376 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -1361,11 +1361,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) != 4)
 	{
 		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);
+				progname, PQntuples(res), PQnfields(res), 1, 4);
 		disconnect_and_exit(1);
 	}
 	sysidentifier = pg_strdup(PQgetvalue(res, 0, 0));
diff --git a/src/bin/pg_basebackup/pg_receivexlog.c b/src/bin/pg_basebackup/pg_receivexlog.c
index 787a395..fe8aef6 100644
--- a/src/bin/pg_basebackup/pg_receivexlog.c
+++ b/src/bin/pg_basebackup/pg_receivexlog.c
@@ -252,11 +252,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) != 4)
 	{
 		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);
+				progname, PQntuples(res), PQnfields(res), 1, 4);
 		disconnect_and_exit(1);
 	}
 	servertli = atoi(PQgetvalue(res, 0, 1));
diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c
index d56a4d7..22a5340 100644
--- a/src/bin/pg_basebackup/receivelog.c
+++ b/src/bin/pg_basebackup/receivelog.c
@@ -534,11 +534,11 @@ ReceiveXlogStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 			PQclear(res);
 			return false;
 		}
-		if (PQnfields(res) != 3 || PQntuples(res) != 1)
+		if (PQnfields(res) != 4 || PQntuples(res) != 1)
 		{
 			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);
+					progname, PQntuples(res), PQnfields(res), 1, 4);
 			PQclear(res);
 			return false;
 		}
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 2cc7ddf..5097235 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -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 */
-- 
1.8.4.21.g992c386.dirty

0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patchtext/x-patch; charset=us-asciiDownload
>From 9cd917e256159c1947aa56afa20b89cd1783c256 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 2/8] wal_decoding: Log xl_running_xact's at a higher frequency
 than checkpoints are done

Logging information about running xacts more frequently is beneficial for both,
hot standby which can reach consistency faster and release some resources
earlier using this information, and future logical replication which can
initialize quicker using this.

Do so in the background writer which seems to be the best choice as its
regularly running and shouldn't be busy for too long without getting back into
its main loop.

Also mark xl_running_xact records as being relevant for async commit so the wal
writer writes them out soonish instead of possibly waiting a long time.
---
 src/backend/postmaster/bgwriter.c | 62 +++++++++++++++++++++++++++++++++++++++
 src/backend/storage/ipc/standby.c | 27 ++++++++++++++---
 src/include/storage/standby.h     |  2 +-
 3 files changed, 86 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 286ae86..13d57c5 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -54,9 +54,11 @@
 #include "storage/shmem.h"
 #include "storage/smgr.h"
 #include "storage/spin.h"
+#include "storage/standby.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
+#include "utils/timestamp.h"
 
 
 /*
@@ -71,6 +73,20 @@ int			BgWriterDelay = 200;
 #define HIBERNATE_FACTOR			50
 
 /*
+ * Interval in which standby snapshots are logged into the WAL stream, in
+ * milliseconds.
+ */
+#define LOG_SNAPSHOT_INTERVAL_MS 15000
+
+/*
+ * LSN and timestamp at which we last issued a LogStandbySnapshot(), to avoid
+ * doing so too often or repeatedly if there has been no other write activity
+ * in the system.
+ */
+static TimestampTz last_snapshot_ts;
+static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
+
+/*
  * Flags set by interrupt handlers for later service in the main loop.
  */
 static volatile sig_atomic_t got_SIGHUP = false;
@@ -142,6 +158,12 @@ BackgroundWriterMain(void)
 	CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer");
 
 	/*
+	 * We just started, assume there has been either a shutdown or
+	 * end-of-recovery snapshot.
+	 */
+	last_snapshot_ts = GetCurrentTimestamp();
+
+	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
 	 * possible memory leaks.  Formerly this code just ran in
@@ -276,6 +298,46 @@ BackgroundWriterMain(void)
 		}
 
 		/*
+		 * Log a new xl_running_xacts every now and then so replication can get
+		 * into a consistent state faster (think of suboverflowed snapshots)
+		 * and clean up resources (locks, KnownXids*) more frequently. The
+		 * costs of this are relatively low, so doing it 4 times
+		 * (LOG_SNAPSHOT_INTERVAL_MS) a minute seems fine.
+		 *
+		 * We assume the interval for writing xl_running_xacts is
+		 * significantly bigger than BgWriterDelay, so we don't complicate the
+		 * overall timeout handling but just assume we're going to get called
+		 * often enough even if hibernation mode is active. It's not that
+		 * important that log_snap_interval_ms is met strictly. To make sure
+		 * we're not waking the disk up unneccesarily on an idle system we
+		 * check whether there has been any WAL inserted since the last time
+		 * we've logged a running xacts.
+		 *
+		 * We do this logging in the bgwriter as its the only process thats
+		 * run regularly and returns to its mainloop all the
+		 * time. E.g. Checkpointer, when active, is barely ever in its
+		 * mainloop and thus makes it hard to log regularly.
+		 */
+		if (XLogStandbyInfoActive() && !RecoveryInProgress())
+		{
+			TimestampTz timeout = 0;
+			TimestampTz now = GetCurrentTimestamp();
+			timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
+												  LOG_SNAPSHOT_INTERVAL_MS);
+
+			/*
+			 * only log if enough time has passed and some xlog record has been
+			 * inserted.
+			 */
+			if (now >= timeout &&
+				last_snapshot_lsn != GetXLogInsertRecPtr())
+			{
+				last_snapshot_lsn = LogStandbySnapshot();
+				last_snapshot_ts = now;
+			}
+		}
+
+		/*
 		 * Sleep until we are signaled or BgWriterDelay has elapsed.
 		 *
 		 * Note: the feedback control loop in BgBufferSync() expects that we
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index c704412..97da1a0 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -42,7 +42,7 @@ static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlis
 									   ProcSignalReason reason);
 static void ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid);
 static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
-static void LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
+static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
 static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
 
 
@@ -853,10 +853,13 @@ standby_redo(XLogRecPtr lsn, XLogRecord *record)
  * currently running xids, performed by StandbyReleaseOldLocks().
  * Zero xids should no longer be possible, but we may be replaying WAL
  * from a time when they were possible.
+ *
+ * Returns the RecPtr of the last inserted record.
  */
-void
+XLogRecPtr
 LogStandbySnapshot(void)
 {
+	XLogRecPtr recptr;
 	RunningTransactions running;
 	xl_standby_lock *locks;
 	int			nlocks;
@@ -876,9 +879,12 @@ LogStandbySnapshot(void)
 	 * record we write, because standby will open up when it sees this.
 	 */
 	running = GetRunningTransactionData();
-	LogCurrentRunningXacts(running);
+	recptr = LogCurrentRunningXacts(running);
+
 	/* GetRunningTransactionData() acquired XidGenLock, we must release it */
 	LWLockRelease(XidGenLock);
+
+	return recptr;
 }
 
 /*
@@ -889,7 +895,7 @@ LogStandbySnapshot(void)
  * is a contiguous chunk of memory and never exists fully until it is
  * assembled in WAL.
  */
-static void
+static XLogRecPtr
 LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 {
 	xl_running_xacts xlrec;
@@ -939,6 +945,19 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 			 CurrRunningXacts->oldestRunningXid,
 			 CurrRunningXacts->latestCompletedXid,
 			 CurrRunningXacts->nextXid);
+
+	/*
+	 * Ensure running_xacts information is synced to disk not too far in the
+	 * future. We don't want to stall anything though (i.e. use XLogFlush()),
+	 * so we let the wal writer do it during normal
+	 * operation. XLogSetAsyncXactLSN() conveniently will mark the LSN as
+	 * to-be-synced and nudge the WALWriter into action if sleeping. Check
+	 * XLogBackgroundFlush() for details why a record might not be flushed
+	 * without it.
+	 */
+	XLogSetAsyncXactLSN(recptr);
+
+	return recptr;
 }
 
 /*
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 7f3f051..d4a8fe4 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -113,6 +113,6 @@ typedef RunningTransactionsData *RunningTransactions;
 extern void LogAccessExclusiveLock(Oid dbOid, Oid relOid);
 extern void LogAccessExclusiveLockPrepare(void);
 
-extern void LogStandbySnapshot(void);
+extern XLogRecPtr LogStandbySnapshot(void);
 
 #endif   /* STANDBY_H */
-- 
1.8.4.21.g992c386.dirty

0003-wal_decoding-Add-information-about-a-tables-primary-.patchtext/x-patch; charset=us-asciiDownload
>From 03866bd4e623c8b28deba62288054ab445400b98 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 3/8] wal_decoding: Add information about a tables primary key
 to struct RelationData

'rd_primary' now contains the Oid of an index over uniquely identifying
columns. Several types of indexes are interesting and are collected in that
order:
* Primary Key
* oid index
* the first (OID order) unique, immediate, non-partial and
  non-expression index over one or more NOT NULL'ed columns

To gather rd_primary value RelationGetIndexList() needs to have been called.

This is helpful because for logical replication we frequently - on the sending
and receiving side - need to lookup that index and RelationGetIndexList already
gathers all the necessary information.

This could be used to replace tablecmd.c's transformFkeyGetPrimaryKey, but
would change the meaning of that, so it seems to require additional discussion.
---
 src/backend/utils/cache/relcache.c | 52 +++++++++++++++++++++++++++++++++++---
 src/include/utils/rel.h            | 12 +++++++++
 2 files changed, 61 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b4cc6ad..44dd0d2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3462,7 +3462,9 @@ RelationGetIndexList(Relation relation)
 	ScanKeyData skey;
 	HeapTuple	htup;
 	List	   *result;
-	Oid			oidIndex;
+	Oid			oidIndex = InvalidOid;
+	Oid			pkeyIndex = InvalidOid;
+	Oid			candidateIndex = InvalidOid;
 	MemoryContext oldcxt;
 
 	/* Quick exit if we already computed the list. */
@@ -3519,17 +3521,61 @@ RelationGetIndexList(Relation relation)
 		Assert(!isnull);
 		indclass = (oidvector *) DatumGetPointer(indclassDatum);
 
+		if (!IndexIsValid(index))
+			continue;
+
 		/* Check to see if it is a unique, non-partial btree index on OID */
-		if (IndexIsValid(index) &&
-			index->indnatts == 1 &&
+		if (index->indnatts == 1 &&
 			index->indisunique && index->indimmediate &&
 			index->indkey.values[0] == ObjectIdAttributeNumber &&
 			indclass->values[0] == OID_BTREE_OPS_OID &&
 			heap_attisnull(htup, Anum_pg_index_indpred))
 			oidIndex = index->indexrelid;
+
+		if (index->indisunique &&
+			index->indimmediate &&
+			heap_attisnull(htup, Anum_pg_index_indpred))
+		{
+			/* always prefer primary keys */
+			if (index->indisprimary)
+				pkeyIndex = index->indexrelid;
+			else if (!OidIsValid(pkeyIndex)
+					&& !OidIsValid(oidIndex)
+					&& !OidIsValid(candidateIndex))
+			{
+				int key;
+				bool found = true;
+				for (key = 0; key < index->indnatts; key++)
+				{
+					int16 attno = index->indkey.values[key];
+					Form_pg_attribute attr;
+					/* internal column, like oid */
+					if (attno <= 0)
+						continue;
+
+					attr = relation->rd_att->attrs[attno - 1];
+					if (!attr->attnotnull)
+					{
+						found = false;
+						break;
+					}
+				}
+				if (found)
+					candidateIndex = index->indexrelid;
+			}
+		}
 	}
 
 	systable_endscan(indscan);
+
+	if (OidIsValid(pkeyIndex))
+		relation->rd_primary = pkeyIndex;
+	/* prefer oid indexes over normal candidate ones */
+	else if (OidIsValid(oidIndex))
+		relation->rd_primary = oidIndex;
+	else if (OidIsValid(candidateIndex))
+		relation->rd_primary = candidateIndex;
+
 	heap_close(indrel, AccessShareLock);
 
 	/* Now save a copy of the completed list in the relcache entry. */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 589c9a8..0281b4b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -111,6 +111,18 @@ typedef struct RelationData
 	TriggerDesc *trigdesc;		/* Trigger info, or NULL if rel has none */
 
 	/*
+	 * The 'best' primary or candidate key that has been found, only set
+	 * correctly if RelationGetIndexList has been called/rd_indexvalid > 0.
+	 *
+	 * Indexes are chosen in the following order:
+	 * * Primary Key
+	 * * oid index
+	 * * the first (OID order) unique, immediate, non-partial and
+	 *   non-expression index over one or more NOT NULL'ed columns
+	 */
+	Oid rd_primary;
+
+	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
 	 * Note that you can NOT look into rd_rel for this data.  NULL means "use
 	 * defaults".
-- 
1.8.4.21.g992c386.dirty

0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patchtext/x-patch; charset=us-asciiDownload
>From 848b60044c7ed27f205abbf67ed2389f6827934c Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 4/8] wal_decoding: Introduce wal decoding via catalog
 timetravel

This introduces several things:
* 'reorderbuffer' module which reassembles transactions from a stream of interspersed changes
* 'snapbuilder' which builds catalog snapshots so that tuples from wal can be understood
* logging more data into wal to facilitate logical decoding
* wal decoding into an reorderbuffer
* shared library output plugins with 5 callbacks
 * init
 * begin
 * change
 * commit
* walsender infrastructur to stream out changes and to keep the global xmin low enough
 * INIT_LOGICAL_REPLICATION $plugin; waits till a consistent snapshot is built and returns
   * initial LSN
   * replication slot identifier
   * id of a pg_export() style snapshot
 * START_LOGICAL_REPLICATION $id $lsn; streams out changes
 * uses named output plugins for output specification

Todo:
* better integrated testing infrastructure
* more docs about the internals

Lowlevel:
* resource owner handling is suboptimal
* invalidations from uninteresting transactions (e.g. from other databases, old ones)
  need to be processed anyway
* error handling in walsender is suboptimal
* pg_receivellog needs to send a reply immediately when postgres is shutting down

Input, Testing and Review by:
Heikki Linnakangas
Kevin Grittner
Michael Paquier
Abhijit Menon-Sen
Peter Gheogegan
Robert Haas
Simon Riggs
Steve Singer

Code By:
Andres Freund

With code contributions by:
Abhijit Menon-Sen
Craig Ringer
Alvaro Herrera

Conflicts:
	src/backend/replication/repl_gram.y
---
 src/backend/access/common/reloptions.c          |   10 +
 src/backend/access/heap/heapam.c                |  465 ++++-
 src/backend/access/heap/pruneheap.c             |    2 +
 src/backend/access/index/indexam.c              |   14 +-
 src/backend/access/rmgrdesc/heapdesc.c          |    9 +
 src/backend/access/rmgrdesc/xlogdesc.c          |    1 +
 src/backend/access/transam/twophase.c           |    4 +-
 src/backend/access/transam/xact.c               |   48 +-
 src/backend/access/transam/xlog.c               |   14 +-
 src/backend/catalog/catalog.c                   |   14 +-
 src/backend/catalog/index.c                     |   15 +-
 src/backend/catalog/system_views.sql            |   10 +
 src/backend/commands/analyze.c                  |    2 +-
 src/backend/commands/cluster.c                  |    2 +
 src/backend/commands/trigger.c                  |    3 +-
 src/backend/commands/vacuum.c                   |    5 +-
 src/backend/commands/vacuumlazy.c               |    3 +
 src/backend/postmaster/postmaster.c             |    2 +-
 src/backend/replication/Makefile                |    2 +
 src/backend/replication/logical/Makefile        |   19 +
 src/backend/replication/logical/decode.c        |  687 ++++++
 src/backend/replication/logical/logical.c       | 1046 ++++++++++
 src/backend/replication/logical/logicalfuncs.c  |  361 ++++
 src/backend/replication/logical/reorderbuffer.c | 2548 +++++++++++++++++++++++
 src/backend/replication/logical/snapbuild.c     | 1581 ++++++++++++++
 src/backend/replication/repl_gram.y             |   75 +-
 src/backend/replication/repl_scanner.l          |   55 +-
 src/backend/replication/walreceiver.c           |    2 +-
 src/backend/replication/walsender.c             |  733 ++++++-
 src/backend/storage/ipc/ipci.c                  |    3 +
 src/backend/storage/ipc/procarray.c             |   72 +-
 src/backend/storage/ipc/standby.c               |   15 +
 src/backend/utils/cache/inval.c                 |    4 +-
 src/backend/utils/cache/relcache.c              |  113 +-
 src/backend/utils/misc/guc.c                    |   12 +
 src/backend/utils/misc/postgresql.conf.sample   |   11 +-
 src/backend/utils/time/snapmgr.c                |    7 +-
 src/backend/utils/time/tqual.c                  |  270 ++-
 src/bin/initdb/initdb.c                         |    4 +-
 src/bin/pg_controldata/pg_controldata.c         |    2 +
 src/include/access/heapam_xlog.h                |   59 +-
 src/include/access/transam.h                    |    5 +
 src/include/access/xact.h                       |    1 +
 src/include/access/xlog.h                       |    8 +-
 src/include/access/xlogreader.h                 |   13 +-
 src/include/catalog/catalog.h                   |    1 +
 src/include/catalog/pg_proc.h                   |    6 +
 src/include/commands/vacuum.h                   |    2 +-
 src/include/nodes/nodes.h                       |    3 +
 src/include/nodes/replnodes.h                   |   35 +
 src/include/replication/decode.h                |   20 +
 src/include/replication/logical.h               |  198 ++
 src/include/replication/logicalfuncs.h          |   19 +
 src/include/replication/output_plugin.h         |   70 +
 src/include/replication/reorderbuffer.h         |  342 +++
 src/include/replication/snapbuild.h             |   79 +
 src/include/replication/walsender_private.h     |    6 +-
 src/include/storage/itemptr.h                   |    3 +
 src/include/storage/lwlock.h                    |    1 +
 src/include/storage/procarray.h                 |    2 +-
 src/include/storage/sinval.h                    |    2 +
 src/include/utils/inval.h                       |    1 +
 src/include/utils/rel.h                         |   30 +-
 src/include/utils/relcache.h                    |   11 +-
 src/include/utils/snapmgr.h                     |    3 +
 src/include/utils/tqual.h                       |   20 +-
 src/test/regress/expected/rules.out             |    9 +-
 src/tools/pgindent/typedefs.list                |   40 +
 68 files changed, 9028 insertions(+), 206 deletions(-)
 create mode 100644 src/backend/replication/logical/Makefile
 create mode 100644 src/backend/replication/logical/decode.c
 create mode 100644 src/backend/replication/logical/logical.c
 create mode 100644 src/backend/replication/logical/logicalfuncs.c
 create mode 100644 src/backend/replication/logical/reorderbuffer.c
 create mode 100644 src/backend/replication/logical/snapbuild.c
 create mode 100644 src/include/replication/decode.h
 create mode 100644 src/include/replication/logical.h
 create mode 100644 src/include/replication/logicalfuncs.h
 create mode 100644 src/include/replication/output_plugin.h
 create mode 100644 src/include/replication/reorderbuffer.h
 create mode 100644 src/include/replication/snapbuild.h

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5fd30a..e1e5040 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -63,6 +63,14 @@ static relopt_bool boolRelOpts[] =
 	},
 	{
 		{
+			"treat_as_catalog_table",
+			"Treat table as a catalog table for the purpose of logical replication",
+			RELOPT_KIND_HEAP
+		},
+		false
+	},
+	{
+		{
 			"fastupdate",
 			"Enables \"fast update\" feature for this GIN index",
 			RELOPT_KIND_GIN
@@ -1166,6 +1174,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, security_barrier)},
 		{"check_option", RELOPT_TYPE_STRING,
 		offsetof(StdRdOptions, check_option_offset)},
+		{"treat_as_catalog_table", RELOPT_TYPE_BOOL,
+		 offsetof(StdRdOptions, treat_as_catalog_table)}
 	};
 
 	options = parseRelOptions(reloptions, validate, kind, &numoptions);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b1a5d9f..b09dde2 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -85,12 +85,14 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
-				HeapTuple newtup, bool all_visible_cleared,
-				bool new_all_visible_cleared);
+				HeapTuple newtup, HeapTuple old_idx_tup,
+				bool all_visible_cleared, bool new_all_visible_cleared);
 static void HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
-							 bool *satisfies_hot, bool *satisfies_key,
-							 HeapTuple oldtup, HeapTuple newtup);
+						  Bitmapset *hot_attrs,
+						  Bitmapset *key_attrs, Bitmapset *ckey_attrs,
+						  bool *satisfies_hot, bool *satisfies_key,
+						  bool *satisfies_ckey,
+						  HeapTuple oldtup, HeapTuple newtup);
 static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -108,6 +110,8 @@ static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static bool ConditionalMultiXactIdWait(MultiXactId multi,
 						   MultiXactStatus status, int *remaining,
 						   uint16 infomask);
+static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static HeapTuple ExtractKeyTuple(Relation rel, HeapTuple tup);
 
 
 /*
@@ -342,8 +346,10 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	/*
 	 * Prune and repair fragmentation for the whole page, if possible.
 	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
+	if (IsSystemRelation(scan->rs_rd) || RelationIsDoingTimetravel(scan->rs_rd))
+		heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
+	else
+		heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalDataXmin);
 
 	/*
 	 * We must hold share lock on the buffer content while examining tuple
@@ -1743,10 +1749,16 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		 */
 		if (!skip)
 		{
+			/* setup the redirected t_self for the benefit of timetravel access */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
 			/* If it's visible per the snapshot, we must return it */
 			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
+			/* reset original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
 			if (valid)
 			{
 				ItemPointerSetOffsetNumber(tid, offnum);
@@ -2101,11 +2113,24 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		xl_heap_insert xlrec;
 		xl_heap_header xlhdr;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[3];
+		XLogRecData rdata[4];
 		Page		page = BufferGetPage(buffer);
 		uint8		info = XLOG_HEAP_INSERT;
+		bool		need_tuple_data;
+
+		/*
+		 * For logical replication, we need the tuple even if we're doing a
+		 * full page write, so make sure to log it separately. (XXX We could
+		 * alternatively store a pointer into the FPW).
+		 *
+		 * Also, if this is a catalog, we need to transmit combocids to
+		 * properly decode, so log that as well.
+		 */
+		need_tuple_data = RelationIsLogicallyLogged(relation);
+		if (RelationIsDoingTimetravel(relation))
+			log_heap_new_cid(relation, heaptup);
 
-		xlrec.all_visible_cleared = all_visible_cleared;
+		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.target.node = relation->rd_node;
 		xlrec.target.tid = heaptup->t_self;
 		rdata[0].data = (char *) &xlrec;
@@ -2124,18 +2149,35 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 */
 		rdata[1].data = (char *) &xlhdr;
 		rdata[1].len = SizeOfHeapHeader;
-		rdata[1].buffer = buffer;
+		rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;
 		rdata[1].buffer_std = true;
 		rdata[1].next = &(rdata[2]);
 
 		/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
 		rdata[2].data = (char *) heaptup->t_data + offsetof(HeapTupleHeaderData, t_bits);
 		rdata[2].len = heaptup->t_len - offsetof(HeapTupleHeaderData, t_bits);
-		rdata[2].buffer = buffer;
+		rdata[2].buffer = need_tuple_data ? InvalidBuffer : buffer;
 		rdata[2].buffer_std = true;
 		rdata[2].next = NULL;
 
 		/*
+		 * add record for the buffer without actual content thats removed if
+		 * fpw is done for that buffer
+		 */
+		if (need_tuple_data)
+		{
+			rdata[2].next = &(rdata[3]);
+
+			rdata[3].data = NULL;
+			rdata[3].len = 0;
+			rdata[3].buffer = buffer;
+			rdata[3].buffer_std = true;
+			rdata[3].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+		}
+
+		/*
 		 * If this is the single and first tuple on page, we can reinit the
 		 * page instead of restoring the whole thing.  Set flag, and hide
 		 * buffer references from XLogInsert.
@@ -2144,7 +2186,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 		{
 			info |= XLOG_HEAP_INIT_PAGE;
-			rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
+			rdata[1].buffer = rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
 		}
 
 		recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -2270,6 +2312,8 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 	Page		page;
 	bool		needwal;
 	Size		saveFreeSpace;
+	bool        need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool        need_cids = RelationIsDoingTimetravel(relation);
 
 	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
 	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
@@ -2356,7 +2400,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 		{
 			XLogRecPtr	recptr;
 			xl_heap_multi_insert *xlrec;
-			XLogRecData rdata[2];
+			XLogRecData rdata[3];
 			uint8		info = XLOG_HEAP2_MULTI_INSERT;
 			char	   *tupledata;
 			int			totaldatalen;
@@ -2386,7 +2430,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			/* the rest of the scratch space is used for tuple data */
 			tupledata = scratchptr;
 
-			xlrec->all_visible_cleared = all_visible_cleared;
+			xlrec->flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 			xlrec->node = relation->rd_node;
 			xlrec->blkno = BufferGetBlockNumber(buffer);
 			xlrec->ntuples = nthispage;
@@ -2418,6 +2462,13 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 					   datalen);
 				tuphdr->datalen = datalen;
 				scratchptr += datalen;
+
+				/*
+				 * We don't use heap_multi_insert for catalog tuples yet, but
+				 * better be prepared...
+				 */
+				if (need_cids)
+					log_heap_new_cid(relation, heaptup);
 			}
 			totaldatalen = scratchptr - tupledata;
 			Assert((scratchptr - scratch) < BLCKSZ);
@@ -2429,17 +2480,33 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 
 			rdata[1].data = tupledata;
 			rdata[1].len = totaldatalen;
-			rdata[1].buffer = buffer;
+			rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;
 			rdata[1].buffer_std = true;
 			rdata[1].next = NULL;
 
 			/*
+			 * add record for the buffer without actual content thats removed if
+			 * fpw is done for that buffer
+			 */
+			if (need_tuple_data)
+			{
+				rdata[1].next = &(rdata[2]);
+
+				rdata[2].data = NULL;
+				rdata[2].len = 0;
+				rdata[2].buffer = buffer;
+				rdata[2].buffer_std = true;
+				rdata[2].next = NULL;
+				xlrec->flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+			}
+
+			/*
 			 * If we're going to reinitialize the whole page using the WAL
 			 * record, hide buffer reference from XLogInsert.
 			 */
 			if (init)
 			{
-				rdata[1].buffer = InvalidBuffer;
+				rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
 				info |= XLOG_HEAP_INIT_PAGE;
 			}
 
@@ -2559,6 +2626,9 @@ heap_delete(Relation relation, ItemPointer tid,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
+	bool		need_tuple_data = RelationNeedsWAL(relation) &&
+		RelationIsLogicallyLogged(relation);
+	HeapTuple idx_tuple = NULL; /* primary key of the tuple */
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -2732,6 +2802,15 @@ l1:
 	/* replace cid with a combo cid if necessary */
 	HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
 
+	/*
+	 * Compute primary key tuple before entering the critical section so we
+	 * don't PANIC uppon a memory allocation failure.
+	 */
+	if (need_tuple_data)
+	{
+		idx_tuple = ExtractKeyTuple(relation, &tp);
+	}
+
 	START_CRIT_SECTION();
 
 	/*
@@ -2784,9 +2863,13 @@ l1:
 	{
 		xl_heap_delete xlrec;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[2];
+		XLogRecData rdata[4];
+
+		/* For logical decode we need combocids to properly decode the catalog */
+		if (RelationIsDoingTimetravel(relation))
+			log_heap_new_cid(relation, &tp);
 
-		xlrec.all_visible_cleared = all_visible_cleared;
+		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
 											  tp.t_data->t_infomask2);
 		xlrec.target.node = relation->rd_node;
@@ -2803,6 +2886,34 @@ l1:
 		rdata[1].buffer_std = true;
 		rdata[1].next = NULL;
 
+		/*
+		 * Log primary key of the deleted tuple
+		 */
+		if (need_tuple_data && idx_tuple != NULL)
+		{
+			xl_heap_header xlhdr;
+
+			xlhdr.t_infomask2 = idx_tuple->t_data->t_infomask2;
+			xlhdr.t_infomask = idx_tuple->t_data->t_infomask;
+			xlhdr.t_hoff = idx_tuple->t_data->t_hoff;
+
+			rdata[1].next = &(rdata[2]);
+			rdata[2].data = (char*)&xlhdr;
+			rdata[2].len = SizeOfHeapHeader;
+			rdata[2].buffer = InvalidBuffer;
+			rdata[2].next = NULL;
+
+			rdata[2].next = &(rdata[3]);
+			rdata[3].data = (char *) idx_tuple->t_data
+				+ offsetof(HeapTupleHeaderData, t_bits);
+			rdata[3].len = idx_tuple->t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+			rdata[3].buffer = InvalidBuffer;
+			rdata[3].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+		}
+
 		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE, rdata);
 
 		PageSetLSN(page, recptr);
@@ -2932,9 +3043,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
+	Bitmapset  *ckey_attrs;
 	ItemId		lp;
 	HeapTupleData oldtup;
 	HeapTuple	heaptup;
+	HeapTuple	old_idx_tuple = NULL;
 	Page		page;
 	BlockNumber block;
 	MultiXactStatus mxact_status;
@@ -2950,6 +3063,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		iscombo;
 	bool		satisfies_hot;
 	bool		satisfies_key;
+	bool		satisfies_ckey;
 	bool		use_hot_update = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
@@ -2977,8 +3091,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * Note that we get a copy here, so we need not worry about relcache flush
 	 * happening midway through.
 	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation, false);
-	key_attrs = RelationGetIndexAttrBitmap(relation, true);
+	hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
+	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	ckey_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_CANDIDATE_KEY);
 
 	block = ItemPointerGetBlockNumber(otid);
 	buffer = ReadBuffer(relation, block);
@@ -3036,9 +3152,9 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitiously arrive at the same key values.
 	 */
-	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs,
+	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, ckey_attrs,
 								 &satisfies_hot, &satisfies_key,
-								 &oldtup, newtup);
+								 &satisfies_ckey, &oldtup, newtup);
 	if (satisfies_key)
 	{
 		*lockmode = LockTupleNoKeyExclusive;
@@ -3508,6 +3624,12 @@ l2:
 		PageSetFull(page);
 	}
 
+	/* compute tuple for loggical logging */
+	if (!satisfies_ckey && RelationIsLogicallyLogged(relation))
+	{
+		old_idx_tuple = ExtractKeyTuple(relation, &oldtup);
+	}
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -3583,11 +3705,20 @@ l2:
 	/* XLOG stuff */
 	if (RelationNeedsWAL(relation))
 	{
-		XLogRecPtr	recptr = log_heap_update(relation, buffer,
-											 newbuf, &oldtup, heaptup,
-											 all_visible_cleared,
-											 all_visible_cleared_new);
+		XLogRecPtr	recptr;
+
+		/* For logical decode we need combocids to properly decode the catalog */
+		if (RelationIsDoingTimetravel(relation))
+		{
+			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, heaptup);
+		}
 
+		recptr = log_heap_update(relation, buffer,
+								 newbuf, &oldtup, heaptup,
+								 old_idx_tuple,
+								 all_visible_cleared,
+								 all_visible_cleared_new);
 		if (newbuf != buffer)
 		{
 			PageSetLSN(BufferGetPage(newbuf), recptr);
@@ -3739,18 +3870,23 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
  * modify columns used in the key.
  */
 static void
-HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
+HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs,
+							 Bitmapset *key_attrs, Bitmapset *ckey_attrs,
 							 bool *satisfies_hot, bool *satisfies_key,
+							 bool *satisfies_ckey,
 							 HeapTuple oldtup, HeapTuple newtup)
 {
 	int			next_hot_attnum;
 	int			next_key_attnum;
+	int			next_ckey_attnum;
 	bool		hot_result = true;
 	bool		key_result = true;
-	bool		key_done = false;
+	bool		ckey_result = true;
 	bool		hot_done = false;
 
+	Assert(bms_is_subset(ckey_attrs, key_attrs));
+	Assert(bms_is_subset(key_attrs, hot_attrs));
+
 	next_hot_attnum = bms_first_member(hot_attrs);
 	if (next_hot_attnum == -1)
 		hot_done = true;
@@ -3759,28 +3895,25 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 		next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
 
 	next_key_attnum = bms_first_member(key_attrs);
-	if (next_key_attnum == -1)
-		key_done = true;
-	else
+	if (next_key_attnum != -1)
 		/* Adjust for system attributes */
 		next_key_attnum += FirstLowInvalidHeapAttributeNumber;
 
+	next_ckey_attnum = bms_first_member(ckey_attrs);
+	if (next_ckey_attnum != -1)
+		/* Adjust for system attributes */
+		next_ckey_attnum += FirstLowInvalidHeapAttributeNumber;
+
 	for (;;)
 	{
 		int			check_now;
 		bool		changed;
 
-		/* both bitmapsets are now empty */
-		if (key_done && hot_done)
+		/* bitmapsets are now empty, hot includes others */
+		if (hot_done)
 			break;
 
-		/* XXX there's probably an easier way ... */
-		if (hot_done)
-			check_now = next_key_attnum;
-		if (key_done)
-			check_now = next_hot_attnum;
-		else
-			check_now = Min(next_hot_attnum, next_key_attnum);
+		check_now = next_hot_attnum;
 
 		changed = !heap_tuple_attr_equals(RelationGetDescr(relation),
 										  check_now, oldtup, newtup);
@@ -3790,11 +3923,15 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 				hot_result = false;
 			if (check_now == next_key_attnum)
 				key_result = false;
+			if (check_now == next_ckey_attnum)
+				ckey_result = false;
 		}
 
 		/* if both are false now, we can stop checking */
-		if (!hot_result && !key_result)
+		if (!hot_result && !key_result && !ckey_result)
+		{
 			break;
+		}
 
 		if (check_now == next_hot_attnum)
 		{
@@ -3808,16 +3945,22 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 		if (check_now == next_key_attnum)
 		{
 			next_key_attnum = bms_first_member(key_attrs);
-			if (next_key_attnum == -1)
-				key_done = true;
-			else
+			if (next_key_attnum != -1)
 				/* Adjust for system attributes */
 				next_key_attnum += FirstLowInvalidHeapAttributeNumber;
 		}
+		if (check_now == next_ckey_attnum)
+		{
+			next_ckey_attnum = bms_first_member(ckey_attrs);
+			if (next_ckey_attnum != -1)
+				/* Adjust for system attributes */
+				next_ckey_attnum += FirstLowInvalidHeapAttributeNumber;
+		}
 	}
 
 	*satisfies_hot = hot_result;
 	*satisfies_key = key_result;
+	*satisfies_ckey = ckey_result;
 }
 
 /*
@@ -5839,15 +5982,22 @@ log_heap_visible(RelFileNode rnode, Buffer heap_buffer, Buffer vm_buffer,
 static XLogRecPtr
 log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
+				HeapTuple idx_tuple,
 				bool all_visible_cleared, bool new_all_visible_cleared)
 {
 	xl_heap_update xlrec;
-	xl_heap_header xlhdr;
+	xl_heap_header_len xlhdr;
+	xl_heap_header_len xlhdr_idx;
 	uint8		info;
 	XLogRecPtr	recptr;
-	XLogRecData rdata[4];
+	XLogRecData rdata[7];
 	Page		page = BufferGetPage(newbuf);
 
+	/*
+	 * Just as for XLOG_HEAP_INSERT we need to make sure the tuple
+	 */
+	bool        need_tuple_data = RelationIsLogicallyLogged(reln);
+
 	/* Caller should not call me on a non-WAL-logged relation */
 	Assert(RelationNeedsWAL(reln));
 
@@ -5862,9 +6012,12 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	xlrec.old_infobits_set = compute_infobits(oldtup->t_data->t_infomask,
 											  oldtup->t_data->t_infomask2);
 	xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
-	xlrec.all_visible_cleared = all_visible_cleared;
+	xlrec.flags = 0;
+	if (all_visible_cleared)
+		xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED;
 	xlrec.newtid = newtup->t_self;
-	xlrec.new_all_visible_cleared = new_all_visible_cleared;
+	if (new_all_visible_cleared)
+		xlrec.flags |= XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED;
 
 	rdata[0].data = (char *) &xlrec;
 	rdata[0].len = SizeOfHeapUpdate;
@@ -5877,33 +6030,78 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	rdata[1].buffer_std = true;
 	rdata[1].next = &(rdata[2]);
 
-	xlhdr.t_infomask2 = newtup->t_data->t_infomask2;
-	xlhdr.t_infomask = newtup->t_data->t_infomask;
-	xlhdr.t_hoff = newtup->t_data->t_hoff;
+	xlhdr.header.t_infomask2 = newtup->t_data->t_infomask2;
+	xlhdr.header.t_infomask = newtup->t_data->t_infomask;
+	xlhdr.header.t_hoff = newtup->t_data->t_hoff;
+	xlhdr.t_len = newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
 
-	/*
-	 * As with insert records, we need not store the rdata[2] segment if we
-	 * decide to store the whole buffer instead.
-	 */
 	rdata[2].data = (char *) &xlhdr;
-	rdata[2].len = SizeOfHeapHeader;
-	rdata[2].buffer = newbuf;
+	rdata[2].len = SizeOfHeapHeaderLen;
+	rdata[2].buffer = need_tuple_data ? InvalidBuffer : newbuf;
 	rdata[2].buffer_std = true;
 	rdata[2].next = &(rdata[3]);
 
 	/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
-	rdata[3].data = (char *) newtup->t_data + offsetof(HeapTupleHeaderData, t_bits);
+	rdata[3].data = (char *) newtup->t_data
+		+ offsetof(HeapTupleHeaderData, t_bits);
 	rdata[3].len = newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
-	rdata[3].buffer = newbuf;
+	rdata[3].buffer = need_tuple_data ? InvalidBuffer : newbuf;
 	rdata[3].buffer_std = true;
 	rdata[3].next = NULL;
 
+	/*
+	 * separate storage for the buffer reference of the new page in the
+	 * wal_level >= logical case
+	*/
+	if(need_tuple_data)
+	{
+		rdata[3].next = &(rdata[4]);
+
+		rdata[4].data = NULL,
+		rdata[4].len = 0;
+		rdata[4].buffer = newbuf;
+		rdata[4].buffer_std = true;
+		rdata[4].next = NULL;
+		xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+
+		/* candidate key changed and we have a candidate key */
+		if (idx_tuple)
+		{
+			/* don't really need this, but its more comfy */
+			xlhdr_idx.header.t_infomask2 = idx_tuple->t_data->t_infomask2;
+			xlhdr_idx.header.t_infomask = idx_tuple->t_data->t_infomask;
+			xlhdr_idx.header.t_hoff = idx_tuple->t_data->t_hoff;
+			xlhdr_idx.t_len = idx_tuple->t_len;
+
+			rdata[4].next = &(rdata[5]);
+			rdata[5].data = (char *) &xlhdr_idx;
+			rdata[5].len = SizeOfHeapHeaderLen;
+			rdata[5].buffer = InvalidBuffer;
+			rdata[5].next = &(rdata[6]);
+
+			/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
+			rdata[6].data = (char *) idx_tuple->t_data
+				+ offsetof(HeapTupleHeaderData, t_bits);
+			rdata[6].len = idx_tuple->t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+			rdata[6].buffer = InvalidBuffer;
+			rdata[6].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+		}
+	}
+
 	/* If new tuple is the single and first tuple on page... */
 	if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
 		PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 	{
+		XLogRecData *rcur = &rdata[0];
 		info |= XLOG_HEAP_INIT_PAGE;
-		rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
+		while (rcur != NULL)
+		{
+			rcur->buffer = InvalidBuffer;
+			rcur = rcur->next;
+		}
 	}
 
 	recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -6010,6 +6208,112 @@ log_newpage_buffer(Buffer buffer)
 }
 
 /*
+ * Perform XLogInsert of a XLOG_HEAP2_NEW_CID record
+ *
+ * This is only used in wal_level >= WAL_LEVEL_LOGICAL
+ */
+static XLogRecPtr
+log_heap_new_cid(Relation relation, HeapTuple tup)
+{
+	xl_heap_new_cid xlrec;
+
+	XLogRecPtr	recptr;
+	XLogRecData rdata[1];
+	HeapTupleHeader hdr = tup->t_data;
+
+	Assert(ItemPointerIsValid(&tup->t_self));
+	Assert(tup->t_tableOid != InvalidOid);
+
+	xlrec.top_xid = GetTopTransactionId();
+	xlrec.target.node = relation->rd_node;
+	xlrec.target.tid = tup->t_self;
+
+	/*
+	 * if the tuple got inserted & deleted in the same TX we definitely have a
+	 * combocid, set cmin and cmax.
+	 */
+	if (hdr->t_infomask & HEAP_COMBOCID)
+	{
+		xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
+		xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
+		xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
+	}
+	/* No combocid, so only cmin or cmax can be set by this TX */
+	else
+	{
+		/* tuple inserted */
+		if (hdr->t_infomask & HEAP_XMAX_INVALID)
+		{
+			xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr);
+			xlrec.cmax = InvalidCommandId;
+		}
+		/* tuple from a different tx updated or deleted */
+		else
+		{
+			xlrec.cmin = InvalidCommandId;
+			xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr);
+
+		}
+		xlrec.combocid = InvalidCommandId;
+	}
+
+	rdata[0].data = (char *) &xlrec;
+	rdata[0].len = SizeOfHeapNewCid;
+	rdata[0].buffer = InvalidBuffer;
+	rdata[0].next = NULL;
+
+	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID, rdata);
+
+	return recptr;
+}
+
+static HeapTuple
+ExtractKeyTuple(Relation relation, HeapTuple tp)
+{
+	HeapTuple idx_tuple = NULL;
+	TupleDesc desc = RelationGetDescr(relation);
+	Relation idx_rel;
+	TupleDesc idx_desc;
+	Datum idx_vals[INDEX_MAX_KEYS];
+	bool idx_isnull[INDEX_MAX_KEYS];
+	int natt;
+
+	/* needs to already have been fetched? */
+	if (relation->rd_indexvalid == 0)
+		RelationGetIndexList(relation);
+
+	if (!OidIsValid(relation->rd_primary))
+	{
+		elog(DEBUG1, "Could not find primary key for table with oid %u",
+			 RelationGetRelid(relation));
+	}
+	else
+	{
+		idx_rel = RelationIdGetRelation(relation->rd_primary);
+		idx_desc = RelationGetDescr(idx_rel);
+
+		for (natt = 0; natt < idx_desc->natts; natt++)
+		{
+			int attno = idx_rel->rd_index->indkey.values[natt];
+			if (attno == ObjectIdAttributeNumber)
+			{
+				idx_vals[natt] = HeapTupleGetOid(tp);
+				idx_isnull[natt] = false;
+			}
+			else
+			{
+				idx_vals[natt] =
+					fastgetattr(tp, attno, desc, &idx_isnull[natt]);
+			}
+			Assert(!idx_isnull[natt]);
+		}
+		idx_tuple = heap_form_tuple(idx_desc, idx_vals, idx_isnull);
+		RelationClose(idx_rel);
+	}
+	return idx_tuple;
+}
+
+/*
  * Handles CLEANUP_INFO
  */
 static void
@@ -6370,7 +6674,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6419,7 +6723,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	/* Make sure there is no forward chain link in t_ctid */
@@ -6453,7 +6757,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6524,7 +6828,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 
 	PageSetLSN(page, lsn);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	MarkBufferDirty(buffer);
@@ -6587,7 +6891,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6670,7 +6974,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 
 	PageSetLSN(page, lsn);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	MarkBufferDirty(buffer);
@@ -6709,7 +7013,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 		HeapTupleHeaderData hdr;
 		char		data[MaxHeapTupleSize];
 	}			tbuf;
-	xl_heap_header xlhdr;
+	xl_heap_header_len xlhdr;
 	int			hsize;
 	uint32		newlen;
 	Size		freespace;
@@ -6718,7 +7022,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		BlockNumber block = ItemPointerGetBlockNumber(&xlrec->target.tid);
@@ -6796,7 +7100,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	/*
@@ -6820,7 +7124,7 @@ newt:;
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->new_all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		BlockNumber block = ItemPointerGetBlockNumber(&xlrec->newtid);
@@ -6878,13 +7182,13 @@ newsame:;
 	if (PageGetMaxOffsetNumber(page) + 1 < offnum)
 		elog(PANIC, "heap_update_redo: invalid max offset number");
 
-	hsize = SizeOfHeapUpdate + SizeOfHeapHeader;
+	hsize = SizeOfHeapUpdate + SizeOfHeapHeaderLen;
 
-	newlen = record->xl_len - hsize;
-	Assert(newlen <= MaxHeapTupleSize);
 	memcpy((char *) &xlhdr,
 		   (char *) xlrec + SizeOfHeapUpdate,
-		   SizeOfHeapHeader);
+		   SizeOfHeapHeaderLen);
+	newlen = xlhdr.t_len;
+	Assert(newlen <= MaxHeapTupleSize);
 	htup = &tbuf.hdr;
 	MemSet((char *) htup, 0, sizeof(HeapTupleHeaderData));
 	/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
@@ -6892,9 +7196,9 @@ newsame:;
 		   (char *) xlrec + hsize,
 		   newlen);
 	newlen += offsetof(HeapTupleHeaderData, t_bits);
-	htup->t_infomask2 = xlhdr.t_infomask2;
-	htup->t_infomask = xlhdr.t_infomask;
-	htup->t_hoff = xlhdr.t_hoff;
+	htup->t_infomask2 = xlhdr.header.t_infomask2;
+	htup->t_infomask = xlhdr.header.t_infomask;
+	htup->t_hoff = xlhdr.header.t_hoff;
 
 	HeapTupleHeaderSetXmin(htup, record->xl_xid);
 	HeapTupleHeaderSetCmin(htup, FirstCommandId);
@@ -6906,7 +7210,7 @@ newsame:;
 	if (offnum == InvalidOffsetNumber)
 		elog(PANIC, "heap_update_redo: failed to add tuple");
 
-	if (xlrec->new_all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	freespace = PageGetHeapFreeSpace(page);		/* needed to update FSM below */
@@ -7157,6 +7461,9 @@ heap2_redo(XLogRecPtr lsn, XLogRecord *record)
 		case XLOG_HEAP2_LOCK_UPDATED:
 			heap_xlog_lock_updated(lsn, record);
 			break;
+		case XLOG_HEAP2_NEW_CID:
+			/* nothing to do on a real replay, only during logical decoding */
+			break;
 		default:
 			elog(PANIC, "heap2_redo: unknown op code %u", info);
 	}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 3ec10a0..7fe9f32 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -75,6 +75,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
 	Page		page = BufferGetPage(buffer);
 	Size		minfree;
 
+	Assert(TransactionIdIsValid(OldestXmin));
+
 	/*
 	 * Let's see if we really need pruning.
 	 *
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index b878155..3bac4a5 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -67,7 +67,10 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "access/xlog.h"
+
 #include "catalog/index.h"
+#include "catalog/catalog.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -520,8 +523,15 @@ index_fetch_heap(IndexScanDesc scan)
 		 * Prune page, but only if we weren't already on this page
 		 */
 		if (prev_buf != scan->xs_cbuf)
-			heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
-								RecentGlobalXmin);
+		{
+			if (IsSystemRelation(scan->heapRelation)
+				|| RelationIsDoingTimetravel(scan->heapRelation))
+				heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
+									RecentGlobalXmin);
+			else
+				heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
+									RecentGlobalDataXmin);
+		}
 	}
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index bc8b985..c750fef 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -184,6 +184,15 @@ heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
 						 xlrec->infobits_set);
 		out_target(buf, &(xlrec->target));
 	}
+	else if (info == XLOG_HEAP2_NEW_CID)
+	{
+		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
+
+		appendStringInfo(buf, "new_cid: ");
+		out_target(buf, &(xlrec->target));
+		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
+						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
+	}
 	else
 		appendStringInfo(buf, "UNKNOWN");
 }
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 1b36f9a..e0900e2 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -28,6 +28,7 @@ const struct config_enum_entry wal_level_options[] = {
 	{"minimal", WAL_LEVEL_MINIMAL, false},
 	{"archive", WAL_LEVEL_ARCHIVE, false},
 	{"hot_standby", WAL_LEVEL_HOT_STANDBY, false},
+	{"logical", WAL_LEVEL_LOGICAL, false},
 	{NULL, 0, false}
 };
 
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index e975f8d..d46a50e 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -47,6 +47,7 @@
 #include "access/twophase.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
+#include "access/xlog.h"
 #include "access/xlogutils.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
@@ -1920,7 +1921,8 @@ RecoverPreparedTransactions(void)
 			 * the prepared transaction generated xid assignment records. Test
 			 * here must match one used in AssignTransactionId().
 			 */
-			if (InHotStandby && hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS)
+			if (InHotStandby && (hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS ||
+			                     XLogLogicalInfoActive()))
 				overwriteOK = true;
 
 			/*
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 0591f3f..b937ffe 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -146,6 +146,7 @@ typedef struct TransactionStateData
 	int			prevSecContext; /* previous SecurityRestrictionContext */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
+	bool		guaranteedlyLogged;		/* has xid been logged? */
 	struct TransactionStateData *parent;		/* back link to parent */
 } TransactionStateData;
 
@@ -175,6 +176,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* previous SecurityRestrictionContext */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
+	false,						/* guaranteedlyLogged */
 	NULL						/* link to parent state block */
 };
 
@@ -391,6 +393,21 @@ GetCurrentTransactionIdIfAny(void)
 }
 
 /*
+ *	MarkCurrentTransactionIdLoggedIfAny
+ *
+ * Remember that the current xid - if it is assigned - now has been wal logged.
+ */
+void
+MarkCurrentTransactionIdLoggedIfAny(void)
+{
+	if (TransactionIdIsValid(CurrentTransactionState->transactionId))
+	{
+		CurrentTransactionState->guaranteedlyLogged = true;
+	}
+}
+
+
+/*
  *	GetStableLatestTransactionId
  *
  * Get the transaction's XID if it has one, else read the next-to-be-assigned
@@ -431,6 +448,7 @@ AssignTransactionId(TransactionState s)
 {
 	bool		isSubXact = (s->parent != NULL);
 	ResourceOwner currentOwner;
+	bool log_unknown_top = false;
 
 	/* Assert that caller didn't screw up */
 	Assert(!TransactionIdIsValid(s->transactionId));
@@ -438,7 +456,7 @@ AssignTransactionId(TransactionState s)
 
 	/*
 	 * Ensure parent(s) have XIDs, so that a child always has an XID later
-	 * than its parent.  Musn't recurse here, or we might get a stack overflow
+	 * than its parent.  May not recurse here, or we might get a stack overflow
 	 * if we're at the bottom of a huge stack of subtransactions none of which
 	 * have XIDs yet.
 	 */
@@ -455,6 +473,8 @@ AssignTransactionId(TransactionState s)
 			p = p->parent;
 		}
 
+		Assert(parentOffset);
+
 		/*
 		 * This is technically a recursive call, but the recursion will never
 		 * be more than one layer deep.
@@ -466,6 +486,21 @@ AssignTransactionId(TransactionState s)
 	}
 
 	/*
+	 * When wal_level=logical, guarantee that a subtransaction's xid can only
+	 * be seen in the WAL stream if its toplevel xid has been logged before. If
+	 * necessary we log a xact_assignment record with fewer than
+	 * PGPROC_MAX_CACHED_SUBXIDS. Note that it is fine if guaranteedlyLogged
+	 * isn't set for a transaction even though it appears in a wal record,
+	 * we'll just superfluously log something.
+	 */
+	if (isSubXact && XLogLogicalInfoActive() &&
+		!TopTransactionStateData.guaranteedlyLogged)
+	{
+		log_unknown_top = true;
+	}
+
+
+	/*
 	 * Generate a new Xid and record it in PG_PROC and pg_subtrans.
 	 *
 	 * NB: we must make the subtrans entry BEFORE the Xid appears anywhere in
@@ -519,6 +554,9 @@ AssignTransactionId(TransactionState s)
 	 * top-level transaction that each subxact belongs to. This is correct in
 	 * recovery only because aborted subtransactions are separately WAL
 	 * logged.
+	 *
+	 * This is correct even for the case where several levels above us didn't
+	 * have an xid assigned as we recursed up to them beforehand.
 	 */
 	if (isSubXact && XLogStandbyInfoActive())
 	{
@@ -529,7 +567,8 @@ AssignTransactionId(TransactionState s)
 		 * ensure this test matches similar one in
 		 * RecoverPreparedTransactions()
 		 */
-		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS)
+		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS ||
+		    log_unknown_top)
 		{
 			XLogRecData rdata[2];
 			xl_xact_assignment xlrec;
@@ -548,13 +587,15 @@ AssignTransactionId(TransactionState s)
 			rdata[0].next = &rdata[1];
 
 			rdata[1].data = (char *) unreportedXids;
-			rdata[1].len = PGPROC_MAX_CACHED_SUBXIDS * sizeof(TransactionId);
+			rdata[1].len = nUnreportedXids * sizeof(TransactionId);
 			rdata[1].buffer = InvalidBuffer;
 			rdata[1].next = NULL;
 
 			(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT, rdata);
 
 			nUnreportedXids = 0;
+			/* mark top, not current xact as having been logged */
+			TopTransactionStateData.guaranteedlyLogged = true;
 		}
 	}
 }
@@ -1733,6 +1774,7 @@ StartTransaction(void)
 	 * initialize reported xid accounting
 	 */
 	nUnreportedXids = 0;
+	s->guaranteedlyLogged = false;
 
 	/*
 	 * must initialize resource-management stuff first
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index dc47c47..1f13250 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -41,6 +41,7 @@
 #include "postmaster/startup.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/logical.h"
 #include "storage/barrier.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1191,6 +1192,8 @@ begin:;
 	 */
 	WALInsertSlotRelease();
 
+	MarkCurrentTransactionIdLoggedIfAny();
+
 	END_CRIT_SECTION();
 
 	/*
@@ -6328,6 +6331,13 @@ StartupXLOG(void)
 	XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
 	XLogCtl->ckptXid = checkPoint.nextXid;
 
+
+	/*
+	 * Startup logical state, needs to be setup now so we have proper data
+	 * during restore. XXX
+	 */
+	StartupLogicalReplication(checkPoint.redo);
+
 	/*
 	 * Initialize unlogged LSN. On a clean shutdown, it's restored from the
 	 * control file. On recovery, all unlogged relations are blown away, so
@@ -8308,7 +8318,7 @@ CreateCheckPoint(int flags)
 	 * StartupSUBTRANS hasn't been called yet.
 	 */
 	if (!RecoveryInProgress())
-		TruncateSUBTRANS(GetOldestXmin(true, false));
+		TruncateSUBTRANS(GetOldestXmin(true, true, false, false));
 
 	/* Real work is done, but log and update stats before releasing lock. */
 	LogCheckpointEnd(false);
@@ -8668,7 +8678,7 @@ CreateRestartPoint(int flags)
 	 * this because StartupSUBTRANS hasn't been called yet.
 	 */
 	if (EnableHotStandby)
-		TruncateSUBTRANS(GetOldestXmin(true, false));
+		TruncateSUBTRANS(GetOldestXmin(true, true, false, false));
 
 	/* Real work is done, but log and update before releasing lock. */
 	LogCheckpointEnd(true);
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index c1287a7..0d4cfcb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -106,7 +106,6 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
 	return path;
 }
 
-
 /*
  * IsSystemRelation
  *		True iff the relation is a system catalog relation.
@@ -123,8 +122,17 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
 bool
 IsSystemRelation(Relation relation)
 {
-	return IsSystemNamespace(RelationGetNamespace(relation)) ||
-		IsToastNamespace(RelationGetNamespace(relation));
+	return IsSystemRelationId(RelationGetRelid(relation));
+}
+
+/*
+ * IsSystemRelationId
+ *		True iff the relation is a system catalog relation.
+ */
+bool
+IsSystemRelationId(Oid relid)
+{
+	return relid < FirstNormalObjectId;
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b73ee4f..49ea38b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2174,9 +2174,20 @@ IndexBuildHeapScan(Relation heapRelation,
 	}
 	else
 	{
+		/*
+		 * We can ignore a) pegged xmins b) shared relations if we don't scan
+		 * something acting as a catalog.
+		 */
+		bool include_systables =
+			IsSystemRelation(heapRelation) ||
+			RelationIsDoingTimetravel(heapRelation);
+
 		snapshot = SnapshotAny;
 		/* okay to ignore lazy VACUUMs here */
-		OldestXmin = GetOldestXmin(heapRelation->rd_rel->relisshared, true);
+		OldestXmin = GetOldestXmin(heapRelation->rd_rel->relisshared,
+								   include_systables,
+								   true,
+								   false);
 	}
 
 	scan = heap_beginscan_strat(heapRelation,	/* relation */
@@ -3340,7 +3351,7 @@ reindex_relation(Oid relid, int flags)
 
 	/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
 	if (is_pg_class)
-		(void) RelationGetIndexAttrBitmap(rel, false);
+		(void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL);
 
 	PG_TRY();
 	{
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 575a40f..2acaf54 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -613,6 +613,16 @@ CREATE VIEW pg_stat_replication AS
     WHERE S.usesysid = U.oid AND
             S.pid = W.pid;
 
+CREATE VIEW pg_stat_logical_decoding AS
+    SELECT
+            L.slot_name,
+            L.plugin,
+            L.database,
+            L.active,
+            L.xmin,
+            L.restart_decoding_lsn
+    FROM pg_stat_get_logical_decoding_slots() AS L;
+
 CREATE VIEW pg_stat_database AS
     SELECT
             D.oid AS datid,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9845b0b..7a05cea 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1081,7 +1081,7 @@ acquire_sample_rows(Relation onerel, int elevel,
 	totalblocks = RelationGetNumberOfBlocks(onerel);
 
 	/* Need a cutoff xmin for HeapTupleSatisfiesVacuum */
-	OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true);
+	OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true, true, false);
 
 	/* Prepare for sampling block numbers */
 	BlockSampler_Init(&bs, totalblocks, targrows);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 051b806..240782e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -858,6 +858,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 	 */
 	vacuum_set_xid_limits(freeze_min_age, freeze_table_age,
 						  OldHeap->rd_rel->relisshared,
+						  IsSystemRelation(OldHeap)
+						  || RelationIsDoingTimetravel(OldHeap),
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactFrzLimit);
 
 	/*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d86e9ad..912f7a8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2355,7 +2355,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	 * concurrency.
 	 */
 	modifiedCols = GetModifiedColumns(relinfo, estate);
-	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, true);
+	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+										 INDEX_ATTR_BITMAP_KEY);
 	if (bms_overlap(keyCols, modifiedCols))
 		lockmode = LockTupleExclusive;
 	else
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2f2c6ac..dc647ad 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -381,6 +381,7 @@ void
 vacuum_set_xid_limits(int freeze_min_age,
 					  int freeze_table_age,
 					  bool sharedRel,
+					  bool catalogRel,
 					  TransactionId *oldestXmin,
 					  TransactionId *freezeLimit,
 					  TransactionId *freezeTableLimit,
@@ -399,7 +400,7 @@ vacuum_set_xid_limits(int freeze_min_age,
 	 * working on a particular table at any time, and that each vacuum is
 	 * always an independent transaction.
 	 */
-	*oldestXmin = GetOldestXmin(sharedRel, true);
+	*oldestXmin = GetOldestXmin(sharedRel, catalogRel, true, false);
 
 	Assert(TransactionIdIsNormal(*oldestXmin));
 
@@ -720,7 +721,7 @@ vac_update_datfrozenxid(void)
 	 * committed pg_class entries for new tables; see AddNewRelationTuple().
 	 * So we cannot produce a wrong minimum by starting with this.
 	 */
-	newFrozenXid = GetOldestXmin(true, true);
+	newFrozenXid = GetOldestXmin(true, true, true, false);
 
 	/*
 	 * Similarly, initialize the MultiXact "min" with the value that would be
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index c34aa53..b650eee 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -44,6 +44,7 @@
 #include "access/multixact.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
+#include "catalog/catalog.h"
 #include "catalog/storage.h"
 #include "commands/dbcommands.h"
 #include "commands/vacuum.h"
@@ -202,6 +203,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
 
 	vacuum_set_xid_limits(vacstmt->freeze_min_age, vacstmt->freeze_table_age,
 						  onerel->rd_rel->relisshared,
+						  IsSystemRelation(onerel)
+						  || RelationIsDoingTimetravel(onerel),
 						  &OldestXmin, &FreezeLimit, &freezeTableLimit,
 						  &MultiXactFrzLimit);
 	scan_all = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index a31b01d..8a52cdc 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -818,7 +818,7 @@ PostmasterMain(int argc, char *argv[])
 				(errmsg("WAL archival (archive_mode=on) requires wal_level \"archive\" or \"hot_standby\"")));
 	if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
 		ereport(ERROR,
-				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\" or \"hot_standby\"")));
+				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\", \"logical\" or \"hot_standby\"")));
 
 	/*
 	 * Other one-time internal sanity checks can go here, if they are fast.
diff --git a/src/backend/replication/Makefile b/src/backend/replication/Makefile
index 2dde011..2e13e27 100644
--- a/src/backend/replication/Makefile
+++ b/src/backend/replication/Makefile
@@ -17,6 +17,8 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 OBJS = walsender.o walreceiverfuncs.o walreceiver.o basebackup.o \
 	repl_gram.o syncrep.o
 
+SUBDIRS = logical
+
 include $(top_srcdir)/src/backend/common.mk
 
 # repl_scanner is compiled as part of repl_gram
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
new file mode 100644
index 0000000..310a45c
--- /dev/null
+++ b/src/backend/replication/logical/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for src/backend/replication/logical
+#
+# IDENTIFICATION
+#    src/backend/replication/logical/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/replication/logical
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
+
+OBJS = decode.o logical.o logicalfuncs.o reorderbuffer.o snapbuild.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
new file mode 100644
index 0000000..53043b9
--- /dev/null
+++ b/src/backend/replication/logical/decode.c
@@ -0,0 +1,687 @@
+/*-------------------------------------------------------------------------
+ *
+ * decode.c
+ *		Decodes WAL records fed from xlogreader.h read into an reorderbuffer
+ *		while simultaneously letting snapbuild.c build an appropriate snapshots
+ *		to decode those.
+ *
+ * NOTE:
+ * 		This basically tries to handle all low level xlog stuff for
+ *      reorderbuffer.c and snapbuild.c. There's some minor leakage where a
+ *      specific record's struct is used to pass data along, but that's just
+ *      because those are convenient and uncomplicated to read.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/decode.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+
+#include "access/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog_internal.h"
+#include "access/xlogreader.h"
+
+#include "catalog/pg_control.h"
+
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h"
+
+#include "storage/standby.h"
+
+/* RMGR Handlers */
+static void DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+
+/* individual record(group)'s handlers */
+static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
+						 TransactionId xid, int nsubxacts, TransactionId *sub_xids,
+						 int ninval_msgs, SharedInvalidationMessage *msg);
+static void DecodeAbort(LogicalDecodingContext *ctx, XLogRecPtr lsn,
+			TransactionId xid, TransactionId *sub_xids, int nsubxacts,
+			bool was_commit);
+
+/* common function to decode tuples */
+static void DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tup);
+
+void
+DecodeRecordIntoReorderBuffer(LogicalDecodingContext *ctx,
+							  XLogRecordBuffer *buf)
+{
+	/* cast so we get a warning when new rmgrs are added */
+	switch ((RmgrIds) buf->record.xl_rmid)
+	{
+		case RM_XLOG_ID:
+			DecodeXLogOp(ctx, buf);
+			break;
+
+		case RM_XACT_ID:
+			DecodeXactOp(ctx, buf);
+			break;
+
+		case RM_STANDBY_ID:
+			DecodeStandbyOp(ctx, buf);
+			break;
+
+		case RM_HEAP_ID:
+			DecodeHeapOp(ctx, buf);
+			break;
+
+		case RM_HEAP2_ID:
+			DecodeHeap2Op(ctx, buf);
+			break;
+
+		/* irrelevant for changeset extraction */
+		case RM_SMGR_ID:
+		case RM_CLOG_ID:
+		case RM_DBASE_ID:
+		case RM_TBLSPC_ID:
+		case RM_MULTIXACT_ID:
+		case RM_RELMAP_ID:
+		case RM_BTREE_ID:
+		case RM_HASH_ID:
+		case RM_GIN_ID:
+		case RM_GIST_ID:
+		case RM_SEQ_ID:
+		case RM_SPGIST_ID:
+			break;
+		case RM_NEXT_ID:
+			elog(ERROR, "unexpected NEXT_ID record");
+	}
+}
+
+static void
+DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  	   *builder = ctx->snapshot_builder;
+	ReorderBuffer  *reorder = ctx->reorder;
+	XLogRecord	   *r = &buf->record;
+
+	/* no point in doing anything yet */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT)
+		return;
+
+	switch (r->xl_info & ~XLR_INFO_MASK)
+	{
+		case XLOG_XACT_COMMIT:
+			{
+				xl_xact_commit *xlrec;
+				TransactionId *subxacts = NULL;
+				SharedInvalidationMessage *invals = NULL;
+
+				xlrec = (xl_xact_commit *) buf->record_data;
+
+				subxacts = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+				invals = (SharedInvalidationMessage *) &(subxacts[xlrec->nsubxacts]);
+
+				/* FIXME: skip if wrong db? */
+
+				DecodeCommit(ctx, buf, r->xl_xid, xlrec->nsubxacts, subxacts,
+							 xlrec->nmsgs, invals);
+
+				break;
+			}
+		case XLOG_XACT_COMMIT_PREPARED:
+			{
+				xl_xact_commit_prepared *prec;
+				xl_xact_commit *xlrec;
+				TransactionId *subxacts;
+				SharedInvalidationMessage *invals = NULL;
+
+
+				prec = (xl_xact_commit_prepared *) buf->record_data;
+				xlrec = &prec->crec;
+
+				subxacts = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+				invals = (SharedInvalidationMessage *) &(subxacts[xlrec->nsubxacts]);
+
+				/* FIXME: skip if wrong db? */
+
+				DecodeCommit(ctx, buf, r->xl_xid, xlrec->nsubxacts, subxacts,
+							 xlrec->nmsgs, invals);
+
+				break;
+			}
+		case XLOG_XACT_COMMIT_COMPACT:
+			{
+				xl_xact_commit_compact *xlrec;
+
+#if 0
+				/* FIXME: should we error out? */
+				elog(WARNING, "unexpectedly got compact commit");
+#endif
+				xlrec = (xl_xact_commit_compact *) buf->record_data;
+
+				DecodeCommit(ctx, buf, r->xl_xid,
+							 xlrec->nsubxacts, xlrec->subxacts,
+							 0, NULL);
+				break;
+			}
+		case XLOG_XACT_ABORT:
+			{
+				xl_xact_abort *xlrec;
+				TransactionId *sub_xids;
+
+				xlrec = (xl_xact_abort *) buf->record_data;
+
+				sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+
+				DecodeAbort(ctx, buf->origptr, r->xl_xid,
+							sub_xids, xlrec->nsubxacts, false);
+				break;
+			}
+		case XLOG_XACT_ABORT_PREPARED:
+			{
+				xl_xact_abort_prepared *prec;
+				xl_xact_abort *xlrec;
+				TransactionId *sub_xids;
+
+				prec = (xl_xact_abort_prepared *) buf->record_data;
+				xlrec = &prec->arec;
+
+				sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+
+				/* r->xl_xid is committed in a separate record */
+				DecodeAbort(ctx, buf->origptr, prec->xid,
+							sub_xids, xlrec->nsubxacts, false);
+				break;
+			}
+
+		case XLOG_XACT_ASSIGNMENT:
+			{
+				xl_xact_assignment *xlrec;
+				int			i;
+				TransactionId *sub_xid;
+
+				xlrec =	(xl_xact_assignment *) buf->record_data;
+
+				sub_xid = &xlrec->xsub[0];
+
+				for (i = 0; i < xlrec->nsubxacts; i++)
+				{
+					ReorderBufferAssignChild(reorder, xlrec->xtop,
+											 *(sub_xid++), buf->origptr);
+				}
+				break;
+			}
+		case XLOG_XACT_PREPARE:
+
+			/*
+			 * XXX: we could replay the transaction and prepare it
+			 * as well.
+			 */
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  *builder = ctx->snapshot_builder;
+	XLogRecord	   *r = &buf->record;
+
+	switch (r->xl_info & ~XLR_INFO_MASK)
+	{
+		case XLOG_RUNNING_XACTS:
+			SnapBuildProcessRunningXacts(builder, buf->origptr,
+										 (xl_running_xacts *) buf->record_data);
+			break;
+		case XLOG_STANDBY_LOCK:
+			break;
+		default:
+			elog(ERROR, "unexpected standby record type");
+	}
+}
+static void
+DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  *builder = ctx->snapshot_builder;
+
+	switch (buf->record.xl_info & ~XLR_INFO_MASK)
+	{
+		/* this is also used in END_OF_RECOVERY checkpoints */
+		case XLOG_CHECKPOINT_SHUTDOWN:
+		case XLOG_END_OF_RECOVERY:
+			SnapBuildSerializationPoint(builder, buf->origptr);
+
+			/*
+			 * abort all transactions that still deemed to be in progress, they
+			 * aren't actually in progress anymore. Do not abort prepared
+			 * transactions that have been prepared for commit.
+			 *
+			 * FIXME: implement.
+			 */
+			break;
+		case XLOG_CHECKPOINT_ONLINE:
+			/*
+			 * a RUNNING_XACTS record will have been logged near to this, we
+			 * can restart from there.
+			 */
+			break;
+		case XLOG_NOOP:
+		case XLOG_NEXTOID:
+		case XLOG_SWITCH:
+		case XLOG_BACKUP_END:
+		case XLOG_PARAMETER_CHANGE:
+		case XLOG_RESTORE_POINT:
+		case XLOG_FPW_CHANGE:
+		case XLOG_FPI:
+			break;
+	}
+}
+
+static void
+DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	uint8 info = buf->record.xl_info & XLOG_HEAP_OPMASK;
+	TransactionId xid = buf->record.xl_xid;
+	SnapBuild *builder = ctx->snapshot_builder;
+
+	/* no point in doing anything yet */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT)
+		return;
+
+	switch (info)
+	{
+		case XLOG_HEAP_INSERT:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeInsert(ctx, buf);
+			break;
+
+			/*
+			 * Treat HOT update as normal updates, there is no useful
+			 * information in the fact that we could make it a HOT update
+			 * locally and the WAL layout is compatible.
+			 */
+		case XLOG_HEAP_HOT_UPDATE:
+		case XLOG_HEAP_UPDATE:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeUpdate(ctx, buf);
+			break;
+
+		case XLOG_HEAP_DELETE:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeDelete(ctx, buf);
+			break;
+
+		case XLOG_HEAP_NEWPAGE:
+			/*
+			 * XXX: There doesn't seem to be a usecase for decoding
+			 * HEAP_NEWPAGE's. Its only used in various indexam's and CLUSTER,
+			 * neither of which should be relevant for the logical
+			 * changestream.
+			 */
+			break;
+		case XLOG_HEAP_INPLACE:
+			/* cannot be important for our purposes, not part of transaction */
+			if (!TransactionIdIsValid(xid))
+				break;
+
+			SnapBuildProcessChange(builder, xid, buf->origptr);
+			/* heap_inplace is only done in catalog modifying txns */
+			ReorderBufferXidSetTimetravel(ctx->reorder, xid, buf->origptr);
+			break;
+		case XLOG_HEAP_LOCK:
+			break;
+		default:
+			elog(ERROR, "unexpected info value %u", info);
+			break;
+	}
+}
+
+static void
+DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	uint8 info = buf->record.xl_info & XLOG_HEAP_OPMASK;
+	TransactionId xid = buf->record.xl_xid;
+	SnapBuild *builder = ctx->snapshot_builder;
+
+	/* no point in doing anything yet */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT)
+		return;
+
+	switch (info)
+	{
+		case XLOG_HEAP2_MULTI_INSERT:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeMultiInsert(ctx, buf);
+			break;
+		case XLOG_HEAP2_NEW_CID:
+			{
+				xl_heap_new_cid *xlrec;
+				xlrec = (xl_heap_new_cid *) buf->record_data;
+				SnapBuildProcessNewCid(builder, xid, buf->origptr, xlrec);
+
+				break;
+			}
+			/*
+			 * everything else here is just low level stuff we're not
+			 * interested in
+			 */
+		case XLOG_HEAP2_FREEZE:
+		case XLOG_HEAP2_CLEAN:
+		case XLOG_HEAP2_CLEANUP_INFO:
+		case XLOG_HEAP2_VISIBLE:
+		case XLOG_HEAP2_LOCK_UPDATED:
+			break;
+		default:
+			elog(ERROR, "unexpected info value %u", info);
+	}
+}
+
+static void
+DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, TransactionId xid,
+			 int nsubxacts, TransactionId *sub_xids,
+			 int ninval_msgs, SharedInvalidationMessage *msgs)
+{
+	int			i;
+
+	/* always need the invalidation messages */
+	if (ninval_msgs > 0)
+	{
+		ReorderBufferAddInvalidations(ctx->reorder, xid, buf->origptr,
+									  ninval_msgs, msgs);
+		ReorderBufferXidSetTimetravel(ctx->reorder, xid, buf->origptr);
+	}
+
+	SnapBuildCommitTxn(ctx->snapshot_builder, buf->origptr, xid,
+					   nsubxacts, sub_xids);
+
+	/*
+	 * If we are not interested in anything up to this LSN convert the commit
+	 * into an ABORT to cleanup.
+	 *
+	 * FIXME: this needs to replay invalidations anyway!
+	 */
+	if (SnapBuildXactNeedsSkip(ctx->snapshot_builder, buf->origptr))
+	{
+		DecodeAbort(ctx, buf->origptr, xid,	sub_xids, nsubxacts, true);
+		return;
+	}
+
+	for (i = 0; i < nsubxacts; i++)
+	{
+		ReorderBufferCommitChild(ctx->reorder, xid, *sub_xids,
+								 buf->origptr, buf->endptr);
+		sub_xids++;
+	}
+
+	/* replay actions of all transaction + subtransactions in order */
+	ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr);
+}
+
+static void
+DecodeAbort(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
+			TransactionId *sub_xids, int nsubxacts, bool was_commit)
+{
+	int			i;
+
+	/*
+	 * this is a bit grotty, but if we're "faking" an abort we've already gone
+	 * through
+	 */
+	if (!was_commit)
+		SnapBuildAbortTxn(ctx->snapshot_builder, xid,
+						  nsubxacts, sub_xids);
+
+
+	/* FIXME: process invalidations anyway if was_commit */
+
+	for (i = 0; i < nsubxacts; i++)
+	{
+		ReorderBufferAbort(ctx->reorder, *sub_xids, lsn);
+		sub_xids++;
+	}
+
+	ReorderBufferAbort(ctx->reorder, xid, lsn);
+}
+
+static void
+DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_insert *xlrec;
+	ReorderBufferChange *change;
+
+	xlrec = (xl_heap_insert *) buf->record_data;
+
+	/* XXX: nicer */
+	if (xlrec->target.node.dbNode != ctx->slot->database)
+		return;
+
+	change = ReorderBufferGetChange(ctx->reorder);
+	change->action = REORDER_BUFFER_CHANGE_INSERT;
+	memcpy(&change->relnode, &xlrec->target.node, sizeof(RelFileNode));
+
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
+	{
+		Assert(r->xl_len > (SizeOfHeapInsert + SizeOfHeapHeader));
+
+		change->newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+		DecodeXLogTuple((char *) xlrec + SizeOfHeapInsert,
+						r->xl_len - SizeOfHeapInsert,
+						change->newtuple);
+	}
+
+	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+}
+
+static void
+DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_update *xlrec;
+	xl_heap_header_len *xlhdr;
+	ReorderBufferChange *change;
+	char	   *data;
+
+	xlrec = (xl_heap_update *) buf->record_data;
+	xlhdr = (xl_heap_header_len *) (buf->record_data + SizeOfHeapUpdate);
+
+	/* XXX: nicer */
+	if (xlrec->target.node.dbNode != ctx->slot->database)
+		return;
+
+	change = ReorderBufferGetChange(ctx->reorder);
+	change->action = REORDER_BUFFER_CHANGE_UPDATE;
+	memcpy(&change->relnode, &xlrec->target.node, sizeof(RelFileNode));
+
+	data = (char *) &xlhdr->header;
+
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
+	{
+		Assert(r->xl_len > (SizeOfHeapUpdate + SizeOfHeapHeaderLen));
+
+		change->newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+		DecodeXLogTuple(data,
+						xlhdr->t_len + SizeOfHeapHeader,
+						change->newtuple);
+		/* skip over the rest of the tuple header */
+		data += SizeOfHeapHeader;
+		/* skip over the tuple data */
+		data += xlhdr->t_len;
+	}
+
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY)
+	{
+		xlhdr = (xl_heap_header_len *) data;
+		change->oldtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+		DecodeXLogTuple((char *) &xlhdr->header,
+						xlhdr->t_len + SizeOfHeapHeader,
+						change->oldtuple);
+		data = (char *) &xlhdr->header;
+		data += SizeOfHeapHeader;
+		data += xlhdr->t_len;
+	}
+
+	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+}
+
+static void
+DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_delete *xlrec;
+	ReorderBufferChange *change;
+
+	xlrec = (xl_heap_delete *) buf->record_data;
+
+	/* XXX: nicer */
+	if (xlrec->target.node.dbNode != ctx->slot->database)
+		return;
+
+	change = ReorderBufferGetChange(ctx->reorder);
+	change->action = REORDER_BUFFER_CHANGE_DELETE;
+
+	memcpy(&change->relnode, &xlrec->target.node, sizeof(RelFileNode));
+
+	/* old primary key stored */
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY)
+	{
+		Assert(r->xl_len > (SizeOfHeapDelete + SizeOfHeapHeader));
+
+		change->oldtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+		DecodeXLogTuple((char *) xlrec + SizeOfHeapDelete,
+						r->xl_len - SizeOfHeapDelete,
+						change->oldtuple);
+	}
+	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+}
+
+/*
+ * Decode xl_heap_multi_insert record into multiple changes.
+ */
+static void
+DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_multi_insert *xlrec;
+	int			i;
+	char	   *data;
+	bool		isinit = (r->xl_info & XLOG_HEAP_INIT_PAGE) != 0;
+
+	xlrec = (xl_heap_multi_insert *) buf->record_data;
+
+	/* XXX: nicer */
+	if (xlrec->node.dbNode != ctx->slot->database)
+		return;
+
+	data = buf->record_data + SizeOfHeapMultiInsert;
+
+	/*
+	 * OffsetNumbers (which are not of interest to us) are stored when
+	 * XLOG_HEAP_INIT_PAGE is not set -- skip over them.
+	 */
+	if (!isinit)
+		data += sizeof(OffsetNumber) * xlrec->ntuples;
+
+	for (i = 0; i < xlrec->ntuples; i++)
+	{
+		ReorderBufferChange *change;
+		xl_multi_insert_tuple *xlhdr;
+		int			datalen;
+		ReorderBufferTupleBuf *tuple;
+
+		change = ReorderBufferGetChange(ctx->reorder);
+		change->action = REORDER_BUFFER_CHANGE_INSERT;
+		memcpy(&change->relnode, &xlrec->node, sizeof(RelFileNode));
+
+		/*
+		 * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
+		 * isn't used for catalogs, but better be future proof.
+		 *
+		 * We decode the tuple in pretty much the same way as DecodeXLogTuple,
+		 * but since the layout is slightly different, we can't use it here.
+		 */
+		if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
+		{
+			change->newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+			tuple = change->newtuple;
+
+			/* not a disk based tuple */
+			ItemPointerSetInvalid(&tuple->tuple.t_self);
+
+			xlhdr = (xl_multi_insert_tuple *) SHORTALIGN(data);
+			data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
+			datalen = xlhdr->datalen;
+
+			/* we can only figure this out after reassembling the transactions */
+			tuple->tuple.t_tableOid = InvalidOid;
+			tuple->tuple.t_data = &tuple->header;
+			tuple->tuple.t_len = datalen + offsetof(HeapTupleHeaderData, t_bits);
+
+			memset(&tuple->header, 0, sizeof(HeapTupleHeaderData));
+
+			memcpy((char *) &tuple->header + offsetof(HeapTupleHeaderData, t_bits),
+				   (char *) data,
+				   datalen);
+			data += datalen;
+
+			tuple->header.t_infomask = xlhdr->t_infomask;
+			tuple->header.t_infomask2 = xlhdr->t_infomask2;
+			tuple->header.t_hoff = xlhdr->t_hoff;
+		}
+
+		ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+	}
+}
+
+/*
+ * Read a tuple of size 'len' from 'data' into 'tuple'.
+ */
+static void
+DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tuple)
+{
+	xl_heap_header xlhdr;
+	int			datalen = len - SizeOfHeapHeader;
+
+	Assert(datalen >= 0);
+	Assert(datalen <= MaxHeapTupleSize);
+
+	tuple->tuple.t_len = datalen + offsetof(HeapTupleHeaderData, t_bits);
+
+	/* not a disk based tuple */
+	ItemPointerSetInvalid(&tuple->tuple.t_self);
+
+	/* we can only figure this out after reassembling the transactions */
+	tuple->tuple.t_tableOid = InvalidOid;
+	tuple->tuple.t_data = &tuple->header;
+
+	/* data is not stored aligned, copy to aligned storage */
+	memcpy((char *) &xlhdr,
+		   data,
+		   SizeOfHeapHeader);
+
+	memset(&tuple->header, 0, sizeof(HeapTupleHeaderData));
+
+	memcpy((char *) &tuple->header + offsetof(HeapTupleHeaderData, t_bits),
+		   data + SizeOfHeapHeader,
+		   datalen);
+
+	tuple->header.t_infomask = xlhdr.t_infomask;
+	tuple->header.t_infomask2 = xlhdr.t_infomask2;
+	tuple->header.t_hoff = xlhdr.t_hoff;
+}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
new file mode 100644
index 0000000..656e995
--- /dev/null
+++ b/src/backend/replication/logical/logical.c
@@ -0,0 +1,1046 @@
+/*-------------------------------------------------------------------------
+ *
+ * logical.c
+ *
+ *	   Logical decoding shared memory management
+ *
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/logical.c
+ *
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "access/transam.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+
+#include "replication/logical.h"
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "storage/fd.h"
+
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+/*
+ * logical replication on-disk data structures.
+ */
+typedef struct LogicalDecodingSlotOnDisk
+{
+	uint32		magic;
+	LogicalDecodingSlot slot;
+} LogicalDecodingSlotOnDisk;
+
+#define LOGICAL_MAGIC	0x1051CA1		/* format identifier */
+
+/* Control array for logical decoding */
+LogicalDecodingCtlData *LogicalDecodingCtl = NULL;
+
+/* My slot for logical rep in the shared memory array */
+LogicalDecodingSlot *MyLogicalDecodingSlot = NULL;
+
+/* user settable parameters */
+int			max_logical_slots = 0;		/* the maximum number of logical slots */
+
+static void LogicalSlotKill(int code, Datum arg);
+
+/* persistency functions */
+static void RestoreLogicalSlot(const char *name);
+static void CreateLogicalSlot(LogicalDecodingSlot *slot);
+static void SaveLogicalSlot(LogicalDecodingSlot *slot);
+static void SaveLogicalSlotInternal(LogicalDecodingSlot *slot, const char *path);
+static void DeleteLogicalSlot(LogicalDecodingSlot *slot);
+
+
+/* Report shared-memory space needed by LogicalDecodingShmemInit */
+Size
+LogicalDecodingShmemSize(void)
+{
+	Size		size = 0;
+
+	if (max_logical_slots == 0)
+		return size;
+
+	size = offsetof(LogicalDecodingCtlData, logical_slots);
+	size = add_size(size,
+					mul_size(max_logical_slots, sizeof(LogicalDecodingSlot)));
+
+	return size;
+}
+
+/* Allocate and initialize walsender-related shared memory */
+void
+LogicalDecodingShmemInit(void)
+{
+	bool		found;
+
+	if (max_logical_slots == 0)
+		return;
+
+	LogicalDecodingCtl = (LogicalDecodingCtlData *)
+		ShmemInitStruct("Logical Decoding Ctl", LogicalDecodingShmemSize(),
+						&found);
+
+	if (!found)
+	{
+		int			i;
+
+		/* First time through, so initialize */
+		MemSet(LogicalDecodingCtl, 0, LogicalDecodingShmemSize());
+
+		LogicalDecodingCtl->xmin = InvalidTransactionId;
+
+		for (i = 0; i < max_logical_slots; i++)
+		{
+			LogicalDecodingSlot *slot =
+			&LogicalDecodingCtl->logical_slots[i];
+
+			slot->xmin = InvalidTransactionId;
+			slot->effective_xmin = InvalidTransactionId;
+			SpinLockInit(&slot->mutex);
+		}
+	}
+}
+
+static void
+LogicalSlotKill(int code, Datum arg)
+{
+	/* LOCK? */
+	if (MyLogicalDecodingSlot && MyLogicalDecodingSlot->active)
+	{
+		MyLogicalDecodingSlot->active = false;
+	}
+	MyLogicalDecodingSlot = NULL;
+}
+
+/*
+ * Set the xmin required for catalog timetravel for the specific decoding slot.
+ */
+void
+IncreaseLogicalXminForSlot(XLogRecPtr lsn, TransactionId xmin)
+{
+	Assert(MyLogicalDecodingSlot != NULL);
+
+	SpinLockAcquire(&MyLogicalDecodingSlot->mutex);
+
+	/*
+	 * Only increase if the previous values have been applied, otherwise we
+	 * might never end up updating if the receiver acks too slowly.
+	 */
+	if (MyLogicalDecodingSlot->candidate_lsn == InvalidXLogRecPtr ||
+		(lsn == MyLogicalDecodingSlot->candidate_lsn &&
+		 !TransactionIdIsValid(MyLogicalDecodingSlot->candidate_xmin)))
+	{
+		MyLogicalDecodingSlot->candidate_lsn = lsn;
+		MyLogicalDecodingSlot->candidate_xmin = xmin;
+		elog(DEBUG1, "got new xmin %u at %X/%X", xmin,
+			 (uint32) (lsn >> 32), (uint32) lsn);
+	}
+	SpinLockRelease(&MyLogicalDecodingSlot->mutex);
+}
+
+void
+IncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn)
+{
+	Assert(MyLogicalDecodingSlot != NULL);
+	Assert(restart_lsn != InvalidXLogRecPtr);
+	Assert(current_lsn != InvalidXLogRecPtr);
+
+	SpinLockAcquire(&MyLogicalDecodingSlot->mutex);
+
+	/*
+	 * Only increase if the previous values have been applied, otherwise we
+	 * might never end up updating if the receiver acks too slowly. A missed
+	 * value here will just cause some extra effort after reconnecting.
+	 */
+	if (MyLogicalDecodingSlot->candidate_lsn == InvalidXLogRecPtr ||
+		(current_lsn == MyLogicalDecodingSlot->candidate_lsn &&
+	 MyLogicalDecodingSlot->candidate_restart_decoding == InvalidXLogRecPtr))
+	{
+		MyLogicalDecodingSlot->candidate_lsn = current_lsn;
+		MyLogicalDecodingSlot->candidate_restart_decoding = restart_lsn;
+
+		elog(DEBUG1, "got new restart lsn %X/%X at %X/%X",
+			 (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
+			 (uint32) (current_lsn >> 32), (uint32) current_lsn);
+
+	}
+	SpinLockRelease(&MyLogicalDecodingSlot->mutex);
+}
+
+void
+LogicalConfirmReceivedLocation(XLogRecPtr lsn)
+{
+	Assert(lsn != InvalidXLogRecPtr);
+
+	/* Do an unlocked check for candidate_lsn first. */
+	if (MyLogicalDecodingSlot->candidate_lsn != InvalidXLogRecPtr)
+	{
+		bool		updated_xmin = false;
+		bool		updated_restart = false;
+
+		/* use volatile pointer to prevent code rearrangement */
+		volatile LogicalDecodingSlot *slot = MyLogicalDecodingSlot;
+
+		SpinLockAcquire(&slot->mutex);
+
+		slot->confirmed_flush = lsn;
+
+		/* if were past the location required for bumping xmin, do so */
+		if (slot->candidate_lsn != InvalidXLogRecPtr &&
+			slot->candidate_lsn < lsn)
+		{
+			/*
+			 * We have to write the changed xmin to disk *before* we change
+			 * the in-memory value, otherwise after a crash we wouldn't know
+			 * that some catalog tuples might have been removed already.
+			 *
+			 * Ensure that by first writing to ->xmin and only update
+			 * ->effective_xmin once the new state is fsynced to disk. After a
+			 * crash ->effective_xmin is set to ->xmin.
+			 */
+			if (TransactionIdIsValid(slot->candidate_xmin) &&
+				slot->xmin != slot->candidate_xmin)
+			{
+				slot->xmin = slot->candidate_xmin;
+				updated_xmin = true;
+			}
+
+			if (slot->candidate_restart_decoding != InvalidXLogRecPtr &&
+				slot->restart_decoding != slot->candidate_restart_decoding)
+			{
+				slot->restart_decoding = slot->candidate_restart_decoding;
+				updated_restart = true;
+			}
+
+			slot->candidate_lsn = InvalidXLogRecPtr;
+			slot->candidate_xmin = InvalidTransactionId;
+			slot->candidate_restart_decoding = InvalidXLogRecPtr;
+		}
+
+		SpinLockRelease(&slot->mutex);
+
+		/* first write new xmin to disk, so we know whats up after a crash */
+		if (updated_xmin || updated_restart)
+			/* cast away volatile, thats ok. */
+			SaveLogicalSlot((LogicalDecodingSlot *) slot);
+
+		/*
+		 * now the new xmin is safely on disk, we can let the global value
+		 * advance
+		 */
+		if (updated_xmin)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->effective_xmin = slot->xmin;
+			SpinLockRelease(&slot->mutex);
+
+			ComputeLogicalXmin();
+		}
+	}
+	else
+	{
+		volatile LogicalDecodingSlot *slot = MyLogicalDecodingSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->confirmed_flush = lsn;
+		SpinLockRelease(&slot->mutex);
+	}
+}
+
+/*
+ * Compute the xmin between all of the decoding slots and store it in
+ * WalSndCtlData.
+ */
+void
+ComputeLogicalXmin(void)
+{
+	int			i;
+	TransactionId xmin = InvalidTransactionId;
+	LogicalDecodingSlot *slot;
+
+	Assert(LogicalDecodingCtl);
+
+	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&slot->mutex);
+		if (slot->in_use &&
+			TransactionIdIsValid(slot->effective_xmin) && (
+											   !TransactionIdIsValid(xmin) ||
+						   TransactionIdPrecedes(slot->effective_xmin, xmin))
+			)
+		{
+			xmin = slot->effective_xmin;
+		}
+		SpinLockRelease(&slot->mutex);
+	}
+	LogicalDecodingCtl->xmin = xmin;
+	LWLockRelease(ProcArrayLock);
+
+	elog(DEBUG1, "computed new global xmin for decoding: %u", xmin);
+}
+
+/*
+ * Make sure the current settings & environment are capable of doing logical
+ * replication.
+ */
+void
+CheckLogicalReplicationRequirements(void)
+{
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* XXX invent class 51 for code 51028? */
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("logical replication requires wal_level=logical")));
+
+	if (MyDatabaseId == InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("logical replication requires to be connected to a database")));
+
+	if (max_logical_slots == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 (errmsg("logical replication requires needs max_logical_slots > 0"))));
+}
+
+/*
+ * Search for a free slot, mark it as used and acquire a valid xmin horizon
+ * value.
+ */
+void
+LogicalDecodingAcquireFreeSlot(const char *name, const char *plugin)
+{
+	LogicalDecodingSlot *slot;
+	bool		name_in_use;
+	int			i;
+
+	Assert(!MyLogicalDecodingSlot);
+
+	CheckLogicalReplicationRequirements();
+
+	LWLockAcquire(LogicalReplicationCtlLock, LW_EXCLUSIVE);
+
+	/* First, make sure the requested name is not in use. */
+
+	name_in_use = false;
+	for (i = 0; i < max_logical_slots && !name_in_use; i++)
+	{
+		LogicalDecodingSlot *s = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&s->mutex);
+		if (s->in_use && strcmp(name, NameStr(s->name)) == 0)
+			name_in_use = true;
+		SpinLockRelease(&s->mutex);
+	}
+
+	if (name_in_use)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+			  errmsg("There already is a logical slot named \"%s\"", name)));
+
+	/* Find the first available (not in_use (=> not active)) slot. */
+
+	slot = NULL;
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		LogicalDecodingSlot *s = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&s->mutex);
+		if (!s->in_use)
+		{
+			Assert(!s->active);
+			/* NOT releasing the lock yet */
+			slot = s;
+			break;
+		}
+		SpinLockRelease(&s->mutex);
+	}
+
+	LWLockRelease(LogicalReplicationCtlLock);
+
+	if (!slot)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("couldn't find free logical slot. free one or increase max_logical_slots")));
+
+	MyLogicalDecodingSlot = slot;
+
+	/* Lets start with enough information if we can */
+	if (!RecoveryInProgress())
+		slot->restart_decoding = LogStandbySnapshot();
+	else
+		slot->restart_decoding = GetRedoRecPtr();
+
+	slot->in_use = true;
+	slot->active = true;
+	slot->database = MyDatabaseId;
+	/* XXX: do we want to use truncate identifier instead? */
+	strncpy(NameStr(slot->plugin), plugin, NAMEDATALEN);
+	NameStr(slot->plugin)[NAMEDATALEN - 1] = '\0';
+	strncpy(NameStr(slot->name), name, NAMEDATALEN);
+	NameStr(slot->name)[NAMEDATALEN - 1] = '\0';
+
+	/* Arrange to clean up at exit/error */
+	on_shmem_exit(LogicalSlotKill, 0);
+
+	/* release slot so it can be examined by others */
+	SpinLockRelease(&slot->mutex);
+
+	/* XXX: verify that the specified plugin is valid */
+
+	/*
+	 * Acquire the current global xmin value and directly set the logical xmin
+	 * before releasing the lock if necessary. We do this so wal decoding is
+	 * guaranteed to have all catalog rows produced by xacts with an xid >
+	 * walsnd->xmin available.
+	 *
+	 * We can't use ComputeLogicalXmin here as that acquires ProcArrayLock
+	 * separately which would open a short window for the global xmin to
+	 * advance above walsnd->xmin.
+	 */
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	slot->effective_xmin = GetOldestXmin(true, true, true, true);
+	slot->xmin = slot->effective_xmin;
+
+	if (!TransactionIdIsValid(LogicalDecodingCtl->xmin) ||
+		NormalTransactionIdPrecedes(slot->effective_xmin, LogicalDecodingCtl->xmin))
+		LogicalDecodingCtl->xmin = slot->effective_xmin;
+	LWLockRelease(ProcArrayLock);
+
+	Assert(slot->effective_xmin <= GetOldestXmin(true, true, true, false));
+
+	LWLockAcquire(LogicalReplicationCtlLock, LW_EXCLUSIVE);
+	CreateLogicalSlot(slot);
+	LWLockRelease(LogicalReplicationCtlLock);
+}
+
+/*
+ * Find an previously initiated slot and mark it as used again.
+ */
+void
+LogicalDecodingReAcquireSlot(const char *name)
+{
+	LogicalDecodingSlot *slot;
+	int			i;
+
+	CheckLogicalReplicationRequirements();
+
+	Assert(!MyLogicalDecodingSlot);
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&slot->mutex);
+		if (slot->in_use && strcmp(name, NameStr(slot->name)) == 0)
+		{
+			MyLogicalDecodingSlot = slot;
+			/* NOT releasing the lock yet */
+			break;
+		}
+		SpinLockRelease(&slot->mutex);
+	}
+
+	if (!MyLogicalDecodingSlot)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("couldn't find logical slot \"%s\"", name)));
+
+	slot = MyLogicalDecodingSlot;
+
+	if (slot->active)
+	{
+		SpinLockRelease(&slot->mutex);
+		MyLogicalDecodingSlot = NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("slot already active")));
+	}
+
+	slot->active = true;
+	/* now that we've marked it as active, we release our lock */
+	SpinLockRelease(&slot->mutex);
+
+	/* Don't let the user switch the database... */
+	if (slot->database != MyDatabaseId)
+	{
+		SpinLockAcquire(&slot->mutex);
+		slot->active = false;
+		SpinLockRelease(&slot->mutex);
+
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 (errmsg("START_LOGICAL_REPLICATION needs to be run in the same database as INIT_LOGICAL_REPLICATION"))));
+	}
+
+	/* Arrange to clean up at exit */
+	on_shmem_exit(LogicalSlotKill, 0);
+
+	SaveLogicalSlot(slot);
+}
+
+/*
+  * Temporarily remove a logical decoding slot, this or another backend can
+  * reacquire it later.
+ */
+void
+LogicalDecodingReleaseSlot(void)
+{
+	LogicalDecodingSlot *slot;
+
+	CheckLogicalReplicationRequirements();
+
+	slot = MyLogicalDecodingSlot;
+
+	Assert(slot != NULL && slot->active);
+
+	SpinLockAcquire(&slot->mutex);
+	slot->active = false;
+	SpinLockRelease(&slot->mutex);
+
+	MyLogicalDecodingSlot = NULL;
+
+	SaveLogicalSlot(slot);
+
+	cancel_shmem_exit(LogicalSlotKill, 0);
+}
+
+/*
+ * Permanently remove a logical decoding slot.
+ */
+void
+LogicalDecodingFreeSlot(const char *name)
+{
+	LogicalDecodingSlot *slot = NULL;
+	int			i;
+
+	CheckLogicalReplicationRequirements();
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&slot->mutex);
+		if (slot->in_use && strcmp(name, NameStr(slot->name)) == 0)
+		{
+			/* NOT releasing the lock yet */
+			break;
+		}
+		SpinLockRelease(&slot->mutex);
+		slot = NULL;
+	}
+
+	if (!slot)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("couldn't find logical slot \"%s\"", name)));
+
+	if (slot->active)
+	{
+		SpinLockRelease(&slot->mutex);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("cannot free active logical slot \"%s\"", name)));
+	}
+
+	/*
+	 * Mark it as as active, so nobody can claim this slot while we are
+	 * working on it. We don't want to hold the spinlock while doing stuff
+	 * like fsyncing the state file to disk.
+	 */
+	slot->active = true;
+
+	SpinLockRelease(&slot->mutex);
+
+	/*
+	 * Start critical section, we can't to be interrupted while on-disk/memory
+	 * state aren't coherent.
+	 */
+	START_CRIT_SECTION();
+
+	DeleteLogicalSlot(slot);
+
+	/* ok, everything gone, after a crash we now wouldn't restore this slot */
+	SpinLockAcquire(&slot->mutex);
+	slot->active = false;
+	slot->in_use = false;
+	SpinLockRelease(&slot->mutex);
+
+	END_CRIT_SECTION();
+
+	/* slot is dead and doesn't nail the xmin anymore */
+	ComputeLogicalXmin();
+}
+
+/*
+ * Load replication state from disk into memory at server startup.
+ */
+void
+StartupLogicalReplication(XLogRecPtr checkPointRedo)
+{
+	DIR		   *logical_dir;
+	struct dirent *logical_de;
+
+	ereport(DEBUG1,
+			(errmsg("starting up logical decoding from %X/%X",
+					(uint32) (checkPointRedo >> 32), (uint32) checkPointRedo)));
+
+	/* restore all slots */
+	logical_dir = AllocateDir("pg_llog");
+	while ((logical_de = ReadDir(logical_dir, "pg_llog")) != NULL)
+	{
+		if (strcmp(logical_de->d_name, ".") == 0 ||
+			strcmp(logical_de->d_name, "..") == 0)
+			continue;
+
+		/* one of our own directories */
+		if (strcmp(logical_de->d_name, "snapshots") == 0)
+			continue;
+
+		/* we crashed while a slot was being setup or deleted, clean up */
+		if (strcmp(logical_de->d_name, "new") == 0 ||
+			strcmp(logical_de->d_name, "old") == 0)
+		{
+			char		path[MAXPGPATH];
+
+			sprintf(path, "pg_llog/%s", logical_de->d_name);
+
+			if (!rmtree(path, true))
+			{
+				FreeDir(logical_dir);
+				ereport(PANIC,
+						(errcode_for_file_access(),
+						 errmsg("could not remove directory \"%s\": %m",
+								path)));
+			}
+			continue;
+		}
+
+		RestoreLogicalSlot(logical_de->d_name);
+	}
+	FreeDir(logical_dir);
+
+	if (max_logical_slots <= 0)
+		return;
+
+	/* Now that we have recovered all the data, compute logical xmin */
+	ComputeLogicalXmin();
+
+	ReorderBufferStartup();
+}
+
+/* ----
+ * Manipulation of ondisk state of logical slots
+ * ----
+ */
+static void
+CreateLogicalSlot(LogicalDecodingSlot *slot)
+{
+	char		tmppath[MAXPGPATH];
+	char		path[MAXPGPATH];
+
+	START_CRIT_SECTION();
+
+	sprintf(tmppath, "pg_llog/new");
+	sprintf(path, "pg_llog/%s", NameStr(slot->name));
+
+	if (mkdir(tmppath, S_IRWXU) < 0)
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not create directory \"%s\": %m",
+						tmppath)));
+
+	fsync_fname(tmppath, true);
+
+	SaveLogicalSlotInternal(slot, tmppath);
+
+	if (rename(tmppath, path) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename logical checkpoint from \"%s\" to \"%s\": %m",
+						tmppath, path)));
+	}
+
+	fsync_fname(path, true);
+
+	END_CRIT_SECTION();
+}
+
+static void
+SaveLogicalSlot(LogicalDecodingSlot *slot)
+{
+	char		path[MAXPGPATH];
+
+	sprintf(path, "pg_llog/%s", NameStr(slot->name));
+	SaveLogicalSlotInternal(slot, path);
+}
+
+/*
+ * Shared functionality between saving and creating a logical slot.
+ */
+static void
+SaveLogicalSlotInternal(LogicalDecodingSlot *slot, const char *dir)
+{
+	char		tmppath[MAXPGPATH];
+	char		path[MAXPGPATH];
+	int			fd;
+	LogicalDecodingSlotOnDisk cp;
+
+	/* silence valgrind :( */
+	memset(&cp, 0, sizeof(LogicalDecodingSlotOnDisk));
+
+	sprintf(tmppath, "%s/state.tmp", dir);
+	sprintf(path, "%s/state", dir);
+
+	START_CRIT_SECTION();
+
+	fd = OpenTransientFile(tmppath,
+						   O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
+						   S_IRUSR | S_IWUSR);
+	if (fd < 0)
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not create logical checkpoint file \"%s\": %m",
+						tmppath)));
+
+	cp.magic = LOGICAL_MAGIC;
+
+	SpinLockAcquire(&slot->mutex);
+
+	cp.slot.xmin = slot->xmin;
+	cp.slot.effective_xmin = slot->effective_xmin;
+
+	strcpy(NameStr(cp.slot.name), NameStr(slot->name));
+	strcpy(NameStr(cp.slot.plugin), NameStr(slot->plugin));
+
+	cp.slot.database = slot->database;
+	cp.slot.confirmed_flush = slot->confirmed_flush;
+	cp.slot.restart_decoding = slot->restart_decoding;
+	cp.slot.candidate_lsn = InvalidXLogRecPtr;
+	cp.slot.candidate_xmin = InvalidTransactionId;
+	cp.slot.candidate_restart_decoding = InvalidXLogRecPtr;
+	cp.slot.in_use = slot->in_use;
+	cp.slot.active = false;
+
+	SpinLockRelease(&slot->mutex);
+
+	if ((write(fd, &cp, sizeof(cp))) != sizeof(cp))
+	{
+		CloseTransientFile(fd);
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not write logical checkpoint file \"%s\": %m",
+						tmppath)));
+	}
+
+	/* fsync the file */
+	if (pg_fsync(fd) != 0)
+	{
+		CloseTransientFile(fd);
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not fsync logical checkpoint \"%s\": %m",
+						tmppath)));
+	}
+
+	CloseTransientFile(fd);
+
+	/* rename to permanent file, fsync file and directory */
+	if (rename(tmppath, path) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename logical checkpoint from \"%s\" to \"%s\": %m",
+						tmppath, path)));
+	}
+
+	fsync_fname((char *) dir, true);
+	fsync_fname(path, false);
+
+	END_CRIT_SECTION();
+}
+
+
+static void
+DeleteLogicalSlot(LogicalDecodingSlot *slot)
+{
+	char		path[MAXPGPATH];
+	char		tmppath[] = "pg_llog/old";
+
+	START_CRIT_SECTION();
+
+	sprintf(path, "pg_llog/%s", NameStr(slot->name));
+
+	if (rename(path, tmppath) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename logical checkpoint from \"%s\" to \"%s\": %m",
+						path, tmppath)));
+	}
+
+	/* make sure no partial state is visible after a crash */
+	fsync_fname(tmppath, true);
+	fsync_fname("pg_llog", true);
+
+	if (!rmtree(tmppath, true))
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not remove directory \"%s\": %m",
+						tmppath)));
+	}
+
+	END_CRIT_SECTION();
+}
+
+/*
+ * Load a single ondisk slot into memory.
+ */
+static void
+RestoreLogicalSlot(const char *name)
+{
+	LogicalDecodingSlotOnDisk cp;
+	int			i;
+	char		path[MAXPGPATH];
+	int			fd;
+	bool		restored = false;
+	int			readBytes;
+
+	START_CRIT_SECTION();
+
+	/* delete temp file if it exists */
+	sprintf(path, "pg_llog/%s/state.tmp", name);
+	if (unlink(path) < 0 && errno != ENOENT)
+		ereport(PANIC, (errmsg("failed while unlinking %s", path)));
+
+	sprintf(path, "pg_llog/%s/state", name);
+
+	elog(DEBUG1, "restoring logical slot from %s", path);
+
+	fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+
+	/*
+	 * We do not need to handle this as we are rename()ing the directory into
+	 * place only after we fsync()ed the state file.
+	 */
+	if (fd < 0)
+		ereport(PANIC, (errmsg("could not open state file %s", path)));
+
+	readBytes = read(fd, &cp, sizeof(cp));
+	if (readBytes != sizeof(cp))
+	{
+		int			saved_errno = errno;
+
+		CloseTransientFile(fd);
+		errno = saved_errno;
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not read logical checkpoint file \"%s\": %m, read %d of %zu",
+						path, readBytes, sizeof(cp))));
+	}
+
+	CloseTransientFile(fd);
+
+	if (cp.magic != LOGICAL_MAGIC)
+		ereport(PANIC, (errmsg("Logical checkpoint has wrong magic %u instead of %u",
+							   cp.magic, LOGICAL_MAGIC)));
+
+	/* nothing can be active yet, don't lock anything */
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		LogicalDecodingSlot *slot;
+
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		if (slot->in_use)
+			continue;
+
+		slot->xmin = cp.slot.xmin;
+		/* XXX: after a crash, always use xmin, not effective_xmin */
+		slot->effective_xmin = cp.slot.xmin;
+		strcpy(NameStr(slot->name), NameStr(cp.slot.name));
+		strcpy(NameStr(slot->plugin), NameStr(cp.slot.plugin));
+		slot->database = cp.slot.database;
+		slot->restart_decoding = cp.slot.restart_decoding;
+		slot->confirmed_flush = cp.slot.confirmed_flush;
+		slot->candidate_lsn = InvalidXLogRecPtr;
+		slot->candidate_xmin = InvalidTransactionId;
+		slot->candidate_restart_decoding = InvalidXLogRecPtr;
+		slot->in_use = true;
+		slot->active = false;
+		restored = true;
+
+		/*
+		 * FIXME: Do some validation here.
+		 */
+		break;
+	}
+
+	if (!restored)
+		ereport(PANIC,
+				(errmsg("too many logical slots active before shutdown, increase max_logical_slots and try again")));
+
+	END_CRIT_SECTION();
+}
+
+
+static void
+LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin)
+{
+	/* lookup symbols in the shared libarary */
+
+	/* optional */
+	callbacks->init_cb = (LogicalDecodeInitCB)
+		load_external_function(plugin, "pg_decode_init", false, NULL);
+
+	/* required */
+	callbacks->begin_cb = (LogicalDecodeBeginCB)
+		load_external_function(plugin, "pg_decode_begin_txn", true, NULL);
+
+	/* required */
+	callbacks->change_cb = (LogicalDecodeChangeCB)
+		load_external_function(plugin, "pg_decode_change", true, NULL);
+
+	/* required */
+	callbacks->commit_cb = (LogicalDecodeCommitCB)
+		load_external_function(plugin, "pg_decode_commit_txn", true, NULL);
+
+	/* optional */
+	callbacks->cleanup_cb = (LogicalDecodeCleanupCB)
+		load_external_function(plugin, "pg_decode_clean", false, NULL);
+}
+
+/*
+ * Context management functions to make coordination between the different
+ * logical decoding pieces.
+ */
+
+/*
+ * Callbacks for ReorderBuffer which add in some more information and then call
+ * output_plugin.h plugins.
+ */
+static void
+begin_txn_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+
+	ctx->callbacks.begin_cb(ctx, txn);
+}
+
+static void
+commit_txn_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn, XLogRecPtr commit_lsn)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+
+	ctx->callbacks.commit_cb(ctx, txn, commit_lsn);
+}
+
+static void
+change_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+			   Relation relation, ReorderBufferChange *change)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+
+	ctx->callbacks.change_cb(ctx, txn, relation, change);
+}
+
+LogicalDecodingContext *
+CreateLogicalDecodingContext(LogicalDecodingSlot *slot,
+							 bool is_init,
+							 XLogRecPtr	start_lsn,
+							 List *output_plugin_options,
+							 XLogPageReadCB read_page,
+						 LogicalOutputPluginWriterPrepareWrite prepare_write,
+							 LogicalOutputPluginWriterWrite do_write)
+{
+	MemoryContext context;
+	MemoryContext old_context;
+	TransactionId xmin_horizon;
+	LogicalDecodingContext *ctx;
+
+	context = AllocSetContextCreate(TopMemoryContext,
+									"ReorderBuffer",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	old_context = MemoryContextSwitchTo(context);
+	ctx = palloc0(sizeof(LogicalDecodingContext));
+
+
+	/* load output plugins first, so we detect a wrong output plugin early */
+	LoadOutputPlugin(&ctx->callbacks, NameStr(slot->plugin));
+
+	if (is_init && start_lsn != InvalidXLogRecPtr)
+		elog(ERROR, "cannot initially start at a specified lsn");
+
+	if (is_init)
+		xmin_horizon = slot->xmin;
+	else
+		xmin_horizon = InvalidTransactionId;
+
+	ctx->slot = slot;
+
+	ctx->reader = XLogReaderAllocate(read_page, ctx);
+	ctx->reader->private_data = ctx;
+
+	ctx->reorder = ReorderBufferAllocate();
+	ctx->snapshot_builder =
+		AllocateSnapshotBuilder(ctx->reorder, xmin_horizon, start_lsn);
+
+	ctx->reorder->private_data = ctx;
+
+	ctx->reorder->begin = begin_txn_wrapper;
+	ctx->reorder->apply_change = change_wrapper;
+	ctx->reorder->commit = commit_txn_wrapper;
+
+	ctx->out = makeStringInfo();
+	ctx->prepare_write = prepare_write;
+	ctx->write = do_write;
+
+	ctx->output_plugin_options = output_plugin_options;
+
+	if (is_init)
+		ctx->stop_after_consistent = true;
+	else
+		ctx->stop_after_consistent = false;
+
+	/* call output plugin initialization callback */
+	if (ctx->callbacks.init_cb != NULL)
+		ctx->callbacks.init_cb(ctx, is_init);
+
+	MemoryContextSwitchTo(old_context);
+
+	return ctx;
+}
+
+void
+FreeLogicalDecodingContext(LogicalDecodingContext *ctx)
+{
+	if (ctx->callbacks.cleanup_cb != NULL)
+		ctx->callbacks.cleanup_cb(ctx);
+}
+
+
+/* has the initial snapshot found a consistent state? */
+bool
+LogicalDecodingContextReady(LogicalDecodingContext *ctx)
+{
+	return SnapBuildCurrentState(ctx->snapshot_builder) == SNAPBUILD_CONSISTENT;
+}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
new file mode 100644
index 0000000..9837a95
--- /dev/null
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -0,0 +1,361 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalfuncs.c
+ *
+ *	   Support functions for using xlog decoding
+ *
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logicalfuncs.c
+ *
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "fmgr.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "storage/fd.h"
+
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalfuncs.h"
+
+Datum		init_logical_replication(PG_FUNCTION_ARGS);
+Datum		stop_logical_replication(PG_FUNCTION_ARGS);
+Datum		pg_stat_get_logical_decoding_slots(PG_FUNCTION_ARGS);
+
+/* FIXME: duplicate code with pg_xlogdump, similar to walsender.c */
+static void
+XLogRead(char *buf, XLogRecPtr startptr, Size count)
+{
+	char	   *p;
+	XLogRecPtr	recptr;
+	Size		nbytes;
+
+	static int	sendFile = -1;
+	static XLogSegNo sendSegNo = 0;
+	static uint32 sendOff = 0;
+
+	p = buf;
+	recptr = startptr;
+	nbytes = count;
+
+	while (nbytes > 0)
+	{
+		uint32		startoff;
+		int			segbytes;
+		int			readbytes;
+
+		startoff = recptr % XLogSegSize;
+
+		if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo))
+		{
+			char		path[MAXPGPATH];
+
+			/* Switch to another logfile segment */
+			if (sendFile >= 0)
+				close(sendFile);
+
+			XLByteToSeg(recptr, sendSegNo);
+
+			XLogFilePath(path, ThisTimeLineID, sendSegNo);
+
+			sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+
+			if (sendFile < 0)
+			{
+				if (errno == ENOENT)
+					ereport(ERROR,
+							(errcode_for_file_access(),
+							 errmsg("requested WAL segment %s has already been removed",
+									path)));
+				else
+					ereport(ERROR,
+							(errcode_for_file_access(),
+							 errmsg("could not open file \"%s\": %m",
+									path)));
+			}
+			sendOff = 0;
+		}
+
+		/* Need to seek in the file? */
+		if (sendOff != startoff)
+		{
+			if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
+			{
+				char		path[MAXPGPATH];
+
+				XLogFilePath(path, ThisTimeLineID, sendSegNo);
+
+				ereport(ERROR,
+						(errcode_for_file_access(),
+				  errmsg("could not seek in log segment %s to offset %u: %m",
+						 path, startoff)));
+			}
+			sendOff = startoff;
+		}
+
+		/* How many bytes are within this segment? */
+		if (nbytes > (XLogSegSize - startoff))
+			segbytes = XLogSegSize - startoff;
+		else
+			segbytes = nbytes;
+
+		readbytes = read(sendFile, p, segbytes);
+		if (readbytes <= 0)
+		{
+			char		path[MAXPGPATH];
+
+			XLogFilePath(path, ThisTimeLineID, sendSegNo);
+
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not read from log segment %s, offset %u, length %lu: %m",
+							path, sendOff, (unsigned long) segbytes)));
+		}
+
+		/* Update state for read */
+		recptr += readbytes;
+
+		sendOff += readbytes;
+		nbytes -= readbytes;
+		p += readbytes;
+	}
+}
+
+int
+logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
+	int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI)
+{
+	XLogRecPtr	flushptr,
+				loc;
+	int			count;
+
+	loc = targetPagePtr + reqLen;
+	while (1)
+	{
+		flushptr = GetFlushRecPtr();
+		if (loc <= flushptr)
+			break;
+		pg_usleep(1000L);
+	}
+
+	/* more than one block available */
+	if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
+		count = XLOG_BLCKSZ;
+	/* not enough data there */
+	else if (targetPagePtr + reqLen > flushptr)
+		return -1;
+	/* part of the page available */
+	else
+		count = flushptr - targetPagePtr;
+
+	/* FIXME: more sensible/efficient implementation */
+	XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ);
+
+	return count;
+}
+
+static void
+DummyWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	elog(ERROR, "init_logical_replication shouldn't be writing anything");
+}
+
+Datum
+init_logical_replication(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	Name		plugin = PG_GETARG_NAME(1);
+
+	char		xpos[MAXFNAMELEN];
+
+	TupleDesc	tupdesc;
+	HeapTuple	tuple;
+	Datum		result;
+	Datum		values[2];
+	bool		nulls[2];
+	LogicalDecodingContext *ctx = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Acquire a logical replication slot */
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingAcquireFreeSlot(NameStr(*name), NameStr(*plugin));
+
+	/* make sure we don't end up with an unreleased slot */
+	PG_TRY();
+	{
+		XLogRecPtr	startptr;
+
+		/*
+		 * Use the same initial_snapshot_reader, but with our own read_page
+		 * callback that does not depend on walsender.
+		 */
+		ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, true,
+										   InvalidXLogRecPtr, NIL,
+										   logical_read_local_xlog_page,
+										   DummyWrite, DummyWrite);
+
+		/* setup from where to read xlog */
+		startptr = ctx->slot->restart_decoding;
+
+		/* Wait for a consistent starting point */
+		for (;;)
+		{
+			XLogRecord *record;
+			XLogRecordBuffer buf;
+			char	   *err = NULL;
+
+			/* the read_page callback waits for new WAL */
+			record = XLogReadRecord(ctx->reader, startptr, &err);
+			if (err)
+				elog(ERROR, "%s", err);
+
+			Assert(record);
+
+			startptr = InvalidXLogRecPtr;
+
+			buf.origptr = ctx->reader->ReadRecPtr;
+			buf.record = *record;
+			buf.record_data = XLogRecGetData(record);
+			DecodeRecordIntoReorderBuffer(ctx, &buf);
+
+			/* only continue till we found a consistent spot */
+			if (LogicalDecodingContextReady(ctx))
+				break;
+		}
+
+		/* Extract the values we want */
+		MyLogicalDecodingSlot->confirmed_flush = ctx->reader->EndRecPtr;
+		snprintf(xpos, sizeof(xpos), "%X/%X",
+				 (uint32) (MyLogicalDecodingSlot->confirmed_flush >> 32),
+				 (uint32) MyLogicalDecodingSlot->confirmed_flush);
+	}
+	PG_CATCH();
+	{
+		LogicalDecodingReleaseSlot();
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	values[0] = CStringGetTextDatum(NameStr(MyLogicalDecodingSlot->name));
+	values[1] = CStringGetTextDatum(xpos);
+
+	memset(nulls, 0, sizeof(nulls));
+
+	tuple = heap_form_tuple(tupdesc, values, nulls);
+	result = HeapTupleGetDatum(tuple);
+
+	LogicalDecodingReleaseSlot();
+
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+stop_logical_replication(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingFreeSlot(NameStr(*name));
+
+	PG_RETURN_INT32(0);
+}
+
+/*
+ * Return one row for each logical replication slot currently in use.
+ */
+
+Datum
+pg_stat_get_logical_decoding_slots(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_LOGICAL_DECODING_SLOTS_COLS 6
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	int			i;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not " \
+						"allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		LogicalDecodingSlot *slot = &LogicalDecodingCtl->logical_slots[i];
+		Datum		values[PG_STAT_GET_LOGICAL_DECODING_SLOTS_COLS];
+		bool		nulls[PG_STAT_GET_LOGICAL_DECODING_SLOTS_COLS];
+		char		location[MAXFNAMELEN];
+		const char *slot_name;
+		const char *plugin;
+		TransactionId xmin;
+		XLogRecPtr	last_req;
+		bool		active;
+		Oid			database;
+
+		SpinLockAcquire(&slot->mutex);
+		if (!slot->in_use)
+		{
+			SpinLockRelease(&slot->mutex);
+			continue;
+		}
+		else
+		{
+			xmin = slot->xmin;
+			active = slot->active;
+			database = slot->database;
+			last_req = slot->restart_decoding;
+			slot_name = pstrdup(NameStr(slot->name));
+			plugin = pstrdup(NameStr(slot->plugin));
+		}
+		SpinLockRelease(&slot->mutex);
+
+		memset(nulls, 0, sizeof(nulls));
+
+		snprintf(location, sizeof(location), "%X/%X",
+				 (uint32) (last_req >> 32), (uint32) last_req);
+
+		values[0] = CStringGetTextDatum(slot_name);
+		values[1] = CStringGetTextDatum(plugin);
+		values[2] = database;
+		values[3] = BoolGetDatum(active);
+		values[4] = TransactionIdGetDatum(xmin);
+		values[5] = CStringGetTextDatum(location);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
new file mode 100644
index 0000000..b6df411
--- /dev/null
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -0,0 +1,2548 @@
+/*-------------------------------------------------------------------------
+ *
+ * reorderbuffer.c
+ *
+ * PostgreSQL logical replay buffer management
+ *
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/reorderbuffer.c
+ *
+ * NOTES
+ *	  This module gets handed individual pieces of transactions in the order
+ *	  they are written to the WAL and is responsible to reassemble them into
+ *	  toplevel transaction sized pieces. When a transaction is completely
+ *	  reassembled - signalled by reading the transaction commit record - it
+ *	  will then call the output plugin (c.f. ReorderBufferCommit()) with the
+ *	  individual changes. The output plugins rely on snapshots built by
+ *	  snapbuild.c which hands them to us.
+ *
+ *	  Transactions and subtransactions/savepoints in postgres are not
+ *	  immediately linked to each other from outside the performing
+ *	  backend. Only at commit/abort (or special xact_assignment records) they
+ *	  are linked together. Which means that we will have to splice together a
+ *	  toplevel transaction from its subtransactions. To do that efficiently we
+ *	  build a binary heap indexed by the smallest current lsn of the individual
+ *	  subtransactions' changestreams. As the individual streams are inherently
+ *	  ordered by LSN - since that is where we build them from - the transaction
+ *	  can easily be reassembled by always using the subtransaction with the
+ *	  smallest current LSN from the heap.
+ *
+ *	  In order to cope with large transactions - which can be several times as
+ *	  big as the available memory - this module supports spooling the contents
+ *	  of a large transactions to disk. When the transaction is replayed the
+ *	  contents of individual (sub-)transactions will be read from disk in
+ *	  chunks.
+ *
+ *	  This module also has to deal with reassembling toast records from the
+ *	  individual chunks stored in WAL. When a new (or initial) version of a
+ *	  tuple is stored in WAL it will always be preceded by the toast chunks
+ *	  emitted for the columns stored out of line. Within a single toplevel
+ *	  transaction there will be no other data carrying records between a row's
+ *	  toast chunks and the row data itself. See ReorderBufferToast* for
+ *	  details.
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "access/transam.h"
+#include "access/xact.h"
+
+#include "catalog/catalog.h"
+
+#include "common/relpath.h"
+
+#include "lib/binaryheap.h"
+
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h" /* just for SnapBuildSnapDecRefcount */
+#include "replication/logical.h"
+
+#include "storage/bufmgr.h"
+#include "storage/fd.h"
+#include "storage/sinval.h"
+
+#include "utils/builtins.h"
+#include "utils/combocid.h"
+#include "utils/memdebug.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+#include "utils/relfilenodemap.h"
+#include "utils/resowner.h"
+#include "utils/tqual.h"
+
+/*
+ * For efficiency and simplicity reasons we want to keep Snapshots, CommandIds
+ * and ComboCids in the same list with the user visible INSERT/UPDATE/DELETE
+ * changes. We don't want to leak those internal values to external users
+ * though (they would just use switch()...default:) because that would make it
+ * harder to add to new user visible values.
+ *
+ * This needs to be synchronized with ReorderBufferChangeType! Adjust the
+ * StaticAssertExpr's in ReorderBufferAllocate if you add anything!
+ */
+typedef enum
+{
+	REORDER_BUFFER_CHANGE_INTERNAL_INSERT,
+	REORDER_BUFFER_CHANGE_INTERNAL_UPDATE,
+	REORDER_BUFFER_CHANGE_INTERNAL_DELETE,
+	REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
+	REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
+	REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID
+} ReorderBufferChangeTypeInternal;
+
+/* entry for a hash table we use to map from xid to our transaction state */
+typedef struct ReorderBufferTXNByIdEnt
+{
+	TransactionId xid;
+	ReorderBufferTXN *txn;
+} ReorderBufferTXNByIdEnt;
+
+/* data structures for (relfilenode, ctid) => (cmin, cmax) mapping */
+typedef struct ReorderBufferTupleCidKey
+{
+	RelFileNode relnode;
+	ItemPointerData tid;
+} ReorderBufferTupleCidKey;
+
+typedef struct ReorderBufferTupleCidEnt
+{
+	ReorderBufferTupleCidKey key;
+	CommandId	cmin;
+	CommandId	cmax;
+	CommandId	combocid;		/* just for debugging */
+} ReorderBufferTupleCidEnt;
+
+/* k-way in-order change iteration support structures */
+typedef struct ReorderBufferIterTXNEntry
+{
+	XLogRecPtr	lsn;
+	ReorderBufferChange *change;
+	ReorderBufferTXN *txn;
+	int			fd;
+	XLogSegNo	segno;
+} ReorderBufferIterTXNEntry;
+
+typedef struct ReorderBufferIterTXNState
+{
+	binaryheap *heap;
+	Size		nr_txns;
+	dlist_head	old_change;
+	ReorderBufferIterTXNEntry entries[FLEXIBLE_ARRAY_MEMBER];
+} ReorderBufferIterTXNState;
+
+/* toast datastructures */
+typedef struct ReorderBufferToastEnt
+{
+	Oid			chunk_id;		/* toast_table.chunk_id */
+	int32		last_chunk_seq; /* toast_table.chunk_seq of the last chunk we
+								 * have seen */
+	Size		num_chunks;		/* number of chunks we've already seen */
+	Size		size;			/* combined size of chunks seen */
+	dlist_head	chunks;			/* linked list of chunks */
+	struct varlena *reconstructed;		/* reconstructed varlena now pointed
+										 * to in main tup */
+} ReorderBufferToastEnt;
+
+
+/* number of changes kept in memory, per transaction */
+const Size	max_memtries = 4096;
+
+/* Size of the slab caches used for frequently allocated objects */
+const Size	max_cached_changes = 4096 * 2;
+const Size	max_cached_tuplebufs = 1024;		/* ~8MB */
+const Size	max_cached_transactions = 512;
+
+
+/* ---------------------------------------
+ * primary reorderbuffer support routines
+ * ---------------------------------------
+ */
+static ReorderBufferTXN *ReorderBufferGetTXN(ReorderBuffer *rb);
+static void ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static ReorderBufferTXN *ReorderBufferTXNByXid(ReorderBuffer *rb,
+					  TransactionId xid, bool create, bool *is_new,
+					  XLogRecPtr lsn, bool create_as_top);
+
+static void AssertTXNLsnOrder(ReorderBuffer *rb);
+
+/* ---------------------------------------
+ * support functions for lsn-order iterating over the ->changes of a
+ * transaction and its subtransactions
+ *
+ * used for iteration over the k-way heap merge of a transaction and its
+ * subtransactions
+ * ---------------------------------------
+ */
+static ReorderBufferIterTXNState *ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static ReorderBufferChange *
+			ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state);
+static void ReorderBufferIterTXNFinish(ReorderBuffer *rb,
+						   ReorderBufferIterTXNState *state);
+static void ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn);
+
+/*
+ * ---------------------------------------
+ * Disk serialization support functions
+ * ---------------------------------------
+ */
+static void ReorderBufferCheckSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							 int fd, ReorderBufferChange *change);
+static Size ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							int *fd, XLogSegNo *segno);
+static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						   char *change);
+static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn);
+
+static void ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap);
+static Snapshot ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
+					  ReorderBufferTXN *txn, CommandId cid);
+
+/* ---------------------------------------
+ * toast reassembly support
+ * ---------------------------------------
+ */
+/* Size of an EXTERNAL datum that contains a standard TOAST pointer */
+#define TOAST_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(struct varatt_external))
+
+/* Size of an indirect datum that contains a standard TOAST pointer */
+#define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(struct varatt_indirect))
+
+static void ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						  Relation relation, ReorderBufferChange *change);
+static void ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							  Relation relation, ReorderBufferChange *change);
+
+
+/*
+ * Allocate a new ReorderBuffer
+ */
+ReorderBuffer *
+ReorderBufferAllocate(void)
+{
+	ReorderBuffer *buffer;
+	HASHCTL		hash_ctl;
+	MemoryContext new_ctx;
+
+	StaticAssertExpr((int) REORDER_BUFFER_CHANGE_INTERNAL_INSERT == (int) REORDER_BUFFER_CHANGE_INSERT, "out of sync enums");
+	StaticAssertExpr((int) REORDER_BUFFER_CHANGE_INTERNAL_UPDATE == (int) REORDER_BUFFER_CHANGE_UPDATE, "out of sync enums");
+	StaticAssertExpr((int) REORDER_BUFFER_CHANGE_INTERNAL_DELETE == (int) REORDER_BUFFER_CHANGE_DELETE, "out of sync enums");
+
+	new_ctx = AllocSetContextCreate(TopMemoryContext,
+									"ReorderBuffer",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+
+	buffer = (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+
+	buffer->context = new_ctx;
+
+	hash_ctl.keysize = sizeof(TransactionId);
+	hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
+	hash_ctl.hash = tag_hash;
+	hash_ctl.hcxt = buffer->context;
+
+	buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
+								 HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+	buffer->by_txn_last_xid = InvalidTransactionId;
+	buffer->by_txn_last_txn = NULL;
+
+	buffer->nr_cached_transactions = 0;
+	buffer->nr_cached_changes = 0;
+	buffer->nr_cached_tuplebufs = 0;
+
+	buffer->outbuf = NULL;
+	buffer->outbufsize = 0;
+
+	buffer->current_restart_decoding_lsn = InvalidXLogRecPtr;
+
+	dlist_init(&buffer->toplevel_by_lsn);
+	dlist_init(&buffer->cached_transactions);
+	dlist_init(&buffer->cached_changes);
+	slist_init(&buffer->cached_tuplebufs);
+
+	return buffer;
+}
+
+/*
+ * Free a ReorderBuffer
+ */
+void
+ReorderBufferFree(ReorderBuffer *rb)
+{
+	MemoryContext context = rb->context;
+
+	/*
+	 * We free separately allocated data by entirely scrapping oure personal
+	 * memory context.
+	 */
+	MemoryContextDelete(context);
+}
+
+/*
+ * Get a unused, possibly preallocated, ReorderBufferTXN.
+ */
+static ReorderBufferTXN *
+ReorderBufferGetTXN(ReorderBuffer *rb)
+{
+	ReorderBufferTXN *txn;
+
+	if (rb->nr_cached_transactions > 0)
+	{
+		rb->nr_cached_transactions--;
+		txn = (ReorderBufferTXN *)
+			dlist_container(ReorderBufferTXN, node,
+							dlist_pop_head_node(&rb->cached_transactions));
+	}
+	else
+	{
+		txn = (ReorderBufferTXN *)
+			MemoryContextAlloc(rb->context, sizeof(ReorderBufferTXN));
+	}
+
+	memset(txn, 0, sizeof(ReorderBufferTXN));
+
+	dlist_init(&txn->changes);
+	dlist_init(&txn->tuplecids);
+	dlist_init(&txn->subtxns);
+
+	return txn;
+}
+
+/*
+ * Free an ReorderBufferTXN. Deallocation might be delayed for efficiency
+ * purposes.
+ */
+void
+ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	/* clean the lookup cache if we were cached (quite likely) */
+	if (rb->by_txn_last_xid == txn->xid)
+	{
+		rb->by_txn_last_xid = InvalidTransactionId;
+		rb->by_txn_last_txn = NULL;
+	}
+
+	if (txn->tuplecid_hash != NULL)
+	{
+		hash_destroy(txn->tuplecid_hash);
+		txn->tuplecid_hash = NULL;
+	}
+
+	if (txn->invalidations)
+	{
+		pfree(txn->invalidations);
+		txn->invalidations = NULL;
+	}
+
+	if (rb->nr_cached_transactions < max_cached_transactions)
+	{
+		rb->nr_cached_transactions++;
+		dlist_push_head(&rb->cached_transactions, &txn->node);
+		VALGRIND_MAKE_MEM_UNDEFINED(txn, sizeof(ReorderBufferTXN));
+		VALGRIND_MAKE_MEM_DEFINED(&txn->node, sizeof(txn->node));
+	}
+	else
+	{
+		pfree(txn);
+	}
+}
+
+/*
+ * Get a unused, possibly preallocated, ReorderBufferChange.
+ */
+ReorderBufferChange *
+ReorderBufferGetChange(ReorderBuffer *rb)
+{
+	ReorderBufferChange *change;
+
+	if (rb->nr_cached_changes)
+	{
+		rb->nr_cached_changes--;
+		change = (ReorderBufferChange *)
+			dlist_container(ReorderBufferChange, node,
+							dlist_pop_head_node(&rb->cached_changes));
+	}
+	else
+	{
+		change = (ReorderBufferChange *)
+			MemoryContextAlloc(rb->context, sizeof(ReorderBufferChange));
+	}
+
+	memset(change, 0, sizeof(ReorderBufferChange));
+	return change;
+}
+
+/*
+ * Free an ReorderBufferChange. Deallocation might be delayed for efficiency
+ * purposes.
+ */
+void
+ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
+{
+	switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+	{
+		case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+		case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+		case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+			if (change->newtuple)
+			{
+				ReorderBufferReturnTupleBuf(rb, change->newtuple);
+				change->newtuple = NULL;
+			}
+
+			if (change->oldtuple)
+			{
+				ReorderBufferReturnTupleBuf(rb, change->oldtuple);
+				change->oldtuple = NULL;
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+			if (change->snapshot)
+			{
+				ReorderBufferFreeSnap(rb, change->snapshot);
+				change->snapshot = NULL;
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+			break;
+	}
+
+	if (rb->nr_cached_changes < max_cached_changes)
+	{
+		rb->nr_cached_changes++;
+		dlist_push_head(&rb->cached_changes, &change->node);
+		VALGRIND_MAKE_MEM_UNDEFINED(change, sizeof(ReorderBufferChange));
+		VALGRIND_MAKE_MEM_DEFINED(&change->node, sizeof(change->node));
+	}
+	else
+	{
+		pfree(change);
+	}
+}
+
+
+/*
+ * Get a unused, possibly preallocated, ReorderBufferTupleBuf
+ */
+ReorderBufferTupleBuf *
+ReorderBufferGetTupleBuf(ReorderBuffer *rb)
+{
+	ReorderBufferTupleBuf *tuple;
+
+	if (rb->nr_cached_tuplebufs)
+	{
+		rb->nr_cached_tuplebufs--;
+		tuple = slist_container(ReorderBufferTupleBuf, node,
+								slist_pop_head_node(&rb->cached_tuplebufs));
+#ifdef USE_ASSERT_CHECKING
+		memset(tuple, 0xdeadbeef, sizeof(ReorderBufferTupleBuf));
+#endif
+	}
+	else
+	{
+		tuple = (ReorderBufferTupleBuf *)
+			MemoryContextAlloc(rb->context, sizeof(ReorderBufferTupleBuf));
+	}
+
+	return tuple;
+}
+
+/*
+ * Free an ReorderBufferTupleBuf. Deallocation might be delayed for efficiency
+ * purposes.
+ */
+void
+ReorderBufferReturnTupleBuf(ReorderBuffer *rb, ReorderBufferTupleBuf *tuple)
+{
+	if (rb->nr_cached_tuplebufs < max_cached_tuplebufs)
+	{
+		rb->nr_cached_tuplebufs++;
+		slist_push_head(&rb->cached_tuplebufs, &tuple->node);
+		VALGRIND_MAKE_MEM_UNDEFINED(tuple, sizeof(ReorderBufferTupleBuf));
+		VALGRIND_MAKE_MEM_DEFINED(&tuple->node, sizeof(tuple->node));
+	}
+	else
+	{
+		pfree(tuple);
+	}
+}
+
+/*
+ * Return the ReorderBufferTXN from the given buffer, specified by Xid.
+ * If create is true, and a transaction doesn't already exist, create it
+ * (with the given LSN, and as top transaction if that's specified);
+ * when this happens, is_new is set to true.
+ */
+static ReorderBufferTXN *
+ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create,
+					  bool *is_new, XLogRecPtr lsn, bool create_as_top)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferTXNByIdEnt *ent;
+	bool		found;
+
+	Assert(TransactionIdIsValid(xid));
+	Assert(!create || lsn != InvalidXLogRecPtr);
+
+	/*
+	 * Check the one-entry lookup cache first
+	 */
+	if (TransactionIdIsValid(rb->by_txn_last_xid) &&
+		rb->by_txn_last_xid == xid)
+	{
+		txn = rb->by_txn_last_txn;
+
+		if (txn != NULL)
+		{
+			/* found it, and it's valid */
+			if (is_new)
+				*is_new = false;
+			return txn;
+		}
+
+		/*
+		 * cached as non-existant, and asked not to create? Then nothing else
+		 * to do.
+		 */
+		if (!create)
+			return NULL;
+		/* otherwise fall through to create it */
+	}
+
+	/*
+	 * If the cache wasn't hit or it yielded an "does-not-exist" and we want
+	 * to create an entry.
+	 */
+
+	/* search the lookup table */
+	ent = (ReorderBufferTXNByIdEnt *)
+		hash_search(rb->by_txn,
+					(void *) &xid,
+					create ? HASH_ENTER : HASH_FIND,
+					&found);
+	if (found)
+		txn = ent->txn;
+	else if (create)
+	{
+		/* initialize the new entry, if creation was requested */
+		Assert(ent != NULL);
+
+		ent->txn = ReorderBufferGetTXN(rb);
+		ent->txn->xid = xid;
+		txn = ent->txn;
+		txn->first_lsn = lsn;
+		txn->restart_decoding_lsn = rb->current_restart_decoding_lsn;
+
+		if (create_as_top)
+		{
+			dlist_push_tail(&rb->toplevel_by_lsn, &txn->node);
+			AssertTXNLsnOrder(rb);
+		}
+	}
+	else
+		txn = NULL;				/* not found and not asked to create */
+
+	/* update cache */
+	rb->by_txn_last_xid = xid;
+	rb->by_txn_last_txn = txn;
+
+	if (is_new)
+		*is_new = !found;
+
+	Assert(!create || !!txn);
+	return txn;
+}
+
+/*
+ * Queue a change into a transaction so it can be replayed upon commit.
+ */
+void
+ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
+					   ReorderBufferChange *change)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	change->lsn = lsn;
+	Assert(InvalidXLogRecPtr != lsn);
+	dlist_push_tail(&txn->changes, &change->node);
+	txn->nentries++;
+	txn->nentries_mem++;
+
+	ReorderBufferCheckSerializeTXN(rb, txn);
+}
+
+static void
+AssertTXNLsnOrder(ReorderBuffer *rb)
+{
+#ifdef USE_ASSERT_CHECKING
+	dlist_iter	iter;
+	XLogRecPtr	prev_first_lsn = InvalidXLogRecPtr;
+
+	dlist_foreach(iter, &rb->toplevel_by_lsn)
+	{
+		ReorderBufferTXN *cur_txn;
+
+		cur_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
+		Assert(cur_txn->first_lsn != InvalidXLogRecPtr);
+
+		if (cur_txn->end_lsn != InvalidXLogRecPtr)
+			Assert(cur_txn->first_lsn <= cur_txn->end_lsn);
+
+		if (prev_first_lsn != InvalidXLogRecPtr)
+			Assert(prev_first_lsn < cur_txn->first_lsn);
+
+		Assert(!cur_txn->is_known_as_subxact);
+		prev_first_lsn = cur_txn->first_lsn;
+	}
+#endif
+}
+
+ReorderBufferTXN *
+ReorderBufferGetOldestTXN(ReorderBuffer *rb)
+{
+	ReorderBufferTXN *txn;
+
+	if (dlist_is_empty(&rb->toplevel_by_lsn))
+		return NULL;
+
+	AssertTXNLsnOrder(rb);
+
+	txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn);
+
+	Assert(!txn->is_known_as_subxact);
+	Assert(txn->first_lsn != InvalidXLogRecPtr);
+	return txn;
+}
+
+void
+ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr)
+{
+	rb->current_restart_decoding_lsn = ptr;
+}
+
+void
+ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid,
+						 TransactionId subxid, XLogRecPtr lsn)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferTXN *subtxn;
+	bool		new_top;
+	bool		new_sub;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true);
+	subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false);
+
+	if (new_sub)
+	{
+		/*
+		 * we assign subtransactions to top level transaction even if we don't
+		 * have data for it yet, assignment records frequently reference xids
+		 * that have not yet produced any records. Knowing those aren't top
+		 * level xids allows us to make processing cheaper in some places.
+		 */
+		dlist_push_tail(&txn->subtxns, &subtxn->node);
+		txn->nsubtxns++;
+	}
+	else if (!subtxn->is_known_as_subxact)
+	{
+		subtxn->is_known_as_subxact = true;
+		Assert(subtxn->nsubtxns == 0);
+
+		/* remove from lsn order list of top-level transactions */
+		dlist_delete(&subtxn->node);
+
+		/* add to toplevel transaction */
+		dlist_push_tail(&txn->subtxns, &subtxn->node);
+		txn->nsubtxns++;
+	}
+	else if (new_top)
+	{
+		elog(ERROR, "existing subxact assigned to unknown toplevel xact");
+	}
+}
+
+/*
+ * Associate a subtransaction with its toplevel transaction at commit
+ * time. There may be no further changes added after this.
+ */
+void
+ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid,
+						 TransactionId subxid, XLogRecPtr commit_lsn,
+						 XLogRecPtr end_lsn)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferTXN *subtxn;
+
+	subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL,
+								   InvalidXLogRecPtr, false);
+
+	/*
+	 * No need to do anything if that subtxn didn't contain any changes
+	 */
+	if (!subtxn)
+		return;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, commit_lsn, true);
+
+	if (txn == NULL)
+		elog(ERROR, "subxact logged without previous toplevel record");
+
+	subtxn->final_lsn = commit_lsn;
+	subtxn->end_lsn = end_lsn;
+
+	if (!subtxn->is_known_as_subxact)
+	{
+		subtxn->is_known_as_subxact = true;
+		Assert(subtxn->nsubtxns == 0);
+
+		/* remove from lsn order list of top-level transactions */
+		dlist_delete(&subtxn->node);
+
+		/* add to subtransaction list */
+		dlist_push_tail(&txn->subtxns, &subtxn->node);
+		txn->nsubtxns++;
+	}
+}
+
+
+/*
+ * Support for efficiently iterating over a transaction's and its
+ * subtransactions' changes.
+ *
+ * We do by doing a k-way merge between transactions/subtransactions. For that
+ * we model the current heads of the different transactions as a binary heap so
+ * we easily know which (sub-)transaction has the change with the smallest lsn
+ * next.
+ *
+ * We assume the changes in individual transactions are already sorted by LSN.
+ */
+
+/*
+ * Binary heap comparison function.
+ */
+static int
+ReorderBufferIterCompare(Datum a, Datum b, void *arg)
+{
+	ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg;
+	XLogRecPtr	pos_a = state->entries[DatumGetInt32(a)].lsn;
+	XLogRecPtr	pos_b = state->entries[DatumGetInt32(b)].lsn;
+
+	if (pos_a < pos_b)
+		return 1;
+	else if (pos_a == pos_b)
+		return 0;
+	return -1;
+}
+
+/*
+ * Allocate & initialize an iterator which iterates in lsn order over a
+ * transaction and all its subtransactions.
+ */
+static ReorderBufferIterTXNState *
+ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	Size		nr_txns = 0;
+	ReorderBufferIterTXNState *state;
+	dlist_iter	cur_txn_i;
+	int32		off;
+
+	/*
+	 * Calculate the size of our heap: one element for every transaction that
+	 * contains changes.  (Besides the transactions already in the reorder
+	 * buffer, we count the one we were directly passed.)
+	 */
+	if (txn->nentries > 0)
+		nr_txns++;
+
+	dlist_foreach(cur_txn_i, &txn->subtxns)
+	{
+		ReorderBufferTXN *cur_txn;
+
+		cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
+
+		if (cur_txn->nentries > 0)
+			nr_txns++;
+	}
+
+	/*
+	 * XXX: Add fastpath for the rather common nr_txns=1 case, no need to
+	 * allocate/build a heap in that case.
+	 */
+
+	/* allocate iteration state */
+	state = (ReorderBufferIterTXNState *)
+		MemoryContextAllocZero(rb->context,
+							   sizeof(ReorderBufferIterTXNState) +
+							   sizeof(ReorderBufferIterTXNEntry) * nr_txns);
+
+	state->nr_txns = nr_txns;
+	dlist_init(&state->old_change);
+
+	for (off = 0; off < state->nr_txns; off++)
+	{
+		state->entries[off].fd = -1;
+		state->entries[off].segno = 0;
+	}
+
+	/* allocate heap */
+	state->heap = binaryheap_allocate(state->nr_txns, ReorderBufferIterCompare,
+									  state);
+
+	/*
+	 * Now insert items into the binary heap, unordered.  (We will run a heap
+	 * assembly step at the end; this is more efficient.)
+	 */
+
+	off = 0;
+
+	/* add toplevel transaction if it contains changes */
+	if (txn->nentries > 0)
+	{
+		ReorderBufferChange *cur_change;
+
+		if (txn->nentries != txn->nentries_mem)
+			ReorderBufferRestoreChanges(rb, txn, &state->entries[off].fd,
+										&state->entries[off].segno);
+
+		cur_change = dlist_head_element(ReorderBufferChange, node,
+										&txn->changes);
+
+		state->entries[off].lsn = cur_change->lsn;
+		state->entries[off].change = cur_change;
+		state->entries[off].txn = txn;
+
+		binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
+	}
+
+	/* add subtransactions if they contain changes */
+	dlist_foreach(cur_txn_i, &txn->subtxns)
+	{
+		ReorderBufferTXN *cur_txn;
+
+		cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
+
+		if (cur_txn->nentries > 0)
+		{
+			ReorderBufferChange *cur_change;
+
+			if (txn->nentries != txn->nentries_mem)
+				ReorderBufferRestoreChanges(rb, cur_txn,
+											&state->entries[off].fd,
+											&state->entries[off].segno);
+
+			cur_change = dlist_head_element(ReorderBufferChange, node,
+											&cur_txn->changes);
+
+			state->entries[off].lsn = cur_change->lsn;
+			state->entries[off].change = cur_change;
+			state->entries[off].txn = cur_txn;
+
+			binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
+		}
+	}
+
+	/* assemble a valid binary heap */
+	binaryheap_build(state->heap);
+
+	return state;
+}
+
+/*
+ * FIXME: better comment and/or name
+ */
+static void
+ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	XLogSegNo	first;
+	XLogSegNo	cur;
+	XLogSegNo	last;
+
+	Assert(txn->first_lsn != InvalidXLogRecPtr);
+	Assert(txn->final_lsn != InvalidXLogRecPtr);
+
+	XLByteToSeg(txn->first_lsn, first);
+	XLByteToSeg(txn->final_lsn, last);
+
+	for (cur = first; cur <= last; cur++)
+	{
+		char		path[MAXPGPATH];
+		XLogRecPtr	recptr;
+
+		XLogSegNoOffsetToRecPtr(cur, 0, recptr);
+
+		sprintf(path, "pg_llog/%s/xid-%u-lsn-%X-%X.snap",
+				NameStr(MyLogicalDecodingSlot->name), txn->xid,
+				(uint32) (recptr >> 32), (uint32) recptr);
+		if (unlink(path) != 0 && errno != ENOENT)
+			elog(FATAL, "could not unlink file \"%s\": %m", path);
+	}
+}
+
+/*
+ * Return the next change when iterating over a transaction and its
+ * subtransaction.
+ *
+ * Returns NULL when no further changes exist.
+ */
+static ReorderBufferChange *
+ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state)
+{
+	ReorderBufferChange *change;
+	ReorderBufferIterTXNEntry *entry;
+	int32		off;
+
+	/* nothing there anymore */
+	if (state->heap->bh_size == 0)
+		return NULL;
+
+	off = DatumGetInt32(binaryheap_first(state->heap));
+	entry = &state->entries[off];
+
+	if (!dlist_is_empty(&entry->txn->subtxns))
+		elog(LOG, "tx with subtxn %u", entry->txn->xid);
+
+	/* free memory we might have "leaked" in the previous *Next call */
+	if (!dlist_is_empty(&state->old_change))
+	{
+		change = dlist_container(ReorderBufferChange, node,
+								 dlist_pop_head_node(&state->old_change));
+		ReorderBufferReturnChange(rb, change);
+		Assert(dlist_is_empty(&state->old_change));
+	}
+
+	change = entry->change;
+
+	/*
+	 * update heap with information about which transaction has the next
+	 * relevant change in LSN order
+	 */
+
+	/* there are in-memory changes */
+	if (dlist_has_next(&entry->txn->changes, &entry->change->node))
+	{
+		dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node);
+		ReorderBufferChange *next_change =
+		dlist_container(ReorderBufferChange, node, next);
+
+		/* txn stays the same */
+		state->entries[off].lsn = next_change->lsn;
+		state->entries[off].change = next_change;
+
+		binaryheap_replace_first(state->heap, Int32GetDatum(off));
+		return change;
+	}
+
+	/* try to load changes from disk */
+	if (entry->txn->nentries != entry->txn->nentries_mem)
+	{
+		/*
+		 * Ugly: restoring changes will reuse *Change records, thus delete the
+		 * current one from the per-tx list and only free in the next call.
+		 */
+		dlist_delete(&change->node);
+		dlist_push_tail(&state->old_change, &change->node);
+
+		if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->fd,
+										&state->entries[off].segno))
+		{
+			/* successfully restored changes from disk */
+			ReorderBufferChange *next_change =
+			dlist_head_element(ReorderBufferChange, node,
+							   &entry->txn->changes);
+
+			elog(DEBUG2, "restored %zu/%zu changes from disk",
+				 entry->txn->nentries_mem, entry->txn->nentries);
+			Assert(entry->txn->nentries_mem);
+			/* txn stays the same */
+			state->entries[off].lsn = next_change->lsn;
+			state->entries[off].change = next_change;
+			binaryheap_replace_first(state->heap, Int32GetDatum(off));
+
+			return change;
+		}
+	}
+
+	/* ok, no changes there anymore, remove */
+	binaryheap_remove_first(state->heap);
+
+	return change;
+}
+
+/*
+ * Deallocate the iterator
+ */
+static void
+ReorderBufferIterTXNFinish(ReorderBuffer *rb,
+						   ReorderBufferIterTXNState *state)
+{
+	int32		off;
+
+	for (off = 0; off < state->nr_txns; off++)
+	{
+		if (state->entries[off].fd != -1)
+			CloseTransientFile(state->entries[off].fd);
+	}
+
+	/* free memory we might have "leaked" in the last *Next call */
+	if (!dlist_is_empty(&state->old_change))
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node,
+								 dlist_pop_head_node(&state->old_change));
+		ReorderBufferReturnChange(rb, change);
+		Assert(dlist_is_empty(&state->old_change));
+	}
+
+	binaryheap_free(state->heap);
+	pfree(state);
+}
+
+/*
+ * Cleanup the contents of a transaction, usually after the transaction
+ * committed or aborted.
+ */
+static void
+ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	bool		found;
+	dlist_mutable_iter iter;
+
+	/* cleanup subtransactions & their changes */
+	dlist_foreach_modify(iter, &txn->subtxns)
+	{
+		ReorderBufferTXN *subtxn;
+
+		subtxn = dlist_container(ReorderBufferTXN, node, iter.cur);
+		Assert(subtxn->is_known_as_subxact);
+		Assert(subtxn->nsubtxns == 0);
+
+		/*
+		 * subtransactions are always associated to the toplevel TXN, even if
+		 * they originally were happening inside another subtxn, so we won't
+		 * ever recurse more than one level here.
+		 */
+		ReorderBufferCleanupTXN(rb, subtxn);
+	}
+
+	/* cleanup changes in the toplevel txn */
+	dlist_foreach_modify(iter, &txn->changes)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+
+		ReorderBufferReturnChange(rb, change);
+	}
+
+	/*
+	 * cleanup the tuplecids we stored timetravel access. They are always
+	 * stored in the toplevel transaction.
+	 */
+	dlist_foreach_modify(iter, &txn->tuplecids)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+		Assert(change->action_internal == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
+		ReorderBufferReturnChange(rb, change);
+	}
+
+	if (txn->base_snapshot != NULL)
+	{
+		SnapBuildSnapDecRefcount(txn->base_snapshot);
+		txn->base_snapshot = NULL;
+	}
+
+	/* delete from list of known subxacts */
+	if (txn->is_known_as_subxact)
+	{
+		dlist_delete(&txn->node);
+	}
+	/* delete from LSN ordered list of toplevel TXNs */
+	else
+	{
+		/* FIXME: adjust nsubxacts count of parent */
+		dlist_delete(&txn->node);
+	}
+
+	/* now remove reference from buffer */
+	hash_search(rb->by_txn,
+				(void *) &txn->xid,
+				HASH_REMOVE,
+				&found);
+	Assert(found);
+
+	/* remove entries spilled to disk */
+	if (txn->nentries != txn->nentries_mem)
+		ReorderBufferRestoreCleanup(rb, txn);
+
+	/* deallocate */
+	ReorderBufferReturnTXN(rb, txn);
+}
+
+/*
+ * Build a hash with a (relfilenode, ctid) -> (cmin, cmax) mapping for use by
+ * tqual.c's HeapTupleSatisfiesMVCCDuringDecoding.
+ */
+static void
+ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	dlist_iter	iter;
+	HASHCTL		hash_ctl;
+
+	if (!txn->does_timetravel || dlist_is_empty(&txn->tuplecids))
+		return;
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+
+	hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
+	hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
+	hash_ctl.hash = tag_hash;
+	hash_ctl.hcxt = rb->context;
+
+	/*
+	 * create the hash with the exact number of to-be-stored tuplecids from
+	 * the start
+	 */
+	txn->tuplecid_hash =
+		hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
+					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+	dlist_foreach(iter, &txn->tuplecids)
+	{
+		ReorderBufferTupleCidKey key;
+		ReorderBufferTupleCidEnt *ent;
+		bool		found;
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+
+		Assert(change->action_internal == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
+
+		/* be careful about padding */
+		memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
+
+		key.relnode = change->tuplecid.node;
+
+		ItemPointerCopy(&change->tuplecid.tid,
+						&key.tid);
+
+		ent = (ReorderBufferTupleCidEnt *)
+			hash_search(txn->tuplecid_hash,
+						(void *) &key,
+						HASH_ENTER | HASH_FIND,
+						&found);
+		if (!found)
+		{
+			ent->cmin = change->tuplecid.cmin;
+			ent->cmax = change->tuplecid.cmax;
+			ent->combocid = change->tuplecid.combocid;
+		}
+		else
+		{
+			Assert(ent->cmin == change->tuplecid.cmin);
+			Assert(ent->cmax == InvalidCommandId ||
+				   ent->cmax == change->tuplecid.cmax);
+
+			/*
+			 * if the tuple got valid in this transaction and now got deleted
+			 * we already have a valid cmin stored. The cmax will be
+			 * InvalidCommandId though.
+			 */
+			ent->cmax = change->tuplecid.cmax;
+		}
+	}
+}
+
+/*
+ * Copy a provided snapshot so we can modify it privately. This is needed so
+ * that catalog modifying transactions can look into intermediate catalog
+ * states.
+ */
+static Snapshot
+ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
+					  ReorderBufferTXN *txn, CommandId cid)
+{
+	Snapshot	snap;
+	dlist_iter	iter;
+	int			i = 0;
+	Size		size;
+
+	size = sizeof(SnapshotData) +
+		sizeof(TransactionId) * orig_snap->xcnt +
+		sizeof(TransactionId) * (txn->nsubtxns + 1);
+
+	elog(DEBUG1, "copying a non-transaction-specific snapshot into timetravel tx %u", txn->xid);
+
+	snap = MemoryContextAllocZero(rb->context, size);
+	memcpy(snap, orig_snap, sizeof(SnapshotData));
+
+	snap->copied = true;
+	snap->active_count = 0;
+	snap->regd_count = 0;
+	snap->xip = (TransactionId *) (snap + 1);
+
+	memcpy(snap->xip, orig_snap->xip, sizeof(TransactionId) * snap->xcnt);
+
+	/*
+	 * ->subxip contains all txids that belong to our transaction which we
+	 * need to check via cmin/cmax. Thats why we store the toplevel
+	 * transaction in there as well.
+	 */
+	snap->subxip = snap->xip + snap->xcnt;
+	snap->subxip[i++] = txn->xid;
+	snap->subxcnt = txn->nsubtxns + 1;
+
+	dlist_foreach(iter, &txn->subtxns)
+	{
+		ReorderBufferTXN *sub_txn;
+
+		sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
+		snap->subxip[i++] = sub_txn->xid;
+	}
+
+	/* sort so we can bsearch() later */
+	qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator);
+
+	/* store the specified current CommandId */
+	snap->curcid = cid;
+
+	return snap;
+}
+
+/*
+ * Free a previously ReorderBufferCopySnap'ed snapshot
+ */
+static void
+ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap)
+{
+	if (snap->copied)
+		pfree(snap);
+	else
+		SnapBuildSnapDecRefcount(snap);
+}
+
+/*
+ * Commit a transaction and replay all actions that previously have been
+ * ReorderBufferQueueChange'd in the toplevel TX or any of the subtransactions
+ * assigned via ReorderBufferCommitChild.
+ */
+void
+ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, XLogRecPtr commit_lsn,
+					XLogRecPtr end_lsn)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferIterTXNState *iterstate = NULL;
+	ReorderBufferChange *change;
+	CommandId	command_id = FirstCommandId;
+	volatile Snapshot snapshot_now;
+	Relation	relation = NULL;
+	Oid reloid;
+	bool is_transaction_state = IsTransactionOrTransactionBlock();
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+
+	/* empty transaction */
+	if (txn == NULL)
+		return;
+
+	txn->final_lsn = commit_lsn;
+	txn->end_lsn = end_lsn;
+
+	/* serialize the last bunch of changes if we need start earlier anyway */
+	if (txn->nentries_mem != txn->nentries)
+		ReorderBufferSerializeTXN(rb, txn);
+
+	/*
+	 * If this transaction didn't have any real changes in our database, it's
+	 * OK not to have a snapshot.
+	 */
+	if (txn->base_snapshot == NULL)
+		return;
+
+	snapshot_now = txn->base_snapshot;
+
+	ReorderBufferBuildTupleCidHash(rb, txn);
+
+	/* setup initial snapshot */
+	SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+
+	PG_TRY();
+	{
+		/*
+		 * Decoding needs access to syscaches et al., which in turn use
+		 * heavyweight locks and such. Thus we need to have enough state around
+		 * to keep track of those. The easiest way is to simply use a
+		 * transaction internally. That also allows us to easily enforce that
+		 * nothing writes to the database by checking for xid assignments.
+		 *
+		 * When we're called via the SQL SRF there's already a transaction
+		 * started, so start an explicit subtransaction there.
+		 */
+		if (is_transaction_state)
+			BeginInternalSubTransaction("replay");
+		else
+			StartTransactionCommand();
+
+		rb->begin(rb, txn);
+
+		iterstate = ReorderBufferIterTXNInit(rb, txn);
+		while ((change = ReorderBufferIterTXNNext(rb, iterstate)))
+		{
+			switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+			{
+				case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+				case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+				case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+					Assert(snapshot_now);
+
+					reloid = RelidByRelfilenode(change->relnode.spcNode,
+												change->relnode.relNode);
+
+					/*
+					 * catalog tuple without data, while catalog has been
+					 * rewritten
+					 */
+					if (reloid == InvalidOid &&
+						change->newtuple == NULL && change->oldtuple == NULL)
+						continue;
+					else if (reloid == InvalidOid)
+						elog(ERROR, "could not lookup relation %s",
+							 relpathperm(change->relnode, MAIN_FORKNUM));
+
+					relation = RelationIdGetRelation(reloid);
+
+					if (relation == NULL)
+						elog(ERROR, "could open relation descriptor %s",
+							 relpathperm(change->relnode, MAIN_FORKNUM));
+
+					if (RelationIsLogicallyLogged(relation))
+					{
+						/* user-triggered change */
+						if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
+						{
+						}
+						else if (!IsToastRelation(relation))
+						{
+							ReorderBufferToastReplace(rb, txn, relation, change);
+							rb->apply_change(rb, txn, relation, change);
+							ReorderBufferToastReset(rb, txn);
+						}
+						/* we're not interested in toast deletions */
+						else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
+						{
+							/*
+							 * need to reassemble change in memory, ensure it
+							 * doesn't get reused till we're done.
+							 */
+							dlist_delete(&change->node);
+							ReorderBufferToastAppendChunk(rb, txn, relation,
+														  change);
+						}
+
+					}
+					RelationClose(relation);
+					break;
+				case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+					/* XXX: we could skip snapshots in non toplevel txns */
+
+					/* get rid of the old */
+					RevertFromDecodingSnapshots();
+
+					if (snapshot_now->copied)
+					{
+						ReorderBufferFreeSnap(rb, snapshot_now);
+						snapshot_now =
+							ReorderBufferCopySnap(rb, change->snapshot,
+												  txn, command_id);
+					}
+
+					/*
+					 * restored from disk, we need to be careful not to double
+					 * free. We could introduce refcounting for that, but for
+					 * now this seems infrequent enough not to care.
+					 */
+					else if (change->snapshot->copied)
+					{
+						snapshot_now =
+							ReorderBufferCopySnap(rb, change->snapshot,
+												  txn, command_id);
+					}
+					else
+					{
+						snapshot_now = change->snapshot;
+					}
+
+
+					/* and start with the new one */
+					SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+					break;
+
+				case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+					if (!snapshot_now->copied)
+					{
+						/* we don't use the global one anymore */
+						snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
+															 txn, command_id);
+					}
+
+					command_id = Max(command_id, change->command_id);
+
+					if (command_id != InvalidCommandId)
+					{
+						snapshot_now->curcid = command_id;
+
+						RevertFromDecodingSnapshots();
+						SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+					}
+
+					/*
+					 * everytime the CommandId is incremented, we could see
+					 * new catalog contents
+					 */
+					ReorderBufferExecuteInvalidations(rb, txn);
+
+					break;
+
+				case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+					elog(ERROR, "tuplecid value in normal queue");
+					break;
+			}
+		}
+
+		ReorderBufferIterTXNFinish(rb, iterstate);
+
+		/* call commit callback */
+		rb->commit(rb, txn, commit_lsn);
+
+		/* make sure nothing has written anything */
+		if (GetTopTransactionIdIfAny() != InvalidTransactionId)
+			elog(ERROR, "cannot write during replay");
+
+		/*
+		 * Abort subtransaction or aborting transaction as a whole has the
+		 * right semantics. We want all locks acquired in here to be released,
+		 * not reassinged to the parent and we do not want any database access
+		 * have persistent effects.
+		 */
+		if (is_transaction_state)
+			RollbackAndReleaseCurrentSubTransaction();
+		else
+			AbortCurrentTransaction();
+
+		/* make sure there's no cache pollution */
+		ReorderBufferExecuteInvalidations(rb, txn);
+
+		/* cleanup */
+		RevertFromDecodingSnapshots();
+
+		if (snapshot_now->copied)
+			ReorderBufferFreeSnap(rb, snapshot_now);
+
+		ReorderBufferCleanupTXN(rb, txn);
+	}
+	PG_CATCH();
+	{
+		/* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */
+		if (iterstate)
+			ReorderBufferIterTXNFinish(rb, iterstate);
+
+		if (is_transaction_state)
+			RollbackAndReleaseCurrentSubTransaction();
+		else
+			AbortCurrentTransaction();
+
+		ReorderBufferExecuteInvalidations(rb, txn);
+
+		RevertFromDecodingSnapshots();
+
+		if (snapshot_now->copied)
+			ReorderBufferFreeSnap(rb, snapshot_now);
+
+		/*
+		 * don't do a ReorderBufferCleanupTXN here, with the vague idea of
+		 * allowing to retry decoding.
+		 */
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+}
+
+/*
+ * Abort a transaction that possibly has previous changes. Needs to be done
+ * independently for toplevel and subtransactions.
+ */
+void
+ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+
+	/* no changes in this commit */
+	if (txn == NULL)
+		return;
+
+	txn->final_lsn = lsn;
+
+	ReorderBufferCleanupTXN(rb, txn);
+}
+
+/*
+ * Check whether a transaction is already known in this module
+ */
+bool
+ReorderBufferIsXidKnown(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+	return txn != NULL;
+}
+
+/*
+ * Add a new snapshot to this transaction that is only used after lsn 'lsn'.
+ */
+void
+ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid,
+						 XLogRecPtr lsn, Snapshot snap)
+{
+	ReorderBufferChange *change = ReorderBufferGetChange(rb);
+
+	change->snapshot = snap;
+	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT;
+
+	ReorderBufferQueueChange(rb, xid, lsn, change);
+}
+
+/*
+ * Setup the base snapshot of a transaction. That is the snapshot that is used
+ * to decode all changes until either this transaction modifies the catalog or
+ * another catalog modifying transaction commits.
+ */
+void
+ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, Snapshot snap)
+{
+	ReorderBufferTXN *txn;
+	bool		is_new;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true);
+	Assert(txn->base_snapshot == NULL);
+
+	txn->base_snapshot = snap;
+}
+
+/*
+ * Access the catalog with this CommandId at this point in the changestream.
+ *
+ * May only be called for command ids > 1
+ */
+void
+ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, CommandId cid)
+{
+	ReorderBufferChange *change = ReorderBufferGetChange(rb);
+
+	change->command_id = cid;
+	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID;
+
+	ReorderBufferQueueChange(rb, xid, lsn, change);
+}
+
+
+/*
+ * Add new (relfilenode, tid) -> (cmin, cmax) mappings.
+ */
+void
+ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, RelFileNode node,
+							 ItemPointerData tid, CommandId cmin,
+							 CommandId cmax, CommandId combocid)
+{
+	ReorderBufferChange *change = ReorderBufferGetChange(rb);
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	change->tuplecid.node = node;
+	change->tuplecid.tid = tid;
+	change->tuplecid.cmin = cmin;
+	change->tuplecid.cmax = cmax;
+	change->tuplecid.combocid = combocid;
+	change->lsn = lsn;
+	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
+
+	dlist_push_tail(&txn->tuplecids, &change->node);
+	txn->ntuplecids++;
+}
+
+/*
+ * Setup the invalidation of the toplevel transaction.
+ *
+ * This needs to be done before ReorderBufferCommit is called!
+ */
+void
+ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid,
+							  XLogRecPtr lsn, Size nmsgs,
+							  SharedInvalidationMessage *msgs)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	if (txn->ninvalidations != 0)
+		elog(ERROR, "only ever add one set of invalidations");
+
+	Assert(nmsgs > 0);
+
+	txn->ninvalidations = nmsgs;
+	txn->invalidations = (SharedInvalidationMessage *)
+		MemoryContextAlloc(rb->context,
+						   sizeof(SharedInvalidationMessage) * nmsgs);
+	memcpy(txn->invalidations, msgs, sizeof(SharedInvalidationMessage) * nmsgs);
+}
+
+/*
+ * Apply all invalidations we know. Possibly we only need parts at this point
+ * in the changestream but we don't know which those are.
+ */
+static void
+ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	int			i;
+
+	for (i = 0; i < txn->ninvalidations; i++)
+		LocalExecuteInvalidationMessage(&txn->invalidations[i]);
+}
+
+/*
+ * Mark a transaction as doing timetravel.
+ */
+void
+ReorderBufferXidSetTimetravel(ReorderBuffer *rb, TransactionId xid,
+							  XLogRecPtr lsn)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	txn->does_timetravel = true;
+}
+
+/*
+ * Query whether a transaction is already *known* to be doing timetravel. This
+ * can be wrong until directly before the commit!
+ */
+bool
+ReorderBufferXidDoesTimetravel(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+	if (txn == NULL)
+		return false;
+
+	return txn->does_timetravel;
+}
+
+/*
+ * Have we already added the first snapshot?
+ */
+bool
+ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+
+	/* transaction isn't known yet, ergo no snapshot */
+	if (txn == NULL)
+		return false;
+
+	return txn->base_snapshot != NULL;
+}
+
+static void
+ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz)
+{
+	if (!rb->outbufsize)
+	{
+		rb->outbuf = MemoryContextAlloc(rb->context, sz);
+		rb->outbufsize = sz;
+	}
+	else if (rb->outbufsize < sz)
+	{
+		rb->outbuf = repalloc(rb->outbuf, sz);
+		rb->outbufsize = sz;
+	}
+}
+
+typedef struct ReorderBufferDiskChange
+{
+	Size		size;
+	ReorderBufferChange change;
+	/* data follows */
+} ReorderBufferDiskChange;
+
+/*
+ * Persistency support
+ */
+static void
+ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							 int fd, ReorderBufferChange *change)
+{
+	ReorderBufferDiskChange *ondisk;
+	Size		sz = sizeof(ReorderBufferDiskChange);
+
+	ReorderBufferSerializeReserve(rb, sz);
+
+	ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+	memcpy(&ondisk->change, change, sizeof(ReorderBufferChange));
+
+	switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+	{
+		case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+			{
+				char	   *data;
+				Size		oldlen = 0;
+				Size		newlen = 0;
+
+				if (change->oldtuple)
+					oldlen = offsetof(ReorderBufferTupleBuf, data)
+						+change->oldtuple->tuple.t_len
+						- offsetof(HeapTupleHeaderData, t_bits);
+
+				if (change->newtuple)
+					newlen = offsetof(ReorderBufferTupleBuf, data)
+						+change->newtuple->tuple.t_len
+						- offsetof(HeapTupleHeaderData, t_bits);
+
+				sz += oldlen;
+				sz += newlen;
+
+				/* make sure we have enough space */
+				ReorderBufferSerializeReserve(rb, sz);
+
+				data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+				/* might have been reallocated above */
+				ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+				if (oldlen)
+				{
+					memcpy(data, change->oldtuple, oldlen);
+					data += oldlen;
+					Assert(&change->oldtuple->header == change->oldtuple->tuple.t_data);
+				}
+
+				if (newlen)
+				{
+					memcpy(data, change->newtuple, newlen);
+					data += newlen;
+					Assert(&change->newtuple->header == change->newtuple->tuple.t_data);
+				}
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+			{
+				char	   *data;
+
+				sz += sizeof(SnapshotData) +
+					sizeof(TransactionId) * change->snapshot->xcnt +
+					sizeof(TransactionId) * change->snapshot->subxcnt
+					;
+
+				/* make sure we have enough space */
+				ReorderBufferSerializeReserve(rb, sz);
+				data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+				/* might have been reallocated above */
+				ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+				memcpy(data, change->snapshot, sizeof(SnapshotData));
+				data += sizeof(SnapshotData);
+
+				if (change->snapshot->xcnt)
+				{
+					memcpy(data, change->snapshot->xip,
+						   sizeof(TransactionId) + change->snapshot->xcnt);
+					data += sizeof(TransactionId) + change->snapshot->xcnt;
+				}
+
+				if (change->snapshot->subxcnt)
+				{
+					memcpy(data, change->snapshot->subxip,
+						   sizeof(TransactionId) + change->snapshot->subxcnt);
+					data += sizeof(TransactionId) + change->snapshot->subxcnt;
+				}
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+			/* ReorderBufferChange contains everything important */
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+			/* ReorderBufferChange contains everything important */
+			break;
+	}
+
+	ondisk->size = sz;
+
+	if (write(fd, rb->outbuf, ondisk->size) != ondisk->size)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not write to xid data file \"%u\": %m",
+						txn->xid)));
+	}
+
+	Assert(ondisk->change.action_internal == change->action_internal);
+}
+
+static void
+ReorderBufferCheckSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	/* FIXME subtxn handling? */
+	if (txn->nentries_mem >= max_memtries)
+	{
+		ReorderBufferSerializeTXN(rb, txn);
+		Assert(txn->nentries_mem == 0);
+	}
+}
+
+static void
+ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	dlist_iter	subtxn_i;
+	dlist_mutable_iter change_i;
+	int			fd = -1;
+	XLogSegNo	curOpenSegNo = 0;
+	Size		spilled = 0;
+	char		path[MAXPGPATH];
+
+	elog(DEBUG2, "spill %zu changes in tx %u to disk",
+		 txn->nentries_mem, txn->xid);
+
+	/* do the same to all child TXs */
+	dlist_foreach(subtxn_i, &txn->subtxns)
+	{
+		ReorderBufferTXN *subtxn;
+
+		subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur);
+		ReorderBufferSerializeTXN(rb, subtxn);
+	}
+
+	/* serialize changestream */
+	dlist_foreach_modify(change_i, &txn->changes)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, change_i.cur);
+
+		/*
+		 * store in segment in which it belongs by start lsn, don't split over
+		 * multiple segments tho
+		 */
+		if (fd == -1 || XLByteInSeg(change->lsn, curOpenSegNo))
+		{
+			XLogRecPtr	recptr;
+
+			if (fd != -1)
+				CloseTransientFile(fd);
+
+			XLByteToSeg(change->lsn, curOpenSegNo);
+			XLogSegNoOffsetToRecPtr(curOpenSegNo, 0, recptr);
+
+			sprintf(path, "pg_llog/%s/xid-%u-lsn-%X-%X.snap",
+					NameStr(MyLogicalDecodingSlot->name), txn->xid,
+					(uint32) (recptr >> 32), (uint32) recptr);
+
+			/* open segment, create it if necessary */
+			fd = OpenTransientFile(path,
+								   O_CREAT | O_WRONLY | O_APPEND | PG_BINARY,
+								   S_IRUSR | S_IWUSR);
+
+			if (fd < 0)
+				ereport(ERROR, (errmsg("could not open reorderbuffer file %s for writing: %m", path)));
+		}
+
+		ReorderBufferSerializeChange(rb, txn, fd, change);
+		dlist_delete(&change->node);
+		ReorderBufferReturnChange(rb, change);
+
+		spilled++;
+	}
+
+	Assert(spilled == txn->nentries_mem);
+	Assert(dlist_is_empty(&txn->changes));
+	txn->nentries_mem = 0;
+
+	if (fd != -1)
+		CloseTransientFile(fd);
+
+	/* issue write barrier */
+	/* serialize main transaction state */
+}
+
+static Size
+ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							int *fd, XLogSegNo *segno)
+{
+	Size		restored = 0;
+	XLogSegNo	last_segno;
+	dlist_mutable_iter cleanup_iter;
+
+	Assert(txn->first_lsn != InvalidXLogRecPtr);
+	Assert(txn->final_lsn != InvalidXLogRecPtr);
+
+	/* free current entries, so we have memory for more */
+	dlist_foreach_modify(cleanup_iter, &txn->changes)
+	{
+		ReorderBufferChange *cleanup =
+		dlist_container(ReorderBufferChange, node, cleanup_iter.cur);
+
+		dlist_delete(&cleanup->node);
+		ReorderBufferReturnChange(rb, cleanup);
+	}
+	txn->nentries_mem = 0;
+	Assert(dlist_is_empty(&txn->changes));
+
+	XLByteToSeg(txn->final_lsn, last_segno);
+
+	while (restored < max_memtries && *segno <= last_segno)
+	{
+		int			readBytes;
+		ReorderBufferDiskChange *ondisk;
+
+		if (*fd == -1)
+		{
+			XLogRecPtr	recptr;
+			char		path[MAXPGPATH];
+
+			/* first time in */
+			if (*segno == 0)
+			{
+				XLByteToSeg(txn->first_lsn, *segno);
+				elog(LOG, "initial restoring from %zu to %zu",
+					 *segno, last_segno);
+			}
+
+			Assert(*segno != 0 || dlist_is_empty(&txn->changes));
+			XLogSegNoOffsetToRecPtr(*segno, 0, recptr);
+
+			sprintf(path, "pg_llog/%s/xid-%u-lsn-%X-%X.snap",
+					NameStr(MyLogicalDecodingSlot->name), txn->xid,
+					(uint32) (recptr >> 32), (uint32) recptr);
+
+			elog(LOG, "opening file %s", path);
+
+			*fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+			if (*fd < 0 && errno == ENOENT)
+			{
+				*fd = -1;
+				(*segno)++;
+				continue;
+			}
+			else if (*fd < 0)
+				ereport(ERROR, (errmsg("could not open reorderbuffer file %s for reading: %m", path)));
+
+		}
+
+		ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange));
+
+
+		/*
+		 * read the statically sized part of a change which has information
+		 * about the total size. If we couldn't read a record, we're at the
+		 * end of this file.
+		 */
+
+		readBytes = read(*fd, rb->outbuf, sizeof(ReorderBufferDiskChange));
+
+		/* eof */
+		if (readBytes == 0)
+		{
+			CloseTransientFile(*fd);
+			*fd = -1;
+			(*segno)++;
+			continue;
+		}
+		else if (readBytes < 0)
+			elog(ERROR, "read failed: %m");
+		else if (readBytes != sizeof(ReorderBufferDiskChange))
+			elog(ERROR, "incomplete read, read %d instead of %zu",
+				 readBytes, sizeof(ReorderBufferDiskChange));
+
+		ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+		ReorderBufferSerializeReserve(rb,
+									  sizeof(ReorderBufferDiskChange) + ondisk->size);
+		ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+		readBytes = read(*fd, rb->outbuf + sizeof(ReorderBufferDiskChange),
+						 ondisk->size - sizeof(ReorderBufferDiskChange));
+
+		if (readBytes < 0)
+			elog(ERROR, "read2 failed: %m");
+		else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange))
+			elog(ERROR, "incomplete read2, read %d instead of %zu",
+				 readBytes, ondisk->size - sizeof(ReorderBufferDiskChange));
+
+		/*
+		 * ok, read a full change from disk, now restore it into proper
+		 * in-memory format
+		 */
+		ReorderBufferRestoreChange(rb, txn, rb->outbuf);
+		restored++;
+	}
+
+	return restored;
+}
+
+/*
+ * Convert change from its on-disk format to in-memory format and queue it onto
+ * the TXN's ->changes list.
+ */
+static void
+ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						   char *data)
+{
+	ReorderBufferDiskChange *ondisk;
+	ReorderBufferChange *change;
+
+	ondisk = (ReorderBufferDiskChange *) data;
+
+	change = ReorderBufferGetChange(rb);
+
+	/* copy static part */
+	memcpy(change, &ondisk->change, sizeof(ReorderBufferChange));
+
+	data += sizeof(ReorderBufferDiskChange);
+
+	/* restore individual stuff */
+	switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+	{
+		case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+			if (change->newtuple)
+			{
+				Size		len = offsetof(ReorderBufferTupleBuf, data)
+				+((ReorderBufferTupleBuf *) data)->tuple.t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+
+				change->newtuple = ReorderBufferGetTupleBuf(rb);
+				memcpy(change->newtuple, data, len);
+				change->newtuple->tuple.t_data = &change->newtuple->header;
+
+				data += len;
+			}
+
+			if (change->oldtuple)
+			{
+				Size		len = offsetof(ReorderBufferTupleBuf, data)
+				+((ReorderBufferTupleBuf *) data)->tuple.t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+
+				change->oldtuple = ReorderBufferGetTupleBuf(rb);
+				memcpy(change->oldtuple, data, len);
+				change->oldtuple->tuple.t_data = &change->oldtuple->header;
+				data += len;
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+			{
+				Snapshot	oldsnap = (Snapshot) data;
+				Size		size = sizeof(SnapshotData) +
+				sizeof(TransactionId) * oldsnap->xcnt +
+				sizeof(TransactionId) * (oldsnap->subxcnt + 0)
+						   ;
+
+				Assert(change->snapshot != NULL);
+
+				change->snapshot = MemoryContextAllocZero(rb->context, size);
+
+				memcpy(change->snapshot, data, size);
+				change->snapshot->xip = (TransactionId *)
+					(((char *) change->snapshot) + sizeof(SnapshotData));
+				change->snapshot->subxip =
+					change->snapshot->xip + change->snapshot->xcnt + 0;
+				change->snapshot->copied = true;
+				break;
+			}
+			/* nothing needs to be done */
+		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+			break;
+	}
+
+	dlist_push_tail(&txn->changes, &change->node);
+	txn->nentries_mem++;
+}
+
+/*
+ * Delete all data spilled to disk after we've restarted/crashed. It will be
+ * recreated when the respective slots are reused.
+ */
+void
+ReorderBufferStartup(void)
+{
+	DIR		   *logical_dir;
+	struct dirent *logical_de;
+
+	DIR		   *spill_dir;
+	struct dirent *spill_de;
+
+	logical_dir = AllocateDir("pg_llog");
+	while ((logical_de = ReadDir(logical_dir, "pg_llog")) != NULL)
+	{
+		char		path[MAXPGPATH];
+
+		if (strcmp(logical_de->d_name, ".") == 0 ||
+			strcmp(logical_de->d_name, "..") == 0)
+			continue;
+
+		/* one of our own directories */
+		if (strcmp(logical_de->d_name, "snapshots") == 0)
+			continue;
+
+		/*
+		 * ok, has to be a surviving logical slot, iterate and delete
+		 * everythign starting with xid-*
+		 */
+		sprintf(path, "pg_llog/%s", logical_de->d_name);
+
+		spill_dir = AllocateDir(path);
+		while ((spill_de = ReadDir(spill_dir, "pg_llog")) != NULL)
+		{
+			if (strcmp(spill_de->d_name, ".") == 0 ||
+				strcmp(spill_de->d_name, "..") == 0)
+				continue;
+
+			if (strncmp(spill_de->d_name, "xid", 3) == 0)
+			{
+				sprintf(path, "pg_llog/%s/%s", logical_de->d_name,
+						spill_de->d_name);
+
+				if (unlink(path) != 0)
+					ereport(PANIC,
+							(errcode_for_file_access(),
+						  errmsg("could not remove xid data file \"%s\": %m",
+								 path)));
+			}
+			/* XXX: WARN? */
+		}
+		FreeDir(spill_dir);
+	}
+	FreeDir(logical_dir);
+}
+
+/*
+ * toast support
+ */
+
+/*
+ * copied stuff from tuptoaster.c. Perhaps there should be toast_internal.h?
+ */
+#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)	\
+do { \
+	varattrib_1b_e *attre = (varattrib_1b_e *) (attr); \
+	Assert(VARATT_IS_EXTERNAL(attre)); \
+	Assert(VARSIZE_EXTERNAL(attre) == sizeof(toast_pointer) + VARHDRSZ_EXTERNAL); \
+	memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), sizeof(toast_pointer)); \
+} while (0)
+
+#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) \
+	((toast_pointer).va_extsize < (toast_pointer).va_rawsize - VARHDRSZ)
+
+/*
+ * Initialize per tuple toast reconstruction support.
+ */
+static void
+ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	HASHCTL		hash_ctl;
+
+	Assert(txn->toast_hash == NULL);
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+	hash_ctl.keysize = sizeof(Oid);
+	hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
+	hash_ctl.hash = tag_hash;
+	hash_ctl.hcxt = rb->context;
+	txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
+								  HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+}
+
+/*
+ * Per toast-chunk handling for toast reconstruction
+ *
+ * Appends a toast chunk so we can reconstruct it when the tuple "owning" the
+ * toasted Datum comes along.
+ */
+static void
+ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							  Relation relation, ReorderBufferChange *change)
+{
+	ReorderBufferToastEnt *ent;
+	bool		found;
+	int32		chunksize;
+	bool		isnull;
+	Pointer		chunk;
+	TupleDesc	desc = RelationGetDescr(relation);
+	Oid			chunk_id;
+	Oid			chunk_seq;
+
+	if (txn->toast_hash == NULL)
+		ReorderBufferToastInitHash(rb, txn);
+
+	Assert(IsToastRelation(relation));
+
+	chunk_id = DatumGetObjectId(fastgetattr(&change->newtuple->tuple, 1, desc, &isnull));
+	Assert(!isnull);
+	chunk_seq = DatumGetInt32(fastgetattr(&change->newtuple->tuple, 2, desc, &isnull));
+	Assert(!isnull);
+
+	ent = (ReorderBufferToastEnt *)
+		hash_search(txn->toast_hash,
+					(void *) &chunk_id,
+					HASH_ENTER,
+					&found);
+
+	if (!found)
+	{
+		Assert(ent->chunk_id == chunk_id);
+		ent->num_chunks = 0;
+		ent->last_chunk_seq = 0;
+		ent->size = 0;
+		ent->reconstructed = NULL;
+		dlist_init(&ent->chunks);
+
+		if (chunk_seq != 0)
+			elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0",
+				 chunk_seq, chunk_id);
+	}
+	else if (found && chunk_seq != ent->last_chunk_seq + 1)
+		elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d",
+			 chunk_seq, chunk_id, ent->last_chunk_seq + 1);
+
+	chunk = DatumGetPointer(fastgetattr(&change->newtuple->tuple, 3, desc, &isnull));
+	Assert(!isnull);
+
+	/* calculate size so we can allocate the right size at once later */
+	if (!VARATT_IS_EXTENDED(chunk))
+		chunksize = VARSIZE(chunk) - VARHDRSZ;
+	else if (VARATT_IS_SHORT(chunk))
+		/* could happen due to heap_form_tuple doing its thing */
+		chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
+	else
+		elog(ERROR, "unexpected type of toast chunk");
+
+	ent->size += chunksize;
+	ent->last_chunk_seq = chunk_seq;
+	ent->num_chunks++;
+	dlist_push_tail(&ent->chunks, &change->node);
+}
+
+/*
+ * Rejigger change->newtuple to point to in-memory toast tuples instead to
+ * on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).
+ *
+ * We cannot replace unchanged toast tuples though, so those will still point
+ * to on-disk toast data.
+ */
+static void
+ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						  Relation relation, ReorderBufferChange *change)
+{
+	TupleDesc	desc;
+	int			natt;
+	Datum	   *attrs;
+	bool	   *isnull;
+	bool	   *free;
+	HeapTuple	newtup;
+	Relation	toast_rel;
+	TupleDesc	toast_desc;
+	MemoryContext oldcontext;
+
+	/* no toast tuples changed */
+	if (txn->toast_hash == NULL)
+		return;
+
+	oldcontext = MemoryContextSwitchTo(rb->context);
+
+	/* we should only have toast tuples in an INSERT or UPDATE */
+	Assert(change->newtuple);
+
+	desc = RelationGetDescr(relation);
+
+	toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
+	toast_desc = RelationGetDescr(toast_rel);
+
+	/* should we allocate from stack instead? */
+	attrs = palloc0(sizeof(Datum) * desc->natts);
+	isnull = palloc0(sizeof(bool) * desc->natts);
+	free = palloc0(sizeof(bool) * desc->natts);
+
+	heap_deform_tuple(&change->newtuple->tuple, desc,
+					  attrs, isnull);
+
+	for (natt = 0; natt < desc->natts; natt++)
+	{
+		Form_pg_attribute attr = desc->attrs[natt];
+		ReorderBufferToastEnt *ent;
+		struct varlena *varlena;
+
+		/* va_rawsize is the size of the original datum -- including header */
+		struct varatt_external toast_pointer;
+		struct varatt_indirect redirect_pointer;
+		struct varlena *new_datum = NULL;
+		struct varlena *reconstructed;
+		dlist_iter	it;
+		Size		data_done = 0;
+
+		/* system columns aren't toasted */
+		if (attr->attnum < 0)
+			continue;
+
+		if (attr->attisdropped)
+			continue;
+
+		/* not a varlena datatype */
+		if (attr->attlen != -1)
+			continue;
+
+		/* no data */
+		if (isnull[natt])
+			continue;
+
+		/* ok, we know we have a toast datum */
+		varlena = (struct varlena *) DatumGetPointer(attrs[natt]);
+
+		/* no need to do anything if the tuple isn't external */
+		if (!VARATT_IS_EXTERNAL(varlena))
+			continue;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena);
+
+		/*
+		 * check whether the toast tuple changed, replace if so.
+		 */
+		ent = (ReorderBufferToastEnt *)
+			hash_search(txn->toast_hash,
+						(void *) &toast_pointer.va_valueid,
+						HASH_FIND,
+						NULL);
+		if (ent == NULL)
+			continue;
+
+		new_datum =
+			(struct varlena *) palloc0(INDIRECT_POINTER_SIZE);
+
+		free[natt] = true;
+
+		reconstructed = palloc0(toast_pointer.va_rawsize);
+
+		ent->reconstructed = reconstructed;
+
+		/* stitch toast tuple back together from its parts */
+		dlist_foreach(it, &ent->chunks)
+		{
+			bool		isnull;
+			ReorderBufferTupleBuf *tup =
+			dlist_container(ReorderBufferChange, node, it.cur)->newtuple;
+			Pointer		chunk =
+			DatumGetPointer(fastgetattr(&tup->tuple, 3, toast_desc, &isnull));
+
+			Assert(!isnull);
+			Assert(!VARATT_IS_EXTERNAL(chunk));
+			Assert(!VARATT_IS_SHORT(chunk));
+
+			memcpy(VARDATA(reconstructed) + data_done,
+				   VARDATA(chunk),
+				   VARSIZE(chunk) - VARHDRSZ);
+			data_done += VARSIZE(chunk) - VARHDRSZ;
+		}
+		Assert(data_done == toast_pointer.va_extsize);
+
+		/* make sure its marked as compressed or not */
+		if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
+			SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
+		else
+			SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
+
+		memset(&redirect_pointer, 0, sizeof(redirect_pointer));
+		redirect_pointer.pointer = reconstructed;
+
+		SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
+		memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
+			   sizeof(redirect_pointer));
+
+		attrs[natt] = PointerGetDatum(new_datum);
+	}
+
+	/*
+	 * Build tuple in separate memory & copy tuple back into the tuplebuf
+	 * passed to the output plugin. We can't directly heap_fill_tuple() into
+	 * the tuplebuf because attrs[] will point back into the current content.
+	 */
+	newtup = heap_form_tuple(desc, attrs, isnull);
+	Assert(change->newtuple->tuple.t_len <= MaxHeapTupleSize);
+	Assert(&change->newtuple->header == change->newtuple->tuple.t_data);
+
+	memcpy(change->newtuple->tuple.t_data,
+		   newtup->t_data,
+		   newtup->t_len);
+	change->newtuple->tuple.t_len = newtup->t_len;
+
+	/*
+	 * free resources we won't further need, more persistent stuff will be
+	 * free'd in ReorderBufferToastReset().
+	 */
+	RelationClose(toast_rel);
+	pfree(newtup);
+	for (natt = 0; natt < desc->natts; natt++)
+	{
+		if (free[natt])
+			pfree(DatumGetPointer(attrs[natt]));
+	}
+	pfree(attrs);
+	pfree(free);
+	pfree(isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * Free all resources allocated for toast reconstruction.
+ */
+static void
+ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	HASH_SEQ_STATUS hstat;
+	ReorderBufferToastEnt *ent;
+
+	if (txn->toast_hash == NULL)
+		return;
+
+	/* sequentially walk over the hash and free everything */
+	hash_seq_init(&hstat, txn->toast_hash);
+	while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
+	{
+		dlist_mutable_iter it;
+
+		if (ent->reconstructed != NULL)
+			pfree(ent->reconstructed);
+
+		dlist_foreach_modify(it, &ent->chunks)
+		{
+			ReorderBufferChange *change =
+			dlist_container(ReorderBufferChange, node, it.cur);
+
+			dlist_delete(&change->node);
+			ReorderBufferReturnChange(rb, change);
+		}
+	}
+
+	hash_destroy(txn->toast_hash);
+	txn->toast_hash = NULL;
+}
+
+
+/*
+ * Visibility support routines
+ */
+
+/*-------------------------------------------------------------------------
+ * Lookup actual cmin/cmax values during timetravel access. We can't always
+ * rely on stored cmin/cmax values because of two scenarios:
+ *
+ * * A tuple got changed multiple times during a single transaction and thus
+ *	 has got a combocid. Combocid's are only valid for the duration of a single
+ *	 transaction.
+ * * A tuple with a cmin but no cmax (and thus no combocid) got deleted/updated
+ *	 in another transaction than the one which created it which we are looking
+ *	 at right now. As only one of cmin, cmax or combocid is actually stored in
+ *	 the heap we don't have access to the the value we need anymore.
+ *
+ * To resolve those problems we have a per-transaction hash of (cmin, cmax)
+ * tuples keyed by (relfilenode, ctid) which contains the actual (cmin, cmax)
+ * values. That also takes care of combocids by simply not caring about them at
+ * all. As we have the real cmin/cmax values thats enough.
+ *
+ * As we only care about catalog tuples here the overhead of this hashtable
+ * should be acceptable.
+ * -------------------------------------------------------------------------
+ */
+extern bool
+ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
+							  HeapTuple htup, Buffer buffer,
+							  CommandId *cmin, CommandId *cmax)
+{
+	ReorderBufferTupleCidKey key;
+	ReorderBufferTupleCidEnt *ent;
+	ForkNumber	forkno;
+	BlockNumber blockno;
+
+	/* be careful about padding */
+	memset(&key, 0, sizeof(key));
+
+	Assert(!BufferIsLocal(buffer));
+
+	/*
+	 * get relfilenode from the buffer, no convenient way to access it other
+	 * than that.
+	 */
+	BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+
+	/* tuples can only be in the main fork */
+	Assert(forkno == MAIN_FORKNUM);
+	Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
+
+	ItemPointerCopy(&htup->t_self,
+					&key.tid);
+
+	ent = (ReorderBufferTupleCidEnt *)
+		hash_search(tuplecid_data,
+					(void *) &key,
+					HASH_FIND,
+					NULL);
+
+	if (ent == NULL)
+		return false;
+
+	if (cmin)
+		*cmin = ent->cmin;
+	if (cmax)
+		*cmax = ent->cmax;
+	return true;
+}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
new file mode 100644
index 0000000..6547e3f
--- /dev/null
+++ b/src/backend/replication/logical/snapbuild.c
@@ -0,0 +1,1581 @@
+/*-------------------------------------------------------------------------
+ *
+ * snapbuild.c
+ *
+ *	  Support for building timetravel snapshots based on the contents of the
+ *	  WAL which then can be used to decode the contents of the WAL.
+ *
+ * NOTES:
+ *
+ * We build snapshots which can *only* be used to read catalog contents by
+ * reading and interpreting the WAL stream. The aim is to build a snapshot that
+ * behaves the same as a freshly taken MVCC snapshot would have at the time the
+ * XLogRecord was generated.
+ *
+ * To build the snapshots we reuse the infrastructure built for hot
+ * standby. The snapshots we build look different than HS' because we have
+ * different needs. To successfully decode data from the WAL we only need to
+ * access catalogs/(sys|rel|cat)cache, not the actual user tables since the
+ * data we decode is contained in the WAL records. Also, our snapshots need to
+ * be different in comparison to normal MVCC ones because in contrast to those
+ * we cannot fully rely on the clog and pg_subtrans for information about
+ * committed transactions because they might commit in the future from the POV
+ * of the wal entry we're currently decoding.
+ *
+ * As the percentage of transactions modifying the catalog normally is fairly
+ * small in comparisons to ones only manipulating user data we keep track of
+ * the committed catalog modifying ones inside (xmin, xmax) instead of keeping
+ * track of all running transactions like its done in a normal snapshot. Note
+ * that we're generally only looking at transactions that have acquired an
+ * xid. That is we keep a list of transactions between snapshot->(xmin, xmax)
+ * that we consider committed, everything else is considered aborted/in
+ * progress. That also allows us not to care about subtransactions before they
+ * have committed which means this modules, in contrast to HS, doesn't have to
+ * care about suboverflowed subtransactions and similar.
+ *
+ * One complexity of doing this is that to e.g. handle mixed DDL/DML
+ * transactions we need Snapshots that see intermediate versions of the catalog
+ * in a transaction. During normal operation this is achieved by using
+ * CommandIds/cmin/cmax. The problem with that however is that for space
+ * efficiency reasons only one value of that is stored (c.f. combocid.c). Since
+ * Combocids are only available in memory we log additional information which
+ * allows us to get the original (cmin, cmax) pair during visibility
+ * checks. Check the reorderbuffer.c's comment above
+ * ResolveCminCmaxDuringDecoding() for details.
+ *
+ * To facilitate all this we need our own visibility routine, as the normal
+ * ones are optimized for different usecases. To make sure no unexpected
+ * database access bypassing our special snapshot is possible - which would
+ * possibly load invalid data into caches - we temporarily overload the
+ * .satisfies methods of the usual snapshots while doing timetravel.
+ *
+ * To replace the normal catalog snapshots with timetravel ones use the
+ * SetupDecodingSnapshots and RevertFromDecodingSnapshots functions.
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/snapbuild.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "miscadmin.h"
+
+#include "access/heapam_xlog.h"
+#include "access/transam.h"
+#include "access/xact.h"
+
+#include "replication/logical.h"
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h"
+
+#include "utils/builtins.h"
+#include "utils/catcache.h" /* FIXME: Use */
+#include "utils/memutils.h"
+#include "utils/snapshot.h"
+#include "utils/snapmgr.h"
+#include "utils/tqual.h"
+
+#include "storage/block.h"		/* debugging output */
+#include "storage/fd.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/standby.h"
+
+typedef struct SnapBuild
+{
+	/* how far are we along building our first full snapshot */
+	SnapBuildState state;
+
+	/* private memory context used to allocate memory for this module. */
+	MemoryContext context;
+
+	/* all transactions < than this have committed/aborted */
+	TransactionId xmin;
+
+	/* all transactions >= than this are uncommitted */
+	TransactionId xmax;
+
+	/*
+	 * Don't replay commits from an LSN <= this LSN. This can be set
+	 * externally but it will also be advanced (never retreat) from within
+	 * snapbuild.c.
+	 */
+	XLogRecPtr	transactions_after;
+
+	/*
+	 * Don't start decoding WAL until the "xl_running_xacts" information
+	 * indicates there are no running xids with a xid smaller than this.
+	 */
+	TransactionId initial_xmin_horizon;
+
+	/*
+	 * Snapshot thats valid to see all currently committed transactions that
+	 * see catalog modifications.
+	 */
+	Snapshot	snapshot;
+
+	/*
+	 * LSN of the last location we are sure a snapshot has been serialized to.
+	 */
+	XLogRecPtr	last_serialized_snapshot;
+
+	ReorderBuffer *reorder;
+
+	/*
+	 * Information about initially running transactions
+	 *
+	 * When we start building a snapshot there already may be transactions in
+	 * progress.  Those are stored in running.xip.	We don't have enough
+	 * information about those to decode their contents, so until they are
+	 * finished (xcnt=0) we cannot switch to a CONSISTENT state.
+	 */
+	struct
+	{
+		/*
+		 * As long as running.xcnt all XIDs < running.xmin and > running.xmax
+		 * have to be checked whether they still are running.
+		 */
+		TransactionId xmin;
+		TransactionId xmax;
+
+		size_t		xcnt;		/* number of used xip entries */
+		size_t		xcnt_space; /* allocated size of xip */
+		TransactionId *xip;		/* running xacts array, xidComparator-sorted */
+	}			running;
+
+	/*
+	 * Array of transactions which could have catalog changes that committed
+	 * between xmin and xmax
+	 */
+	struct
+	{
+		/* number of committed transactions */
+		size_t		xcnt;
+
+		/* available space for committed transactions */
+		size_t		xcnt_space;
+
+		/*
+		 * Until we reach a CONSISTENT state, we record commits of all
+		 * transactions, not just the catalog changing ones. Record when that
+		 * changes so we know we cannot export a snapshot safely anymore.
+		 */
+		bool		includes_all_transactions;
+
+		/*
+		 * Array of committed transactions that have modified the catalog.
+		 *
+		 * As this array is frequently modified we do *not* keep it in
+		 * xidComparator order. Instead we sort the array when building &
+		 * distributing a snapshot.
+		 *
+		 * XXX: That doesn't seem to be good reasoning anymore. Everytime we
+		 * add something here after becoming consistent will also require
+		 * distributing a snapshot. Storing them sorted would potentially make
+		 * it easier to purge as well (but more complicated wrt wraparound?).
+		 */
+		TransactionId *xip;
+	}			committed;
+} SnapBuild;
+
+/*
+ * Starting a transaction -- which we need to do while exporting a snapshot --
+ * removes knowledge about the previously used resowner, so we save it here.
+ */
+ResourceOwner SavedResourceOwnerDuringExport = NULL;
+
+/* transaction state manipulation functions */
+static void SnapBuildEndTxn(SnapBuild *builder, TransactionId xid);
+
+/* ->running manipulation */
+static bool SnapBuildTxnIsRunning(SnapBuild *builder, TransactionId xid);
+
+/* ->committed manipulation */
+static void SnapBuildPurgeCommittedTxn(SnapBuild *builder);
+
+/* snapshot building/manipulation/distribution functions */
+static Snapshot SnapBuildBuildSnapshot(SnapBuild *builder, TransactionId xid);
+
+static void SnapBuildFreeSnapshot(Snapshot snap);
+
+static void SnapBuildSnapIncRefcount(Snapshot snap);
+
+static void SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn);
+
+/* xlog reading helper functions for SnapBuildProcessRecord */
+static bool SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running);
+
+/* serialization functions */
+static void SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn);
+static bool SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn);
+
+
+/*
+ * Allocate a new snapshot builder.
+ */
+SnapBuild *
+AllocateSnapshotBuilder(ReorderBuffer *reorder,
+						TransactionId xmin_horizon,
+						XLogRecPtr start_lsn)
+{
+	MemoryContext context;
+	MemoryContext oldcontext;
+	SnapBuild  *builder;
+
+	context = AllocSetContextCreate(TopMemoryContext,
+									"snapshot builder context",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	oldcontext = MemoryContextSwitchTo(context);
+
+	builder = palloc0(sizeof(SnapBuild));
+
+	builder->state = SNAPBUILD_START;
+	builder->context = context;
+	builder->reorder = reorder;
+	/* Other struct members initialized by zeroing, above */
+
+	/* builder->running is initialized by zeroing, above */
+
+	builder->committed.xcnt = 0;
+	builder->committed.xcnt_space = 128;		/* arbitrary number */
+	builder->committed.xip =
+		palloc0(builder->committed.xcnt_space * sizeof(TransactionId));
+	builder->committed.includes_all_transactions = true;
+	builder->committed.xip =
+		palloc0(builder->committed.xcnt_space * sizeof(TransactionId));
+	builder->initial_xmin_horizon = xmin_horizon;
+	builder->transactions_after = start_lsn;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return builder;
+}
+
+/*
+ * Free a snapshot builder.
+ */
+void
+FreeSnapshotBuilder(SnapBuild *builder)
+{
+	MemoryContext context = builder->context;
+
+	if (builder->snapshot)
+		SnapBuildFreeSnapshot(builder->snapshot);
+
+	if (builder->running.xip)
+		pfree(builder->running.xip);
+
+	if (builder->committed.xip)
+		pfree(builder->committed.xip);
+
+	pfree(builder);
+
+	MemoryContextDelete(context);
+}
+
+/*
+ * Free an unreferenced snapshot that has previously been built by us.
+ */
+static void
+SnapBuildFreeSnapshot(Snapshot snap)
+{
+	/* make sure we don't get passed an external snapshot */
+	Assert(snap->satisfies == HeapTupleSatisfiesMVCCDuringDecoding);
+
+	/* make sure nobody modified our snapshot */
+	Assert(snap->curcid == FirstCommandId);
+	Assert(!snap->suboverflowed);
+	Assert(!snap->takenDuringRecovery);
+	Assert(!snap->regd_count);
+
+	/* slightly more likely, so it's checked even without c-asserts */
+	if (snap->copied)
+		elog(ERROR, "can't free a copied snapshot");
+
+	if (snap->active_count)
+		elog(ERROR, "can't free an active snapshot");
+
+	pfree(snap);
+}
+
+/*
+ * In which state of snapshot building ar we?
+ */
+SnapBuildState
+SnapBuildCurrentState(SnapBuild *builder)
+{
+	return builder->state;
+}
+
+/*
+ * Should the contents of transaction ending at 'ptr' be decoded?
+ */
+bool
+SnapBuildXactNeedsSkip(SnapBuild *builder, XLogRecPtr ptr)
+{
+	return ptr <= builder->transactions_after;
+}
+
+/*
+ * Increase refcount of a snapshot.
+ *
+ * This is used when handing out a snapshot to some external resource or when
+ * adding a Snapshot as builder->snapshot.
+ */
+static void
+SnapBuildSnapIncRefcount(Snapshot snap)
+{
+	snap->active_count++;
+}
+
+/*
+ * Decrease refcount of a snapshot and free if the refcount reaches zero.
+ *
+ * Externally visible so external resources that have been handed an IncRef'ed
+ * Snapshot can free it easily.
+ */
+void
+SnapBuildSnapDecRefcount(Snapshot snap)
+{
+	/* make sure we don't get passed an external snapshot */
+	Assert(snap->satisfies == HeapTupleSatisfiesMVCCDuringDecoding);
+
+	/* make sure nobody modified our snapshot */
+	Assert(snap->curcid == FirstCommandId);
+	Assert(!snap->suboverflowed);
+	Assert(!snap->takenDuringRecovery);
+	Assert(!snap->regd_count);
+
+	Assert(snap->active_count);
+
+	/* slightly more likely, so its checked even without casserts */
+	if (snap->copied)
+		elog(ERROR, "can't free a copied snapshot");
+
+	snap->active_count--;
+	if (!snap->active_count)
+		SnapBuildFreeSnapshot(snap);
+}
+
+/*
+ * Build a new snapshot, based on currently committed catalog-modifying
+ * transactions.
+ *
+ * In-progress transactions with catalog access are *not* allowed to modify
+ * these snapshots; they have to copy them and fill in appropriate ->curcid and
+ * ->subxip/subxcnt values.
+ */
+static Snapshot
+SnapBuildBuildSnapshot(SnapBuild *builder, TransactionId xid)
+{
+	Snapshot	snapshot;
+	Size		ssize;
+
+	Assert(builder->state >= SNAPBUILD_FULL_SNAPSHOT);
+
+	ssize = sizeof(SnapshotData)
+		+ sizeof(TransactionId) * builder->committed.xcnt
+		+ sizeof(TransactionId) * 1 /* toplevel xid */ ;
+
+	snapshot = MemoryContextAllocZero(builder->context, ssize);
+
+	snapshot->satisfies = HeapTupleSatisfiesMVCCDuringDecoding;
+
+	/*
+	 * We misuse the original meaning of SnapshotData's xip and subxip fields
+	 * to make the more fitting for our needs.
+	 *
+	 * In the 'xip' array we store transactions that have to be treated as
+	 * committed. Since we will only ever look at tuples from transactions
+	 * that have modified the catalog its more efficient to store those few
+	 * that exist between xmin and xmax (frequently there are none).
+	 *
+	 * Snapshots that are used in transactions that have modified the catalog
+	 * also use the 'subxip' array to store their toplevel xid and all the
+	 * subtransaction xids so we can recognize when we need to treat rows as
+	 * visible that are not in xip but still need to be visible. Subxip only
+	 * gets filled when the transaction is copied into the context of a
+	 * catalog modifying transaction since we otherwise share a snapshot
+	 * between transactions. As long as a txn hasn't modified the catalog it
+	 * doesn't need to treat any uncommitted rows as visible, so there is no
+	 * need for those xids.
+	 *
+	 * Both arrays are qsort'ed so that we can use bsearch() on them.
+	 *
+	 * XXX: Do we want extra fields instead of misusing existing ones instead?
+	 */
+	Assert(TransactionIdIsNormal(builder->xmin));
+	Assert(TransactionIdIsNormal(builder->xmax));
+
+	snapshot->xmin = builder->xmin;
+	snapshot->xmax = builder->xmax;
+
+	/* store all transactions to be treated as committed by this snapshot */
+	snapshot->xip =
+		(TransactionId *) ((char *) snapshot + sizeof(SnapshotData));
+	snapshot->xcnt = builder->committed.xcnt;
+	memcpy(snapshot->xip,
+		   builder->committed.xip,
+		   builder->committed.xcnt * sizeof(TransactionId));
+
+	/* sort so we can bsearch() */
+	qsort(snapshot->xip, snapshot->xcnt, sizeof(TransactionId), xidComparator);
+
+	/*
+	 * Initially, subxip is empty, i.e. it's a snapshot to be used by
+	 * transactions that don't modify the catalog. Will be filled by
+	 * ReorderBufferCopySnap() if necessary.
+	 */
+	snapshot->subxcnt = 0;
+	snapshot->subxip = NULL;
+
+	snapshot->suboverflowed = false;
+	snapshot->takenDuringRecovery = false;
+	snapshot->copied = false;
+	snapshot->curcid = FirstCommandId;
+	snapshot->active_count = 0;
+	snapshot->regd_count = 0;
+
+	return snapshot;
+}
+
+/*
+ * Export a snapshot so it can be set in another session with SET TRANSACTION
+ * SNAPSHOT.
+ *
+ * For that we need to start a transaction in the current backend as the
+ * importing side checks whether the source transaction is still open to make
+ * sure the xmin horizon hasn't advanced since then.
+ *
+ * After that we convert a locally built snapshot into the normal variant
+ * understood by HeapTupleSatisfiesMVCC et al.
+ */
+const char *
+SnapBuildExportSnapshot(SnapBuild *builder)
+{
+	Snapshot	snap;
+	char	   *snapname;
+	TransactionId xid;
+	TransactionId *newxip;
+	int			newxcnt = 0;
+
+	elog(LOG, "building snapshot");
+
+	if (builder->state != SNAPBUILD_CONSISTENT)
+		elog(ERROR, "cannot export a snapshot before reaching a consistent state");
+
+	if (!builder->committed.includes_all_transactions)
+		elog(ERROR, "cannot export a snapshot, not all transactions are monitored anymore");
+
+	/* so we don't overwrite the existing value */
+	if (TransactionIdIsValid(MyPgXact->xmin))
+		elog(ERROR, "cannot export a snapshot when MyPgXact->xmin already is valid");
+
+	if (IsTransactionOrTransactionBlock())
+		elog(ERROR, "cannot export a snapshot from within a transaction");
+
+	if (SavedResourceOwnerDuringExport)
+		elog(ERROR, "can only export one snapshot at a time");
+
+	SavedResourceOwnerDuringExport = CurrentResourceOwner;
+
+	StartTransactionCommand();
+
+	Assert(!FirstSnapshotSet);
+
+	/* There doesn't seem to a nice API to set these */
+	XactIsoLevel = XACT_REPEATABLE_READ;
+	XactReadOnly = true;
+
+	snap = SnapBuildBuildSnapshot(builder, GetTopTransactionId());
+
+	/*
+	 * We know that snap->xmin is alive, enforced by the logical xmin
+	 * mechanism. Due to that we can do this without locks, we're only
+	 * changing our own value.
+	 */
+	MyPgXact->xmin = snap->xmin;
+
+	/* allocate in transaction context */
+	newxip = (TransactionId *)
+		palloc(sizeof(TransactionId) * GetMaxSnapshotXidCount());
+
+	/*
+	 * snapbuild.c builds transactions in an "inverted" manner, which means it
+	 * stores committed transactions in ->xip, not ones in progress. Build a
+	 * classical snapshot by marking all non-committed transactions as
+	 * in-progress. This can be expensive.
+	 */
+	for (xid = snap->xmin; NormalTransactionIdPrecedes(xid, snap->xmax);)
+	{
+		void	   *test;
+
+		/*
+		 * check whether transaction committed using the timetravel meaning of
+		 * ->xip
+		 */
+		test = bsearch(&xid, snap->xip, snap->xcnt,
+					   sizeof(TransactionId), xidComparator);
+
+		elog(DEBUG2, "checking xid %u.. %d (xmin %u, xmax %u)",
+			 xid, test == NULL, snap->xmin, snap->xmax);
+
+		if (test == NULL)
+		{
+			if (newxcnt >= GetMaxSnapshotXidCount())
+				elog(ERROR, "snapshot too large");
+
+			newxip[newxcnt++] = xid;
+
+			elog(DEBUG2, "treat %u as in-progress", xid);
+		}
+
+		TransactionIdAdvance(xid);
+	}
+
+	snap->xcnt = newxcnt;
+	snap->xip = newxip;
+
+	/*
+	 * now that we've built a plain snapshot, use the normal mechanisms for
+	 * exporting it
+	 */
+	snapname = ExportSnapshot(snap);
+
+	elog(LOG, "exported snapbuild snapshot: %s xcnt %u", snapname, snap->xcnt);
+	return snapname;
+}
+
+/*
+ * Reset a previously SnapBuildExportSnapshot()'ed snapshot if there is
+ * any. Aborts the previously started transaction and resets the resource owner
+ * back to it's original value.
+ */
+void
+SnapBuildClearExportedSnapshot()
+{
+	/* nothing exported, thats the usual case */
+	if (SavedResourceOwnerDuringExport == NULL)
+		return;
+
+	Assert(IsTransactionState());
+
+	/* make sure nothing  could have ever happened */
+	AbortCurrentTransaction();
+
+	CurrentResourceOwner = SavedResourceOwnerDuringExport;
+	SavedResourceOwnerDuringExport = NULL;
+}
+
+/*
+ * Handle the effects of a single heap change, appropriate to the current state
+ * of the snapshot builder and returns whether changes made at (xid, lsn) may
+ * be decoded.
+ */
+bool
+SnapBuildProcessChange(SnapBuild *builder, TransactionId xid, XLogRecPtr lsn)
+{
+	bool is_old_tx;
+
+	/*
+	 * We can't handle data in transactions if we haven't built a snapshot
+	 * yet, so don't store them.
+	 */
+	if (builder->state < SNAPBUILD_FULL_SNAPSHOT)
+		return false;
+
+	/*
+	 * No point in keeping track of changes in transactions that we don't have
+	 * enough information about to decode.
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT &&
+		SnapBuildTxnIsRunning(builder, xid))
+		return false;
+
+	is_old_tx = ReorderBufferIsXidKnown(builder->reorder, xid);
+
+	if (!is_old_tx || !ReorderBufferXidHasBaseSnapshot(builder->reorder, xid))
+	{
+		/* only build a new snapshot if we don't have a prebuilt one */
+		if (builder->snapshot == NULL)
+		{
+			builder->snapshot = SnapBuildBuildSnapshot(builder, xid);
+			/* inrease refcount for the snapshot builder */
+			SnapBuildSnapIncRefcount(builder->snapshot);
+		}
+
+		/* increase refcount for the transaction */
+		SnapBuildSnapIncRefcount(builder->snapshot);
+		ReorderBufferSetBaseSnapshot(builder->reorder, xid, lsn,
+									 builder->snapshot);
+	}
+
+	return true;
+}
+
+/*
+ * Do CommandId/ComboCid handling after reading a xl_heap_new_cid record. This
+ * implies that a transaction has done some for of write to system catalogs.
+ */
+void
+SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
+					   XLogRecPtr lsn, xl_heap_new_cid *xlrec)
+{
+	CommandId	cid;
+
+	/*
+	 * we only log new_cid's if a catalog tuple was modified, so
+	 * set transaction to timetravelling.
+	 */
+	ReorderBufferXidSetTimetravel(builder->reorder, xid,lsn);
+
+	ReorderBufferAddNewTupleCids(builder->reorder, xlrec->top_xid, lsn,
+								 xlrec->target.node, xlrec->target.tid,
+								 xlrec->cmin, xlrec->cmax,
+								 xlrec->combocid);
+
+	/* figure out new command id */
+	if (xlrec->cmin != InvalidCommandId &&
+		xlrec->cmax != InvalidCommandId)
+		cid = Max(xlrec->cmin, xlrec->cmax);
+	else if (xlrec->cmax != InvalidCommandId)
+		cid = xlrec->cmax;
+	else if (xlrec->cmin != InvalidCommandId)
+		cid = xlrec->cmin;
+	else
+	{
+		cid = InvalidCommandId;		/* silence compiler */
+		elog(ERROR, "broken arrow, no cid?");
+	}
+
+	/*
+	 * FIXME: potential race condition here: if multiple snapshots were running
+	 * & generating changes in the same transaction on the source side this
+	 * could be problematic. But this cannot happen for system catalogs, right?
+	 */
+	ReorderBufferAddNewCommandId(builder->reorder, xid, lsn, cid + 1);
+}
+
+/*
+ * Check whether `xid` is currently 'running'. Running transactions in our
+ * parlance are transactions which we didn't observe from the start so we can't
+ * properly decode them. They only exist after we freshly started from an
+ * < CONSISTENT snapshot.
+ */
+static bool
+SnapBuildTxnIsRunning(SnapBuild *builder, TransactionId xid)
+{
+	Assert(builder->state < SNAPBUILD_CONSISTENT);
+	Assert(TransactionIdIsValid(builder->running.xmin));
+	Assert(TransactionIdIsValid(builder->running.xmax));
+
+	if (builder->running.xcnt &&
+		NormalTransactionIdFollows(xid, builder->running.xmin) &&
+		NormalTransactionIdPrecedes(xid, builder->running.xmax))
+	{
+		TransactionId *search =
+		bsearch(&xid, builder->running.xip, builder->running.xcnt_space,
+				sizeof(TransactionId), xidComparator);
+
+		if (search != NULL)
+		{
+			Assert(*search == xid);
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Add a new Snapshot to all transactions we're decoding that currently are
+ * in-progress so they can see new catalog contents made by the transaction
+ * that just committed. This is necessary because those in-progress
+ * transactions will use the new catalog's contents from here on (at the very
+ * least everything they do needs to be compatible with newer catalog contents).
+ */
+static void
+SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn)
+{
+	dlist_iter	txn_i;
+	ReorderBufferTXN *txn;
+
+	/*
+	 * Iterate through all toplevel transactions. This can include
+	 * subtransactions which we just don't yet know to be that, but that's
+	 * fine, they will just get an unneccesary snapshot queued.
+	 */
+	dlist_foreach(txn_i, &builder->reorder->toplevel_by_lsn)
+	{
+		txn = dlist_container(ReorderBufferTXN, node, txn_i.cur);
+
+		Assert(TransactionIdIsValid(txn->xid));
+
+		/*
+		 * If we don't have a base snapshot yet, there are no changes in this
+		 * transaction which in turn implies we don't yet need a snapshot at
+		 * all. We'll add add a snapshot when the first change gets queued.
+		 *
+		 * XXX: is that fine if only a subtransaction has a base snapshot so
+		 * far?
+		 */
+		if (!ReorderBufferXidHasBaseSnapshot(builder->reorder, txn->xid))
+			continue;
+
+		elog(DEBUG2, "adding a new snapshot to %u at %X/%X",
+			 txn->xid, (uint32) (lsn >> 32), (uint32) lsn);
+
+		/* increase refcount for the transaction */
+		SnapBuildSnapIncRefcount(builder->snapshot);
+		ReorderBufferAddSnapshot(builder->reorder, txn->xid, lsn,
+								 builder->snapshot);
+	}
+}
+
+/*
+ * Keep track of a new catalog changing transaction that has committed.
+ */
+static void
+SnapBuildAddCommittedTxn(SnapBuild *builder, TransactionId xid)
+{
+	Assert(TransactionIdIsValid(xid));
+
+	if (builder->committed.xcnt == builder->committed.xcnt_space)
+	{
+		builder->committed.xcnt_space = builder->committed.xcnt_space * 2 + 1;
+
+		/* XXX: put in a limit here as a defense against bugs? */
+
+		elog(DEBUG1, "increasing space for committed transactions to %zu",
+			 builder->committed.xcnt_space);
+
+		builder->committed.xip = repalloc(builder->committed.xip,
+					builder->committed.xcnt_space * sizeof(TransactionId));
+	}
+
+	/*
+	 * XXX: It might make sense to keep the array sorted here instead of doing
+	 * it everytime we build a new snapshot. On the other hand this gets called
+	 * repeatedly when a transaction with subtransactions commits.
+	 */
+	builder->committed.xip[builder->committed.xcnt++] = xid;
+}
+
+/*
+ * Remove knowledge about transactions we treat as committed that are smaller
+ * than ->xmin. Those won't ever get checked via the ->commited array but via
+ * the clog machinery, so we don't need to waste memory on them.
+ */
+static void
+SnapBuildPurgeCommittedTxn(SnapBuild *builder)
+{
+	int			off;
+	TransactionId *workspace;
+	int			surviving_xids = 0;
+
+	/* not ready yet */
+	if (!TransactionIdIsNormal(builder->xmin))
+		return;
+
+	/* XXX: Neater algorithm? */
+	workspace =
+		MemoryContextAlloc(builder->context,
+						   builder->committed.xcnt * sizeof(TransactionId));
+
+	/* copy xids that still are interesting to workspace */
+	for (off = 0; off < builder->committed.xcnt; off++)
+	{
+		if (NormalTransactionIdPrecedes(builder->committed.xip[off],
+										builder->xmin))
+			;					/* remove */
+		else
+			workspace[surviving_xids++] = builder->committed.xip[off];
+	}
+
+	/* copy workspace back to persistent state */
+	memcpy(builder->committed.xip, workspace,
+		   surviving_xids * sizeof(TransactionId));
+
+	elog(DEBUG1, "purged committed transactions from %u to %u, xmin: %u, xmax: %u",
+		 (uint32) builder->committed.xcnt, (uint32) surviving_xids,
+		 builder->xmin, builder->xmax);
+	builder->committed.xcnt = surviving_xids;
+
+	pfree(workspace);
+}
+
+/*
+ * Common logic for SnapBuildAbortTxn and SnapBuildCommitTxn dealing with
+ * keeping track of the amount of running transactions.
+ */
+static void
+SnapBuildEndTxn(SnapBuild *builder, TransactionId xid)
+{
+	if (builder->state == SNAPBUILD_CONSISTENT)
+		return;
+
+	if (SnapBuildTxnIsRunning(builder, xid))
+	{
+		Assert(builder->running.xcnt > 0);
+
+		if (!--builder->running.xcnt)
+		{
+			/*
+			 * None of the originally running transaction is running anymore.
+			 * Due to that our incrementaly built snapshot now is complete.
+			 */
+			elog(LOG, "found consistent point due to SnapBuildEndTxn + running: %u", xid);
+			builder->state = SNAPBUILD_CONSISTENT;
+		}
+	}
+}
+
+/*
+ * Abort a transaction, throw away all state we kept
+ */
+void
+SnapBuildAbortTxn(SnapBuild *builder, TransactionId xid,
+				  int nsubxacts, TransactionId *subxacts)
+{
+	int			i;
+
+	for (i = 0; i < nsubxacts; i++)
+	{
+		TransactionId subxid = subxacts[i];
+
+		SnapBuildEndTxn(builder, subxid);
+	}
+
+	SnapBuildEndTxn(builder, xid);
+}
+
+/*
+ * Handle everything that needs to be done when a transaction commits
+ */
+void
+SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
+				   int nsubxacts, TransactionId *subxacts)
+{
+	int			nxact;
+
+	bool		forced_timetravel = false;
+	bool		sub_does_timetravel = false;
+	bool		top_does_timetravel = false;
+
+	TransactionId xmax = xid;
+
+	/*
+	 * If we couldn't observe every change of a transaction because it was
+	 * already running at the point we started to observe we have to assume it
+	 * made catalog changes.
+	 *
+	 * This has the positive benefit that we afterwards have enough
+	 * information to build an exportable snapshot thats usable by pg_dump et
+	 * al.
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT)
+	{
+		/* ensure that only commits after this are getting replayed */
+		if (builder->transactions_after < lsn)
+			builder->transactions_after = lsn;
+
+		/*
+		 * we could avoid treating !SnapBuildTxnIsRunning transactions as
+		 * timetravel ones, but we want to be able to export a snapshot when
+		 * we reached consistency.
+		 */
+		forced_timetravel = true;
+		elog(DEBUG1, "forced to assume catalog changes for xid %u because it was running to early", xid);
+	}
+
+	for (nxact = 0; nxact < nsubxacts; nxact++)
+	{
+		TransactionId subxid = subxacts[nxact];
+
+		/*
+		 * make sure txn is not tracked in running txn's anymore, switch state
+		 */
+		SnapBuildEndTxn(builder, subxid);
+
+		/*
+		 * If we're forcing timetravel we also need accurate subtransaction
+		 * status.
+		 */
+		if (forced_timetravel)
+		{
+			SnapBuildAddCommittedTxn(builder, subxid);
+			if (NormalTransactionIdFollows(subxid, xmax))
+				xmax = subxid;
+		}
+
+		/*
+		 * add subtransaction to base snapshot, we don't distinguish to
+		 * toplevel transactions there.
+		 */
+		else if (ReorderBufferXidDoesTimetravel(builder->reorder, subxid))
+		{
+			sub_does_timetravel = true;
+
+			elog(DEBUG1, "found subtransaction %u:%u with catalog changes.",
+				 xid, subxid);
+
+			SnapBuildAddCommittedTxn(builder, subxid);
+
+			if (NormalTransactionIdFollows(subxid, xmax))
+				xmax = subxid;
+		}
+	}
+
+	/*
+	 * make sure txn is not tracked in running txn's anymore, switch state
+	 */
+	SnapBuildEndTxn(builder, xid);
+
+	if (forced_timetravel)
+	{
+		elog(DEBUG1, "forced transaction %u to do timetravel.", xid);
+
+		SnapBuildAddCommittedTxn(builder, xid);
+	}
+	/* add toplevel transaction to base snapshot */
+	else if (ReorderBufferXidDoesTimetravel(builder->reorder, xid))
+	{
+		elog(DEBUG1, "found top level transaction %u, with catalog changes!",
+			 xid);
+
+		top_does_timetravel = true;
+		SnapBuildAddCommittedTxn(builder, xid);
+	}
+	else if (sub_does_timetravel)
+	{
+		/* mark toplevel txn as timetravel as well */
+		SnapBuildAddCommittedTxn(builder, xid);
+	}
+
+	if (forced_timetravel || top_does_timetravel || sub_does_timetravel)
+	{
+		if (!TransactionIdIsValid(builder->xmax) ||
+			TransactionIdFollowsOrEquals(xmax, builder->xmax))
+		{
+			builder->xmax = xmax;
+			TransactionIdAdvance(builder->xmax);
+		}
+
+		if (builder->state < SNAPBUILD_FULL_SNAPSHOT)
+			return;
+
+		/* decrease the snapshot builder's refcount of the old snapshot */
+		if (builder->snapshot)
+			SnapBuildSnapDecRefcount(builder->snapshot);
+
+		builder->snapshot = SnapBuildBuildSnapshot(builder, xid);
+
+		/* refcount of the snapshot builder for the new snapshot */
+		SnapBuildSnapIncRefcount(builder->snapshot);
+
+		/* add a new SnapshotNow to all currently running transactions */
+		SnapBuildDistributeNewCatalogSnapshot(builder, lsn);
+	}
+	else
+	{
+		/* record that we cannot export a general snapshot anymore */
+		builder->committed.includes_all_transactions = false;
+	}
+}
+
+
+/* -----------------------------------
+ * Snapshot building functions dealing with xlog records
+ * -----------------------------------
+ */
+void
+SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
+{
+	ReorderBufferTXN *txn;
+
+	if (builder->state < SNAPBUILD_CONSISTENT)
+	{
+		/* returns false if there's no point in performing cleanup just yet */
+		if (!SnapBuildFindSnapshot(builder, lsn, running))
+			return;
+	}
+	else
+	{
+		SnapBuildSerialize(builder, lsn);
+	}
+
+	/*
+	 * update range of interesting xids. We don't increase ->xmax because once
+	 * we are in a consistent state we can do that ourselves and much more
+	 * efficiently so because we only need to do it for catalog transactions.
+	 */
+	builder->xmin = running->oldestRunningXid;
+
+	/*
+	 * xmax can be lower than xmin here because we only increase xmax when we
+	 * hit a transaction with catalog changes. While odd looking, its correct
+	 * and actually more efficient this way since we hit fast paths in tqual.c.
+	 */
+
+	/* Remove transactions we don't need to keep track off anymore */
+	SnapBuildPurgeCommittedTxn(builder);
+
+	elog(DEBUG1, "xmin: %u, xmax: %u, oldestrunning: %u",
+		 builder->xmin, builder->xmax,
+		 running->oldestRunningXid);
+
+	/*
+	 * inrease shared memory state, so vacuum can work on tuples we prevent
+	 * from being pruned till now.
+	 */
+	IncreaseLogicalXminForSlot(lsn, running->oldestRunningXid);
+
+	/*
+	 * Also tell the slot where we can restart decoding from. We don't want to
+	 * do that after every commit because changing that implies an fsync of the
+	 * logical slot's state file, so we only do it everytime we see a running
+	 * xacts record.
+	 *
+	 * Do so by looking for the oldest in progress transaction (determined by
+	 * the first LSN of any of its relevant records). Every transaction
+	 * remembers the last location we stored the snapshot to disk before its
+	 * beginning. That point is where we can restart from.
+	 */
+
+	/*
+	 * Can't know about a serialized snapshot's location if we're not
+	 * consistent
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT)
+		return;
+
+	txn = ReorderBufferGetOldestTXN(builder->reorder);
+
+	/*
+	 * oldest ongoing txn might have started when we didn't yet serialize
+	 * anything because we hadn't reached a consistent state yet.
+	 */
+	if (txn != NULL && txn->restart_decoding_lsn != InvalidXLogRecPtr)
+		IncreaseRestartDecodingForSlot(lsn, txn->restart_decoding_lsn);
+
+	/*
+	 * No in-progress transaction, can reuse the last serialized snapshot if we
+	 * have one.
+	 */
+	else if (txn == NULL &&
+			 builder->reorder->current_restart_decoding_lsn != InvalidXLogRecPtr &&
+			 builder->last_serialized_snapshot != InvalidXLogRecPtr)
+		IncreaseRestartDecodingForSlot(lsn, builder->last_serialized_snapshot);
+}
+
+
+/*
+ * Build the start of a snapshot that's capable of decoding the catalog. Helper
+ * function for SnapBuildProcessRunningXacts() while we're not yet consistent.
+ *
+ * Returns true if there is a point in performing internal maintenance/cleanup
+ * using the xl_running_xacts record.
+ */
+static bool
+SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
+{
+	/* ---
+	 * Build catalog decoding snapshot incrementally using information about
+	 * the currently running transactions. There are several ways to do that:
+
+	 * a) There were no running transactions when the xl_running_xacts record
+	 *    was inserted, jump to CONSISTENT immediately. We might find such a
+	 *    state we were waiting for b) and c).
+
+	 * b) Wait for all toplevel transactions that were running to end. We
+	 *    simply track the number of in-progress toplevel transactions and
+	 *    lower it whenever one commits or aborts. When that number
+	 *    (builder->running.xcnt) reaches zero, we can go from FULL_SNAPSHOT to
+	 *    CONSISTENT.
+	 *	  NB: We need to search running.xip when seeing a transaction's end to
+	 *    make sure it's a toplevel transaction and it's been one of the
+	 *    intially running ones.
+	 *	  Interestingly, in contrast to HS this allows us not to care about
+	 *	  subtransactions - and by extension suboverflowed xl_running_xacts -
+	 *	  at all.
+	 *
+	 * c) This (in a previous run) or another decoding slot serialized a
+	 *    snapshot to disk that we can use.
+	 * ---
+	 */
+
+	/*
+	 * xl_running_xact record is older than what we can use, we might not have
+	 * all necessary catalog rows anymore.
+	 */
+	if (TransactionIdIsNormal(builder->initial_xmin_horizon) &&
+		NormalTransactionIdPrecedes(running->oldestRunningXid,
+									builder->initial_xmin_horizon))
+	{
+		elog(LOG, "skipping snapshot at %X/%X due to initial xmin horizon of %u vs the snapshot's %u",
+			 (uint32) (lsn >> 32), (uint32) lsn,
+			 builder->initial_xmin_horizon, running->oldestRunningXid);
+		return true;
+	}
+
+	/*
+	 * a) No transaction were running, we can jump to consistent.
+	 *
+	 * NB: We might have already started to incrementally assemble a snapshot,
+	 * so we need to be careful to deal with that.
+	 */
+	if (running->xcnt == 0)
+	{
+		if (builder->transactions_after == InvalidXLogRecPtr ||
+			builder->transactions_after < lsn)
+			builder->transactions_after = lsn;
+
+		builder->xmin = running->oldestRunningXid;
+		builder->xmax = running->latestCompletedXid;
+		TransactionIdAdvance(builder->xmax);
+
+		Assert(TransactionIdIsNormal(builder->xmin));
+		Assert(TransactionIdIsNormal(builder->xmax));
+
+		/* no transactions running now */
+		builder->running.xcnt = 0;
+		builder->running.xmin = InvalidTransactionId;
+		builder->running.xmax = InvalidTransactionId;
+
+		/*
+		 * FIXME: abort everything we have stored about running transactions,
+		 * relevant e.g. after a crash.
+		 */
+		builder->state = SNAPBUILD_CONSISTENT;
+
+		elog(LOG, "found initial snapshot (xmin %u) due to running xacts with xcnt == 0",
+			 builder->xmin);
+
+		return false;
+	}
+	/* c) valid on disk state */
+	else if (SnapBuildRestore(builder, lsn))
+	{
+		/* there won't be any state to cleanup */
+		return false;
+	}
+
+	/*
+	 * b) first encounter of a useable xl_running_xacts record. If we had found
+	 * one earlier we would either track running transactions
+	 * (i.e. builder->running.xcnt != 0) or be consistent (this function
+	 * wouldn't get called)..
+	 */
+	else if (!builder->running.xcnt)
+	{
+		/*
+		 * We only care about toplevel xids as those are the ones we definitely
+		 * see in the wal stream. As snapbuild.c tracks committed instead of
+		 * running transactions we don't need to know anything about
+		 * uncommitted subtransactions.
+		 */
+		builder->xmin = running->oldestRunningXid;
+		builder->xmax = running->latestCompletedXid;
+		TransactionIdAdvance(builder->xmax);
+
+		/* so we can safely use the faster comparisons */
+		Assert(TransactionIdIsNormal(builder->xmin));
+		Assert(TransactionIdIsNormal(builder->xmax));
+
+		builder->running.xcnt = running->xcnt;
+		builder->running.xcnt_space = running->xcnt;
+		builder->running.xip =
+			MemoryContextAlloc(builder->context,
+							builder->running.xcnt * sizeof(TransactionId));
+		memcpy(builder->running.xip, running->xids,
+			   builder->running.xcnt * sizeof(TransactionId));
+
+		/* sort so we can do a binary search */
+		qsort(builder->running.xip, builder->running.xcnt,
+			  sizeof(TransactionId), xidComparator);
+
+		builder->running.xmin = builder->running.xip[0];
+		builder->running.xmax = builder->running.xip[running->xcnt - 1];
+
+		/* makes comparisons cheaper later */
+		TransactionIdRetreat(builder->running.xmin);
+		TransactionIdAdvance(builder->running.xmax);
+
+		builder->state = SNAPBUILD_FULL_SNAPSHOT;
+
+		elog(LOG, "found initial snapshot (xmin %u) due to running xacts, %u xacts need to finish",
+			 builder->xmin, (uint32) builder->running.xcnt);
+
+		/* nothing could have built up so far */
+		return false;
+	}
+
+	/*
+	 * We already started to track running xacts and need to wait for all
+	 * in-progress ones to finish. We fall through to the normal processing of
+	 * records so incremental cleanup can be performed.
+	 */
+	return true;
+}
+
+
+/* -----------------------------------
+ * Snapshot serialization support
+ * -----------------------------------
+ */
+
+/*
+ * We store current state of struct SnapBuild on disk in the following manner:
+ *
+ * struct SnapBuildOnDisk;
+ * TransactionId * running.xcnt_space;
+ * TransactionId * committed.xcnt; (*not xcnt_space*)
+ *
+ */
+typedef struct SnapBuildOnDisk
+{
+	uint32		magic;
+	/* how large is the SnapBuildOnDisk including all data in state */
+	Size		size;
+	SnapBuild	builder;
+
+	/* XXX: Should we store a CRC32? */
+
+	/* variable amount of TransactionId's */
+} SnapBuildOnDisk;
+
+#define SNAPBUILD_MAGIC 0x51A1E001
+
+/*
+ * Store/Load a snapshot from disk, depending on the snapshot builder's state.
+ *
+ * Supposed to be used by external (i.e. not snapbuild.c) code that just reada
+ * record that's a potential location for a serialized snapshot.
+ */
+void
+SnapBuildSerializationPoint(SnapBuild *builder, XLogRecPtr lsn)
+{
+	if (builder->state < SNAPBUILD_CONSISTENT)
+		SnapBuildRestore(builder, lsn);
+	else
+		SnapBuildSerialize(builder, lsn);
+}
+
+/*
+ * Serialize the snapshot 'builder' at the location 'lsn' if it hasn't already
+ * been done by another decoding process.
+ */
+static void
+SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
+{
+	Size		needed_size;
+	SnapBuildOnDisk *ondisk;
+	char	   *ondisk_c;
+	int			fd;
+	char		tmppath[MAXPGPATH];
+	char		path[MAXPGPATH];
+	int			ret;
+	struct stat stat_buf;
+
+	needed_size = sizeof(SnapBuildOnDisk) +
+		sizeof(TransactionId) * builder->running.xcnt_space +
+		sizeof(TransactionId) * builder->committed.xcnt;
+
+	Assert(lsn != InvalidXLogRecPtr);
+	Assert(builder->last_serialized_snapshot == InvalidXLogRecPtr ||
+		   builder->last_serialized_snapshot <= lsn);
+
+	/*
+	 * no point in serializing if we cannot continue to work immediately after
+	 * restoring the snapshot
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT)
+		return;
+
+	/*
+	 * FIXME: Timeline handling/naming.
+	 */
+
+	/*
+	 * first check whether some other backend already has written the snapshot
+	 * for this LSN. It's perfectly fine if there's none, so we accept ENOENT
+	 * as a valid state. Everything else is an unexpected error.
+	 */
+	sprintf(path, "pg_llog/snapshots/%X-%X.snap",
+			(uint32) (lsn >> 32), (uint32) lsn);
+
+	ret = stat(path, &stat_buf);
+
+	if (ret != 0 && errno != ENOENT)
+		ereport(ERROR, (errmsg("could not stat snapbuild state file %s", path)));
+	else if (ret == 0)
+	{
+		/*
+		 * somebody else has already serialized to this point, don't overwrite
+		 * but remember location, so we don't need to read old data again.
+		 *
+		 * FIXME: Is it safe to set this as restartpoint below? While we can
+		 * see the file it's not guaranteed to persist after a crash...
+		 */
+		builder->last_serialized_snapshot = lsn;
+		goto out;
+	}
+
+	/*
+	 * there is an obvious race condition here between the time we stat(2) the
+	 * file and us writing the file. But we rename the file into place
+	 * atomically and all files created need to contain the same data anyway,
+	 * so this is perfectly fine, although a bit of a resource waste. Locking
+	 * seems like pointless complication.
+	 */
+	elog(DEBUG1, "serializing snapshot to %s", path);
+
+	/* to make sure only we will write to this tempfile, include pid */
+	sprintf(tmppath, "pg_llog/snapshots/%X-%X.snap.%u.tmp",
+			(uint32) (lsn >> 32), (uint32) lsn, MyProcPid);
+
+	/*
+	 * Unlink temporary file if it already exists, needs to have been before a
+	 * crash/error since we won't enter this function twice from within a
+	 * single decoding slot/backend and the temporary file contains the pid of
+	 * the current process.
+	 */
+	if (unlink(tmppath) != 0 && errno != ENOENT)
+		ereport(ERROR, (errmsg("could not unlink old snapbuild state file %s", path)));
+
+	ondisk = MemoryContextAllocZero(builder->context, needed_size);
+	ondisk_c = ((char *) ondisk) + sizeof(SnapBuildOnDisk);
+	ondisk->magic = SNAPBUILD_MAGIC;
+	ondisk->size = needed_size;
+
+	/* copy state per struct assignment, lalala lazy. */
+	ondisk->builder = *builder;
+
+	/* NULL-ify memory-only data */
+	ondisk->builder.context = NULL;
+	ondisk->builder.snapshot = NULL;
+	ondisk->builder.reorder = NULL;
+
+	/* copy running xacts */
+	memcpy(ondisk_c, builder->running.xip,
+		   sizeof(TransactionId) * builder->running.xcnt_space);
+	ondisk_c += sizeof(TransactionId) * builder->running.xcnt_space;
+
+	/* copy  committed xacts */
+	memcpy(ondisk_c, builder->committed.xip,
+		   sizeof(TransactionId) * builder->committed.xcnt);
+	ondisk_c += sizeof(TransactionId) * builder->committed.xcnt;
+
+	/* we have valid data now, open tempfile and write it there */
+	fd = OpenTransientFile(tmppath,
+						   O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
+						   S_IRUSR | S_IWUSR);
+	if (fd < 0)
+		ereport(ERROR, (errmsg("could not open snapbuild state file %s for writing: %m", path)));
+
+	if ((write(fd, ondisk, needed_size)) != needed_size)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not write to snapbuild state file \"%s\": %m",
+						tmppath)));
+	}
+
+	/*
+	 * fsync the file before renaming so that even if we crash after this we
+	 * have either a fully valid file or nothing.
+	 *
+	 * TODO: Do the fsync() via checkpoints/restartpoints, doing it here has
+	 * some noticeable overhead since it's performed synchronously during
+	 * decoding?
+	 */
+	if (pg_fsync(fd) != 0)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not fsync snapbuild state file \"%s\": %m",
+						tmppath)));
+	}
+
+	CloseTransientFile(fd);
+
+	/*
+	 * We may overwrite the work from some other backend, but that's ok, our
+	 * snapshot is valid as well.
+	 */
+	if (rename(tmppath, path) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename snapbuild state file from \"%s\" to \"%s\": %m",
+						tmppath, path)));
+	}
+
+	/* make sure we persist */
+	fsync_fname(path, false);
+	fsync_fname("pg_llog/snapshots", true);
+
+	/*
+	 * now there's no way we loose the dumped state anymore, remember
+	 * serialization point.
+	 */
+	builder->last_serialized_snapshot = lsn;
+
+out:
+	ReorderBufferSetRestartPoint(builder->reorder,
+								 builder->last_serialized_snapshot);
+}
+
+/*
+ * Restore a snapshot into 'builder' if previously one has been stored at the
+ * location indicated by 'lsn'. Returns true if successfull, false otherwise.
+ */
+static bool
+SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn)
+{
+	SnapBuildOnDisk ondisk;
+	int			fd;
+	char		path[MAXPGPATH];
+	Size		sz;
+
+	/* no point in loading a snapshot if we're already there */
+	if (builder->state == SNAPBUILD_CONSISTENT)
+		return false;
+
+	sprintf(path, "pg_llog/snapshots/%X-%X.snap",
+			(uint32) (lsn >> 32), (uint32) lsn);
+
+	fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+
+	elog(LOG, "restoring snapbuild state from %s", path);
+
+	if (fd < 0 && errno == ENOENT)
+		return false;
+	else if (fd < 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open snapbuild state file %s", path)));
+
+	elog(LOG, "really restoring from %s", path);
+
+	/* read statically sized portion of snapshot */
+	if (read(fd, &ondisk, sizeof(ondisk)) != sizeof(ondisk))
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not read snapbuild file \"%s\": %m",
+						path)));
+	}
+
+	if (ondisk.magic != SNAPBUILD_MAGIC)
+		ereport(ERROR, (errmsg("snapbuild state file has wrong magic %u instead of %u",
+							   ondisk.magic, SNAPBUILD_MAGIC)));
+
+	/* restore running xact information */
+	sz = sizeof(TransactionId) * ondisk.builder.running.xcnt_space;
+	ondisk.builder.running.xip = MemoryContextAlloc(builder->context, sz);
+	if (read(fd, ondisk.builder.running.xip, sz) != sz)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+		errmsg("could not read running xacts from snapbuild file \"%s\": %m",
+			   path)));
+	}
+
+	/* restore running xact information */
+	sz = sizeof(TransactionId) * ondisk.builder.committed.xcnt;
+	ondisk.builder.committed.xip = MemoryContextAlloc(builder->context, sz);
+	if (read(fd, ondisk.builder.committed.xip, sz) != sz)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not read committed xacts from snapbuild file \"%s\": %m",
+						path)));
+	}
+
+	CloseTransientFile(fd);
+
+	/*
+	 * ok, we now have a sensible snapshot here, figure out if it has more
+	 * information than we have.
+	 */
+
+	/*
+	 * We are only interested in consistent snapshots for now, comparing
+	 * whether one imcomplete snapshot is more "advanced" seems to be
+	 * unnecessarily complex.
+	 */
+	if (ondisk.builder.state < SNAPBUILD_CONSISTENT)
+		goto snapshot_not_interesting;
+
+	/*
+	 * Don't use a snapshot that requires an xmin that we cannot guarantee to
+	 * be available.
+	 */
+	if (TransactionIdPrecedes(ondisk.builder.xmin, builder->initial_xmin_horizon))
+		goto snapshot_not_interesting;
+
+	/*
+	 * XXX: transactions_after needs to be updated differently, to be checked
+	 * here
+	 */
+
+	/* ok, we think the snapshot is sensible, copy over everything important */
+	builder->xmin = ondisk.builder.xmin;
+	builder->xmax = ondisk.builder.xmax;
+	builder->state = ondisk.builder.state;
+
+	builder->committed.xcnt = ondisk.builder.committed.xcnt;
+	/* We only allocated/stored xcnt, not xcnt_space xids ! */
+	/* don't overwrite preallocated xip, if we don't have anything here */
+	if (builder->committed.xcnt > 0)
+	{
+		pfree(builder->committed.xip);
+		builder->committed.xcnt_space = ondisk.builder.committed.xcnt;
+		builder->committed.xip = ondisk.builder.committed.xip;
+	}
+	ondisk.builder.committed.xip = NULL;
+
+	builder->running.xcnt = ondisk.builder.committed.xcnt;
+	if (builder->running.xip)
+		pfree(builder->running.xip);
+	builder->running.xcnt_space = ondisk.builder.committed.xcnt_space;
+	builder->running.xip = ondisk.builder.running.xip;
+
+	/* our snapshot is not interesting anymore, build a new one */
+	if (builder->snapshot != NULL)
+	{
+		SnapBuildSnapDecRefcount(builder->snapshot);
+	}
+	builder->snapshot = SnapBuildBuildSnapshot(builder, InvalidTransactionId);
+	SnapBuildSnapIncRefcount(builder->snapshot);
+
+	ReorderBufferSetRestartPoint(builder->reorder, lsn);
+
+	Assert(builder->state == SNAPBUILD_CONSISTENT);
+	elog(LOG, "recovered initial snapshot (xmin %u) from disk",	 builder->xmin);
+
+	return true;
+
+snapshot_not_interesting:
+	if (ondisk.builder.running.xip != NULL)
+		pfree(ondisk.builder.running.xip);
+	if (ondisk.builder.committed.xip != NULL)
+		pfree(ondisk.builder.committed.xip);
+	return false;
+}
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 8c83780..0d64156 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -65,7 +65,7 @@ Node *replication_parse_result;
 }
 
 /* Non-keyword tokens */
-%token <str> SCONST
+%token <str> SCONST IDENT
 %token <uintval> UCONST
 %token <recptr> RECPTR
 
@@ -73,6 +73,9 @@ Node *replication_parse_result;
 %token K_BASE_BACKUP
 %token K_IDENTIFY_SYSTEM
 %token K_START_REPLICATION
+%token K_INIT_LOGICAL_REPLICATION
+%token K_START_LOGICAL_REPLICATION
+%token K_FREE_LOGICAL_REPLICATION
 %token K_TIMELINE_HISTORY
 %token K_LABEL
 %token K_PROGRESS
@@ -82,10 +85,13 @@ Node *replication_parse_result;
 %token K_TIMELINE
 
 %type <node>	command
-%type <node>	base_backup start_replication identify_system timeline_history
+%type <node>	base_backup start_replication start_logical_replication init_logical_replication free_logical_replication identify_system timeline_history
 %type <list>	base_backup_opt_list
 %type <defelt>	base_backup_opt
 %type <uintval>	opt_timeline
+%type <list>	plugin_options plugin_opt_list
+%type <defelt>	plugin_opt_elem
+%type <node>	plugin_opt_arg
 %%
 
 firstcmd: command opt_semicolon
@@ -102,6 +108,9 @@ command:
 			identify_system
 			| base_backup
 			| start_replication
+			| init_logical_replication
+			| start_logical_replication
+			| free_logical_replication
 			| timeline_history
 			;
 
@@ -186,6 +195,67 @@ opt_timeline:
 				| /* nothing */			{ $$ = 0; }
 			;
 
+init_logical_replication:
+			K_INIT_LOGICAL_REPLICATION IDENT IDENT
+				{
+					InitLogicalReplicationCmd *cmd;
+					cmd = makeNode(InitLogicalReplicationCmd);
+					cmd->name = $2;
+					cmd->plugin = $3;
+					$$ = (Node *) cmd;
+				}
+			;
+
+start_logical_replication:
+			K_START_LOGICAL_REPLICATION IDENT RECPTR plugin_options
+				{
+					StartLogicalReplicationCmd *cmd;
+					cmd = makeNode(StartLogicalReplicationCmd);
+					cmd->name = $2;
+					cmd->startpoint = $3;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
+plugin_options:
+			'(' plugin_opt_list ')'			{ $$ = $2; }
+			| /* EMPTY */					{ $$ = NIL; }
+		;
+
+plugin_opt_list:
+			plugin_opt_elem
+				{
+					$$ = list_make1($1);
+				}
+			| plugin_opt_list ',' plugin_opt_elem
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+plugin_opt_elem:
+			IDENT plugin_opt_arg
+				{
+					$$ = makeDefElem($1, $2);
+				}
+		;
+
+plugin_opt_arg:
+			SCONST							{ $$ = (Node *) makeString($1); }
+			| /* EMPTY */					{ $$ = NULL; }
+		;
+
+free_logical_replication:
+			K_FREE_LOGICAL_REPLICATION IDENT
+				{
+					FreeLogicalReplicationCmd *cmd;
+					cmd = makeNode(FreeLogicalReplicationCmd);
+					cmd->name = $2;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * TIMELINE_HISTORY %d
  */
@@ -205,6 +275,7 @@ timeline_history:
 					$$ = (Node *) cmd;
 				}
 			;
+
 %%
 
 #include "repl_scanner.c"
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 3d930f1..2b0f2ff 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "utils/builtins.h"
+#include "parser/scansup.h"
 
 /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
 #undef fprintf
@@ -48,7 +49,7 @@ static void addlitchar(unsigned char ychar);
 %option warn
 %option prefix="replication_yy"
 
-%x xq
+%x xq xd
 
 /* Extended quote
  * xqdouble implements embedded quote, ''''
@@ -57,12 +58,26 @@ xqstart			{quote}
 xqdouble		{quote}{quote}
 xqinside		[^']+
 
+/* Double quote
+ * Allows embedded spaces and other special characters into identifiers.
+ */
+dquote			\"
+xdstart			{dquote}
+xdstop			{dquote}
+xddouble		{dquote}{dquote}
+xdinside		[^"]+
+
 digit			[0-9]+
 hexdigit		[0-9A-Za-z]+
 
 quote			'
 quotestop		{quote}
 
+ident_start		[A-Za-z\200-\377_]
+ident_cont		[A-Za-z\200-\377_0-9\$]
+
+identifier		{ident_start}{ident_cont}*
+
 %%
 
 BASE_BACKUP			{ return K_BASE_BACKUP; }
@@ -74,9 +89,14 @@ PROGRESS			{ return K_PROGRESS; }
 WAL			{ return K_WAL; }
 TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
+INIT_LOGICAL_REPLICATION	{ return K_INIT_LOGICAL_REPLICATION; }
+START_LOGICAL_REPLICATION	{ return K_START_LOGICAL_REPLICATION; }
+FREE_LOGICAL_REPLICATION	{ return K_FREE_LOGICAL_REPLICATION; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 ","				{ return ','; }
 ";"				{ return ';'; }
+"("				{ return '('; }
+")"				{ return ')'; }
 
 [\n]			;
 [\t]			;
@@ -100,20 +120,49 @@ TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 					BEGIN(xq);
 					startlit();
 				}
+
 <xq>{quotestop}	{
 					yyless(1);
 					BEGIN(INITIAL);
 					yylval.str = litbufdup();
 					return SCONST;
 				}
-<xq>{xqdouble} {
+
+<xq>{xqdouble}	{
 					addlitchar('\'');
 				}
+
 <xq>{xqinside}  {
 					addlit(yytext, yyleng);
 				}
 
-<xq><<EOF>>		{ yyerror("unterminated quoted string"); }
+{xdstart}		{
+					BEGIN(xd);
+					startlit();
+				}
+
+<xd>{xdstop}	{
+					int len;
+					yyless(1);
+					BEGIN(INITIAL);
+					yylval.str = litbufdup();
+					len = strlen(yylval.str);
+					truncate_identifier(yylval.str, len, true);
+					return IDENT;
+				}
+
+<xd>{xdinside}  {
+					addlit(yytext, yyleng);
+				}
+
+{identifier}	{
+					int len = strlen(yytext);
+
+					yylval.str = downcase_truncate_identifier(yytext, len, true);
+					return IDENT;
+				}
+
+<xq,xd><<EOF>>	{ yyerror("unterminated quoted string"); }
 
 
 <<EOF>>			{
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 413f0b9..e73f566 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1137,7 +1137,7 @@ XLogWalRcvSendHSFeedback(bool immed)
 	 * everything else has been checked.
 	 */
 	if (hot_standby_feedback)
-		xmin = GetOldestXmin(true, false);
+		xmin = GetOldestXmin(true, true, false, false);
 	else
 		xmin = InvalidTransactionId;
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b00a91a..2187d96 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -45,9 +45,8 @@
 
 #include "access/timeline.h"
 #include "access/transam.h"
-#include "access/xlog_internal.h"
 #include "access/xact.h"
-
+#include "access/xlog_internal.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
@@ -56,6 +55,10 @@
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "replication/basebackup.h"
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalfuncs.h"
+#include "replication/snapbuild.h"
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
@@ -157,6 +160,9 @@ static bool ping_sent = false;
 static bool streamingDoneSending;
 static bool streamingDoneReceiving;
 
+/* Are we there yet? */
+static bool		WalSndCaughtUp = false;
+
 /* Flags set by signal handlers for later service in main loop */
 static volatile sig_atomic_t got_SIGHUP = false;
 static volatile sig_atomic_t walsender_ready_to_stop = false;
@@ -169,24 +175,42 @@ static volatile sig_atomic_t walsender_ready_to_stop = false;
  */
 static volatile sig_atomic_t replication_active = false;
 
+/* XXX reader */
+static MemoryContext decoding_ctx = NULL;
+static MemoryContext old_decoding_ctx = NULL;
+
+static LogicalDecodingContext *logical_decoding_ctx = NULL;
+static XLogRecPtr  logical_startptr = InvalidXLogRecPtr;
+
 /* Signal handlers */
 static void WalSndSigHupHandler(SIGNAL_ARGS);
 static void WalSndXLogSendHandler(SIGNAL_ARGS);
 static void WalSndLastCycleHandler(SIGNAL_ARGS);
 
 /* Prototypes for private functions */
-static void WalSndLoop(void);
+typedef void (*WalSndSendData)(void);
+static void WalSndLoop(WalSndSendData send_data);
 static void InitWalSenderSlot(void);
 static void WalSndKill(int code, Datum arg);
-static void XLogSend(bool *caughtup);
+static void XLogSendPhysical(void);
+static void XLogSendLogical(void);
+static void WalSndDone(WalSndSendData send_data);
 static XLogRecPtr GetStandbyFlushRecPtr(void);
 static void IdentifySystem(void);
 static void StartReplication(StartReplicationCmd *cmd);
+static void InitLogicalReplication(InitLogicalReplicationCmd *cmd);
+static void StartLogicalReplication(StartLogicalReplicationCmd *cmd);
+static void FreeLogicalReplication(FreeLogicalReplicationCmd *cmd);
 static void ProcessStandbyMessage(void);
 static void ProcessStandbyReplyMessage(void);
 static void ProcessStandbyHSFeedbackMessage(void);
 static void ProcessRepliesIfAny(void);
 static void WalSndKeepalive(bool requestReply);
+static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid);
+static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid);
+static void XLogRead(char *buf, XLogRecPtr startptr, Size count);
+
+
 
 
 /* Initialize walsender process before entering the main command loop */
@@ -247,14 +271,13 @@ IdentifySystem(void)
 	char		tli[11];
 	char		xpos[MAXFNAMELEN];
 	XLogRecPtr	logptr;
-	char*        dbname = NULL;
+	char	   *dbname = NULL;
 
 	/*
 	 * 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,
 			 GetSystemIdentifier());
 
@@ -308,22 +331,22 @@ 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");
-	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, "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 */
@@ -335,9 +358,16 @@ IdentifySystem(void)
 	pq_sendbytes(&buf, (char *) tli, strlen(tli));
 	pq_sendint(&buf, strlen(xpos), 4);	/* col3 len */
 	pq_sendbytes(&buf, (char *) xpos, strlen(xpos));
-	pq_sendint(&buf, strlen(dbname), 4);	/* col4 len */
-	pq_sendbytes(&buf, (char *) dbname, strlen(dbname));
-
+	/* 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);
 }
 
@@ -586,7 +616,7 @@ StartReplication(StartReplicationCmd *cmd)
 		/* Main loop of walsender */
 		replication_active = true;
 
-		WalSndLoop();
+		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
 		if (walsender_ready_to_stop)
@@ -653,6 +683,497 @@ StartReplication(StartReplicationCmd *cmd)
 	pq_puttextmessage('C', "START_STREAMING");
 }
 
+static int
+replay_read_page(XLogReaderState* state, XLogRecPtr targetPagePtr, int reqLen,
+				 XLogRecPtr targetRecPtr, char* cur_page, TimeLineID *pageTLI)
+{
+	XLogRecPtr flushptr;
+	int		count;
+
+	flushptr = WalSndWaitForWal(targetPagePtr + reqLen);
+
+	/* more than one block available */
+	if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
+		count = XLOG_BLCKSZ;
+	/* not enough data there */
+	else if (targetPagePtr + reqLen > flushptr)
+		return -1;
+	/* part of the page available */
+	else
+		count = flushptr - targetPagePtr;
+
+	/* FIXME: more sensible/efficient implementation */
+	XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ);
+
+	return count;
+}
+
+/*
+ * Initialize logical replication and wait for an initial consistent point to
+ * start sending changes from.
+ */
+static void
+InitLogicalReplication(InitLogicalReplicationCmd *cmd)
+{
+	const char *slot_name;
+	StringInfoData buf;
+	char		xpos[MAXFNAMELEN];
+	const char *snapshot_name = NULL;
+	LogicalDecodingContext *ctx;
+	XLogRecPtr startptr;
+
+	CheckLogicalReplicationRequirements();
+
+	Assert(!MyLogicalDecodingSlot);
+
+	/* XXX apply sanity checking to slot name? */
+	LogicalDecodingAcquireFreeSlot(cmd->name, cmd->plugin);
+
+	Assert(MyLogicalDecodingSlot);
+
+	decoding_ctx = AllocSetContextCreate(TopMemoryContext,
+										 "decoding context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	old_decoding_ctx = MemoryContextSwitchTo(decoding_ctx);
+
+	/* setup state for XLogReadPage */
+	sendTimeLineIsHistoric = false;
+	sendTimeLine = ThisTimeLineID;
+
+	initStringInfo(&output_message);
+	ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, false, InvalidXLogRecPtr,
+									   NIL,	replay_read_page,
+									   WalSndPrepareWrite, WalSndWriteData);
+
+	MemoryContextSwitchTo(old_decoding_ctx);
+
+	startptr = MyLogicalDecodingSlot->restart_decoding;
+
+	elog(WARNING, "Initiating logical rep from %X/%X",
+		 (uint32)(startptr >> 32), (uint32)startptr);
+
+	for (;;)
+	{
+		XLogRecord *record;
+		XLogRecordBuffer buf;
+		char *err = NULL;
+
+		/* the read_page callback waits for new WAL */
+		record = XLogReadRecord(ctx->reader, startptr, &err);
+		/* xlog record was invalid */
+		if (err)
+			elog(ERROR, "%s", err);
+
+		/* read up from last position next time round */
+		startptr = InvalidXLogRecPtr;
+
+		Assert(record);
+
+		buf.origptr = ctx->reader->ReadRecPtr;
+		buf.endptr = ctx->reader->EndRecPtr;
+		buf.record = *record;
+		buf.record_data = XLogRecGetData(record);
+		DecodeRecordIntoReorderBuffer(ctx, &buf);
+
+		/* only continue till we found a consistent spot */
+		if (LogicalDecodingContextReady(ctx))
+		{
+			/* export plain, importable, snapshot to the user */
+			snapshot_name = SnapBuildExportSnapshot(ctx->snapshot_builder);
+			break;
+		}
+	}
+
+	MyLogicalDecodingSlot->confirmed_flush = ctx->reader->EndRecPtr;
+	slot_name = NameStr(MyLogicalDecodingSlot->name);
+	snprintf(xpos, sizeof(xpos), "%X/%X",
+			 (uint32) (MyLogicalDecodingSlot->confirmed_flush >> 32),
+			 (uint32) MyLogicalDecodingSlot->confirmed_flush);
+
+	pq_beginmessage(&buf, 'T');
+	pq_sendint(&buf, 4, 2);		/* 4 fields */
+
+	/* first field */
+	pq_sendstring(&buf, "replication_id");	/* 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_sendstring(&buf, "consistent_point");	/* 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_sendstring(&buf, "snapshot_name");	/* 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_sendstring(&buf, "plugin");	/* 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, 4, 2);		/* # of columns */
+
+	/* replication_id */
+	pq_sendint(&buf, strlen(slot_name), 4); /* col1 len */
+	pq_sendbytes(&buf, slot_name, strlen(slot_name));
+
+	/* consistent wal location */
+	pq_sendint(&buf, strlen(xpos), 4); /* col2 len */
+	pq_sendbytes(&buf, xpos, strlen(xpos));
+
+	/* snapshot name */
+	pq_sendint(&buf, strlen(snapshot_name), 4); /* col3 len */
+	pq_sendbytes(&buf, snapshot_name, strlen(snapshot_name));
+
+	/* plugin */
+	pq_sendint(&buf, strlen(cmd->plugin), 4); /* col4 len */
+	pq_sendbytes(&buf, cmd->plugin, strlen(cmd->plugin));
+
+	pq_endmessage(&buf);
+
+	/*
+	 * release active status again, START_LOGICAL_REPLICATION will reacquire it
+	 */
+	LogicalDecodingReleaseSlot();
+}
+
+/*
+ * Load previously initiated logical slot and prepare for sending data (via
+ * WalSndLoop).
+ */
+static void
+StartLogicalReplication(StartLogicalReplicationCmd *cmd)
+{
+	StringInfoData buf;
+	XLogRecPtr confirmed_flush;
+
+	elog(WARNING, "Starting logical replication from %x/%x",
+		 (uint32)(cmd->startpoint >> 32), (uint32)cmd->startpoint);
+
+	/* make sure that our requirements are still fulfilled */
+	CheckLogicalReplicationRequirements();
+
+	Assert(!MyLogicalDecodingSlot);
+
+	LogicalDecodingReAcquireSlot(cmd->name);
+
+	if (am_cascading_walsender && !RecoveryInProgress())
+	{
+		ereport(LOG,
+				(errmsg("terminating walsender process to force cascaded standby to update timeline and reconnect")));
+		walsender_ready_to_stop = true;
+	}
+
+	WalSndSetState(WALSNDSTATE_CATCHUP);
+
+	/* Send a CopyBothResponse message, and start streaming */
+	pq_beginmessage(&buf, 'W');
+	pq_sendbyte(&buf, 0);
+	pq_sendint(&buf, 0, 2);
+	pq_endmessage(&buf);
+	pq_flush();
+
+	/* setup state for XLogReadPage */
+	sendTimeLineIsHistoric = false;
+	sendTimeLine = ThisTimeLineID;
+
+	confirmed_flush = MyLogicalDecodingSlot->confirmed_flush;
+
+	Assert(confirmed_flush != InvalidXLogRecPtr);
+
+	/* continue from last position */
+	if (cmd->startpoint == InvalidXLogRecPtr)
+		cmd->startpoint = MyLogicalDecodingSlot->confirmed_flush;
+	else if (cmd->startpoint > MyLogicalDecodingSlot->confirmed_flush)
+		elog(ERROR, "cannot stream from %X/%X, minimum is %X/%X",
+			 (uint32)(cmd->startpoint >> 32), (uint32)cmd->startpoint,
+			 (uint32)(confirmed_flush >> 32), (uint32)confirmed_flush);
+
+	/*
+	 * Initialize position to the last ack'ed one, then the xlog records begin
+	 * to be shipped from that position.
+	 */
+	logical_decoding_ctx = CreateLogicalDecodingContext(
+		MyLogicalDecodingSlot, false, cmd->startpoint, cmd->options,
+		replay_read_page, WalSndPrepareWrite, WalSndWriteData);
+
+	/*
+	 * XXX: For feedback purposes it would be nicer to set sentPtr to
+	 * cmd->startpoint, but we use it to know where to read xlog in the main
+	 * loop...
+	 */
+	sentPtr = MyLogicalDecodingSlot->restart_decoding;
+	logical_startptr = sentPtr;
+
+	/* Also update the start position status in shared memory */
+	{
+		/* use volatile pointer to prevent code rearrangement */
+		volatile WalSnd *walsnd = MyWalSnd;
+
+		SpinLockAcquire(&walsnd->mutex);
+		walsnd->sentPtr = MyLogicalDecodingSlot->restart_decoding;
+		SpinLockRelease(&walsnd->mutex);
+	}
+
+	elog(LOG, "starting to decode from %X/%X, replay %X/%X",
+		 (uint32)(MyWalSnd->sentPtr >> 32), (uint32)MyWalSnd->sentPtr,
+		 (uint32)(cmd->startpoint >> 32), (uint32)cmd->startpoint);
+
+	replication_active = true;
+
+	SyncRepInitConfig();
+
+	/* Main loop of walsender */
+	WalSndLoop(XLogSendLogical);
+
+	LogicalDecodingReleaseSlot();
+
+	replication_active = false;
+	if (walsender_ready_to_stop)
+		proc_exit(0);
+	WalSndSetState(WALSNDSTATE_STARTUP);
+
+	/* Get out of COPY mode (CommandComplete). */
+	EndCommand("COPY 0", DestRemote);
+}
+
+/*
+ * Free permanent state by a now inactive but defined logical slot.
+ */
+static void
+FreeLogicalReplication(FreeLogicalReplicationCmd *cmd)
+{
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingFreeSlot(cmd->name);
+	EndCommand("FREE_LOGICAL_REPLICATION", DestRemote);
+}
+
+/*
+ * LogicalDecodingContext 'prepare_write' callback.
+ *
+ * Prepare a write into a StringInfo.
+ *
+ * Don't do anything lasting in here, it's quite possible that nothing will done
+ * with the data.
+ */
+static void
+WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	AssertVariableIsOfType(&WalSndPrepareWrite, LogicalOutputPluginWriterPrepareWrite);
+
+	resetStringInfo(ctx->out);
+
+	pq_sendbyte(ctx->out, 'w');
+	pq_sendint64(ctx->out, lsn);	/* dataStart */
+	/* XXX: overwrite when data is assembled */
+	pq_sendint64(ctx->out, lsn);	/* walEnd */
+	/* XXX: gather that value later just as it's done in XLogSendPhysical */
+	pq_sendint64(ctx->out, 0 /*GetCurrentIntegerTimestamp() */);/* sendtime */
+}
+
+/*
+ * LogicalDecodingContext 'write' callback.
+ *
+ * Actually write out data previously prepared by WalSndPrepareWrite out to the
+ * network, take as long as needed but process replies from the other side
+ * during that.
+ */
+static void
+WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	AssertVariableIsOfType(&WalSndWriteData, LogicalOutputPluginWriterWrite);
+
+	/* output previously gathered data in a CopyData packet */
+	pq_putmessage_noblock('d', ctx->out->data, ctx->out->len);
+
+	/* fast path */
+	/* Try to flush pending output to the client */
+	if (pq_flush_if_writable() != 0)
+		return;
+
+	if (!pq_is_send_pending())
+		return;
+
+	for (;;)
+	{
+		int			wakeEvents;
+		long		sleeptime = 10000;		/* 10s */
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (!PostmasterIsAlive())
+			exit(1);
+
+		/* Process any requests or signals received recently */
+		if (got_SIGHUP)
+		{
+			got_SIGHUP = false;
+			ProcessConfigFile(PGC_SIGHUP);
+			SyncRepInitConfig();
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Check for input from the client */
+		ProcessRepliesIfAny();
+
+		/* Clear any already-pending wakeups */
+		ResetLatch(&MyWalSnd->latch);
+
+		/* Try to flush pending output to the client */
+		if (pq_flush_if_writable() != 0)
+			break;
+
+		/* If we finished clearing the buffered data, we're done here. */
+		if (!pq_is_send_pending())
+			break;
+
+		/*
+		 * Note we don't set a timeout here.  It would be pointless, because
+		 * if the socket is not writable there's not much we can do elsewhere
+		 * anyway.
+		 */
+		wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH |
+			WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE | WL_TIMEOUT;
+
+		ImmediateInterruptOK = true;
+		CHECK_FOR_INTERRUPTS();
+		WaitLatchOrSocket(&MyWalSnd->latch, wakeEvents,
+						  MyProcPort->sock, sleeptime);
+		ImmediateInterruptOK = false;
+	}
+
+	/* reactivate latch so WalSndLoop knows to continue */
+	SetLatch(&MyWalSnd->latch);
+}
+
+/*
+ * Wait till WAL < loc is flushed to disk so it can be safely read.
+ */
+XLogRecPtr
+WalSndWaitForWal(XLogRecPtr loc)
+{
+	int			wakeEvents;
+	XLogRecPtr  flushptr;
+
+	/* fast path if everything is there already */
+	/*
+	 * XXX: introduce RecentFlushPtr to avoid acquiring the spinlock in the
+	 * fast path case where we already know we have enough WAL available.
+	 */
+	flushptr = GetFlushRecPtr();
+	if (loc <= flushptr)
+		return flushptr;
+
+	for (;;)
+	{
+		long		sleeptime = 10000;		/* 10 s */
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (!PostmasterIsAlive())
+			exit(1);
+
+		/* Process any requests or signals received recently */
+		if (got_SIGHUP)
+		{
+			got_SIGHUP = false;
+			ProcessConfigFile(PGC_SIGHUP);
+			SyncRepInitConfig();
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Check for input from the client */
+		ProcessRepliesIfAny();
+
+		/* Clear any already-pending wakeups */
+		ResetLatch(&MyWalSnd->latch);
+
+		/* Update our idea of flushed position. */
+		flushptr = GetFlushRecPtr();
+
+		/* If postmaster asked us to stop, don't wait here anymore */
+		if (walsender_ready_to_stop)
+			break;
+
+		/* check whether we're done */
+		if (loc <= flushptr)
+			break;
+
+		/* Determine time until replication timeout */
+		if (wal_sender_timeout > 0)
+		{
+			if (!ping_sent)
+			{
+				TimestampTz timeout;
+
+				/*
+				 * If half of wal_sender_timeout has lapsed without receiving
+				 * any reply from standby, send a keep-alive message to standby
+				 * requesting an immediate reply.
+				 */
+				timeout = TimestampTzPlusMilliseconds(last_reply_timestamp,
+													  wal_sender_timeout / 2);
+				if (GetCurrentTimestamp() >= timeout)
+				{
+					WalSndKeepalive(true);
+					ping_sent = true;
+					/* Try to flush pending output to the client */
+					if (pq_flush_if_writable() != 0)
+						break;
+				}
+			}
+
+			sleeptime = 1 + (wal_sender_timeout / 10);
+		}
+
+		wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH |
+			WL_SOCKET_READABLE | WL_TIMEOUT;
+
+		ImmediateInterruptOK = true;
+		CHECK_FOR_INTERRUPTS();
+		WaitLatchOrSocket(&MyWalSnd->latch, wakeEvents,
+						  MyProcPort->sock, sleeptime);
+		ImmediateInterruptOK = false;
+
+		/*
+		 * The equivalent code in WalSndLoop checks here that replication
+		 * timeout hasn't been exceeded.  We don't do that here.   XXX explain
+		 * why.
+		 */
+	}
+
+	/* reactivate latch so WalSndLoop knows to continue */
+	SetLatch(&MyWalSnd->latch);
+	return flushptr;
+}
+
 /*
  * Execute an incoming replication command.
  */
@@ -664,6 +1185,12 @@ exec_replication_command(const char *cmd_string)
 	MemoryContext cmd_context;
 	MemoryContext old_context;
 
+	/*
+	 * INIT_LOGICAL_REPLICATION exports a snapshot until the next command
+	 * arrives. Clean up the old stuff if there's anything.
+	 */
+	SnapBuildClearExportedSnapshot();
+
 	elog(DEBUG1, "received replication command: %s", cmd_string);
 
 	CHECK_FOR_INTERRUPTS();
@@ -695,6 +1222,18 @@ exec_replication_command(const char *cmd_string)
 			StartReplication((StartReplicationCmd *) cmd_node);
 			break;
 
+		case T_InitLogicalReplicationCmd:
+			InitLogicalReplication((InitLogicalReplicationCmd *) cmd_node);
+			break;
+
+		case T_StartLogicalReplicationCmd:
+			StartLogicalReplication((StartLogicalReplicationCmd *) cmd_node);
+			break;
+
+		case T_FreeLogicalReplicationCmd:
+			FreeLogicalReplication((FreeLogicalReplicationCmd *) cmd_node);
+			break;
+
 		case T_BaseBackupCmd:
 			SendBaseBackup((BaseBackupCmd *) cmd_node);
 			break;
@@ -904,6 +1443,12 @@ ProcessStandbyReplyMessage(void)
 		SpinLockRelease(&walsnd->mutex);
 	}
 
+	/*
+	 * Advance our local xmin horizon when the client confirmed a flush.
+	 */
+	if (MyLogicalDecodingSlot && flushPtr != InvalidXLogRecPtr)
+		LogicalConfirmReceivedLocation(flushPtr);
+
 	if (!am_cascading_walsender)
 		SyncRepReleaseWaiters();
 }
@@ -988,10 +1533,8 @@ ProcessStandbyHSFeedbackMessage(void)
 
 /* Main loop of walsender process that streams the WAL over Copy messages. */
 static void
-WalSndLoop(void)
+WalSndLoop(WalSndSendData send_data)
 {
-	bool		caughtup = false;
-
 	/*
 	 * Allocate buffers that will be used for each outgoing and incoming
 	 * message.  We do this just once to reduce palloc overhead.
@@ -1043,21 +1586,21 @@ WalSndLoop(void)
 
 		/*
 		 * If we don't have any pending data in the output buffer, try to send
-		 * some more.  If there is some, we don't bother to call XLogSend
+		 * some more.  If there is some, we don't bother to call send_data
 		 * again until we've flushed it ... but we'd better assume we are not
 		 * caught up.
 		 */
 		if (!pq_is_send_pending())
-			XLogSend(&caughtup);
+			send_data();
 		else
-			caughtup = false;
+			WalSndCaughtUp = false;
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			goto send_failure;
 
 		/* If nothing remains to be sent right now ... */
-		if (caughtup && !pq_is_send_pending())
+		if (WalSndCaughtUp && !pq_is_send_pending())
 		{
 			/*
 			 * If we're in catchup state, move to streaming.  This is an
@@ -1083,29 +1626,17 @@ WalSndLoop(void)
 			 * the walsender is not sure which.
 			 */
 			if (walsender_ready_to_stop)
-			{
-				/* ... let's just be real sure we're caught up ... */
-				XLogSend(&caughtup);
-				if (caughtup && sentPtr == MyWalSnd->flush &&
-					!pq_is_send_pending())
-				{
-					/* Inform the standby that XLOG streaming is done */
-					EndCommand("COPY 0", DestRemote);
-					pq_flush();
-
-					proc_exit(0);
-				}
-			}
+				WalSndDone(send_data);
 		}
 
 		/*
 		 * We don't block if not caught up, unless there is unsent data
 		 * pending in which case we'd better block until the socket is
-		 * write-ready.  This test is only needed for the case where XLogSend
+		 * write-ready.  This test is only needed for the case where send_data
 		 * loaded a subset of the available data but then pq_flush_if_writable
 		 * flushed it all --- we should immediately try to send more.
 		 */
-		if ((caughtup && !streamingDoneSending) || pq_is_send_pending())
+		if ((WalSndCaughtUp && !streamingDoneSending) || pq_is_send_pending())
 		{
 			TimestampTz timeout = 0;
 			long		sleeptime = 10000;		/* 10 s */
@@ -1434,15 +1965,17 @@ retry:
 }
 
 /*
+ * Send out the WAL in its normal physical/stored form.
+ *
  * Read up to MAX_SEND_SIZE bytes of WAL that's been flushed to disk,
  * but not yet sent to the client, and buffer it in the libpq output
  * buffer.
  *
- * If there is no unsent WAL remaining, *caughtup is set to true, otherwise
- * *caughtup is set to false.
+ * If there is no unsent WAL remaining, WalSndCaughtUp is set to true,
+ * otherwise WalSndCaughtUp is set to false.
  */
 static void
-XLogSend(bool *caughtup)
+XLogSendPhysical(void)
 {
 	XLogRecPtr	SendRqstPtr;
 	XLogRecPtr	startptr;
@@ -1451,7 +1984,7 @@ XLogSend(bool *caughtup)
 
 	if (streamingDoneSending)
 	{
-		*caughtup = true;
+		WalSndCaughtUp = true;
 		return;
 	}
 
@@ -1568,7 +2101,7 @@ XLogSend(bool *caughtup)
 		pq_putmessage_noblock('c', NULL, 0);
 		streamingDoneSending = true;
 
-		*caughtup = true;
+		WalSndCaughtUp = true;
 
 		elog(DEBUG1, "walsender reached end of timeline at %X/%X (sent up to %X/%X)",
 			 (uint32) (sendTimeLineValidUpto >> 32), (uint32) sendTimeLineValidUpto,
@@ -1580,7 +2113,7 @@ XLogSend(bool *caughtup)
 	Assert(sentPtr <= SendRqstPtr);
 	if (SendRqstPtr <= sentPtr)
 	{
-		*caughtup = true;
+		WalSndCaughtUp = true;
 		return;
 	}
 
@@ -1604,15 +2137,15 @@ XLogSend(bool *caughtup)
 	{
 		endptr = SendRqstPtr;
 		if (sendTimeLineIsHistoric)
-			*caughtup = false;
+			WalSndCaughtUp = false;
 		else
-			*caughtup = true;
+			WalSndCaughtUp = true;
 	}
 	else
 	{
 		/* round down to page boundary. */
 		endptr -= (endptr % XLOG_BLCKSZ);
-		*caughtup = false;
+		WalSndCaughtUp = false;
 	}
 
 	nbytes = endptr - startptr;
@@ -1673,6 +2206,96 @@ XLogSend(bool *caughtup)
 }
 
 /*
+ * Send out the WAL after it being decoded into a logical format by the output
+ * plugin specified in INIT_LOGICAL_DECODING
+ */
+static void
+XLogSendLogical(void)
+{
+	XLogRecord *record;
+	char	   *errm;
+
+	if (decoding_ctx == NULL)
+	{
+		decoding_ctx = AllocSetContextCreate(TopMemoryContext,
+											 "decoding context",
+											 ALLOCSET_DEFAULT_MINSIZE,
+											 ALLOCSET_DEFAULT_INITSIZE,
+											 ALLOCSET_DEFAULT_MAXSIZE);
+	}
+
+	record = XLogReadRecord(logical_decoding_ctx->reader, logical_startptr, &errm);
+	logical_startptr = InvalidXLogRecPtr;
+
+	/* xlog record was invalid */
+	if (errm != NULL)
+		elog(ERROR, "%s", errm);
+
+	if (record != NULL)
+	{
+		XLogRecordBuffer buf;
+
+		buf.origptr = logical_decoding_ctx->reader->ReadRecPtr;
+		buf.endptr = logical_decoding_ctx->reader->EndRecPtr;
+		buf.record = *record;
+		buf.record_data = XLogRecGetData(record);
+
+		old_decoding_ctx = MemoryContextSwitchTo(decoding_ctx);
+
+		DecodeRecordIntoReorderBuffer(logical_decoding_ctx, &buf);
+
+		MemoryContextSwitchTo(old_decoding_ctx);
+
+		/*
+		 * If the record we just read is at or beyond the flushed point, then
+		 * we're caught up.
+		 */
+		WalSndCaughtUp =
+			logical_decoding_ctx->reader->EndRecPtr >= GetFlushRecPtr();
+	}
+	else
+		/*
+		 * xlogreader failed, and no error was reported? we must be caught up.
+		 */
+		WalSndCaughtUp = true;
+
+	/* Update shared memory status */
+	{
+		/* use volatile pointer to prevent code rearrangement */
+		volatile WalSnd *walsnd = MyWalSnd;
+
+		SpinLockAcquire(&walsnd->mutex);
+		walsnd->sentPtr = logical_decoding_ctx->reader->ReadRecPtr;
+		SpinLockRelease(&walsnd->mutex);
+	}
+}
+
+/*
+ * The sender is caught up, so we can go away for shutdown processing
+ * to finish normally.  (This should only be called when the shutdown
+ * signal has been received from postmaster.)
+ *
+ * Note that if while doing this we determine that there's still more
+ * data to send, this function will return control to the caller.
+ */
+static void
+WalSndDone(WalSndSendData send_data)
+{
+	/* ... let's just be real sure we're caught up ... */
+	send_data();
+
+	if (WalSndCaughtUp && sentPtr == MyWalSnd->flush &&
+		!pq_is_send_pending())
+	{
+		/* Inform the standby that XLOG streaming is done */
+		EndCommand("COPY 0", DestRemote);
+		pq_flush();
+
+		proc_exit(0);
+	}
+}
+
+/*
  * Returns the latest point in WAL that has been safely flushed to disk, and
  * can be sent to the standby. This should only be called when in recovery,
  * ie. we're streaming to a cascaded standby.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index a0b741b..71d8f04 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -27,6 +27,7 @@
 #include "postmaster/bgworker_internals.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/postmaster.h"
+#include "replication/logical.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
@@ -124,6 +125,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, ProcSignalShmemSize());
 		size = add_size(size, CheckpointerShmemSize());
 		size = add_size(size, AutoVacuumShmemSize());
+		size = add_size(size, LogicalDecodingShmemSize());
 		size = add_size(size, WalSndShmemSize());
 		size = add_size(size, WalRcvShmemSize());
 		size = add_size(size, BTreeShmemSize());
@@ -230,6 +232,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	ProcSignalShmemInit();
 	CheckpointerShmemInit();
 	AutoVacuumShmemInit();
+	LogicalDecodingShmemInit();
 	WalSndShmemInit();
 	WalRcvShmemInit();
 
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c2f86ff..11aa1f5 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -51,6 +51,9 @@
 #include "access/xact.h"
 #include "access/twophase.h"
 #include "miscadmin.h"
+#include "replication/logical.h"
+#include "replication/walsender.h"
+#include "replication/walsender_private.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
@@ -1141,16 +1144,18 @@ TransactionIdIsActive(TransactionId xid)
  * GetOldestXmin() move backwards, with no consequences for data integrity.
  */
 TransactionId
-GetOldestXmin(bool allDbs, bool ignoreVacuum)
+GetOldestXmin(bool allDbs, bool ignoreVacuum, bool systable, bool alreadyLocked)
 {
 	ProcArrayStruct *arrayP = procArray;
 	TransactionId result;
 	int			index;
+	volatile TransactionId logical_xmin = InvalidTransactionId;
 
 	/* Cannot look for individual databases during recovery */
 	Assert(allDbs || !RecoveryInProgress());
 
-	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (!alreadyLocked)
+		LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
 	 * We initialize the MIN() calculation with latestCompletedXid + 1. This
@@ -1197,6 +1202,10 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 		}
 	}
 
+	/* fetch into volatile var while ProcArrayLock is held */
+	if (max_logical_slots > 0)
+		logical_xmin = LogicalDecodingCtl->xmin;
+
 	if (RecoveryInProgress())
 	{
 		/*
@@ -1205,7 +1214,8 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 		 */
 		TransactionId kaxmin = KnownAssignedXidsGetOldestXmin();
 
-		LWLockRelease(ProcArrayLock);
+		if (!alreadyLocked)
+			LWLockRelease(ProcArrayLock);
 
 		if (TransactionIdIsNormal(kaxmin) &&
 			TransactionIdPrecedes(kaxmin, result))
@@ -1213,10 +1223,8 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 	}
 	else
 	{
-		/*
-		 * No other information needed, so release the lock immediately.
-		 */
-		LWLockRelease(ProcArrayLock);
+		if (!alreadyLocked)
+			LWLockRelease(ProcArrayLock);
 
 		/*
 		 * Compute the cutoff XID by subtracting vacuum_defer_cleanup_age,
@@ -1237,6 +1245,15 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 			result = FirstNormalTransactionId;
 	}
 
+	/*
+	 * after locks are released and defer_cleanup_age has been applied, check
+	 * whether we need to back up further to make logical decoding possible.
+	 */
+	if (systable &&
+		TransactionIdIsValid(logical_xmin) &&
+		NormalTransactionIdPrecedes(logical_xmin, result))
+		result = logical_xmin;
+
 	return result;
 }
 
@@ -1290,7 +1307,9 @@ GetMaxSnapshotSubxidCount(void)
  *			older than this are known not running any more.
  *		RecentGlobalXmin: the global xmin (oldest TransactionXmin across all
  *			running transactions, except those running LAZY VACUUM).  This is
- *			the same computation done by GetOldestXmin(true, true).
+ *			the same computation done by GetOldestXmin(true, true, ...).
+ *		RecentGlobalDataXmin: the global xmin for non-catalog tables
+ *			>= RecentGlobalXmin
  *
  * Note: this function should probably not be called with an argument that's
  * not statically allocated (see xip allocation below).
@@ -1306,6 +1325,7 @@ GetSnapshotData(Snapshot snapshot)
 	int			count = 0;
 	int			subcount = 0;
 	bool		suboverflowed = false;
+	volatile TransactionId logical_xmin = InvalidTransactionId;
 
 	Assert(snapshot != NULL);
 
@@ -1483,8 +1503,14 @@ GetSnapshotData(Snapshot snapshot)
 			suboverflowed = true;
 	}
 
+
+	/* fetch into volatile var while ProcArrayLock is held */
+	if (max_logical_slots > 0)
+		logical_xmin = LogicalDecodingCtl->xmin;
+
 	if (!TransactionIdIsValid(MyPgXact->xmin))
 		MyPgXact->xmin = TransactionXmin = xmin;
+
 	LWLockRelease(ProcArrayLock);
 
 	/*
@@ -1499,6 +1525,17 @@ GetSnapshotData(Snapshot snapshot)
 	RecentGlobalXmin = globalxmin - vacuum_defer_cleanup_age;
 	if (!TransactionIdIsNormal(RecentGlobalXmin))
 		RecentGlobalXmin = FirstNormalTransactionId;
+
+	/* Non-catalog tables can be vacuumed if older than this xid */
+	RecentGlobalDataXmin = RecentGlobalXmin;
+
+	/*
+	 * peg the global xmin to the one required for logical decoding if required
+	 */
+	if (TransactionIdIsNormal(logical_xmin) &&
+		NormalTransactionIdPrecedes(logical_xmin, RecentGlobalXmin))
+		RecentGlobalXmin = logical_xmin;
+
 	RecentXmin = xmin;
 
 	snapshot->xmin = xmin;
@@ -1599,9 +1636,11 @@ ProcArrayInstallImportedXmin(TransactionId xmin, TransactionId sourcexid)
  * Similar to GetSnapshotData but returns more information. We include
  * all PGXACTs with an assigned TransactionId, even VACUUM processes.
  *
- * We acquire XidGenLock, but the caller is responsible for releasing it.
- * This ensures that no new XIDs enter the proc array until the caller has
- * WAL-logged this snapshot, and releases the lock.
+ * We acquire XidGenLock and ProcArrayLock, but the caller is responsible for
+ * releasing them. Acquiring XidGenLock ensures that no new XIDs enter the proc
+ * array until the caller has WAL-logged this snapshot, and releases the
+ * lock. Acquiring ProcArrayLock ensures that no transactions commit until the
+ * lock is released.
  *
  * The returned data structure is statically allocated; caller should not
  * modify it, and must not assume it is valid past the next call.
@@ -1736,6 +1775,12 @@ GetRunningTransactionData(void)
 		}
 	}
 
+	/*
+	 * Its important *not* to track decoding tasks here because snapbuild.c
+	 * uses ->oldestRunningXid to manage its xmin. If it were to be included
+	 * here the initial value could never increase.
+	 */
+
 	CurrentRunningXacts->xcnt = count - subcount;
 	CurrentRunningXacts->subxcnt = subcount;
 	CurrentRunningXacts->subxid_overflow = suboverflowed;
@@ -1743,13 +1788,12 @@ GetRunningTransactionData(void)
 	CurrentRunningXacts->oldestRunningXid = oldestRunningXid;
 	CurrentRunningXacts->latestCompletedXid = latestCompletedXid;
 
-	/* We don't release XidGenLock here, the caller is responsible for that */
-	LWLockRelease(ProcArrayLock);
-
 	Assert(TransactionIdIsValid(CurrentRunningXacts->nextXid));
 	Assert(TransactionIdIsValid(CurrentRunningXacts->oldestRunningXid));
 	Assert(TransactionIdIsNormal(CurrentRunningXacts->latestCompletedXid));
 
+	/* We don't release the locks here, the caller is responsible for that */
+
 	return CurrentRunningXacts;
 }
 
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 97da1a0..5f74c3e 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -879,8 +879,23 @@ LogStandbySnapshot(void)
 	 * record we write, because standby will open up when it sees this.
 	 */
 	running = GetRunningTransactionData();
+
+	/*
+	 * GetRunningTransactionData() acquired ProcArrayLock, we must release
+	 * it. We can do that before inserting the WAL record because
+	 * ProcArrayApplyRecoveryInfo can recheck the commit status using the
+	 * clog. If we're doing logical replication we can't do that though, so
+	 * hold the lock for a moment longer.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		LWLockRelease(ProcArrayLock);
+
 	recptr = LogCurrentRunningXacts(running);
 
+	/* Release lock if we kept it longer ... */
+	if (wal_level >= WAL_LEVEL_LOGICAL)
+		LWLockRelease(ProcArrayLock);
+
 	/* GetRunningTransactionData() acquired XidGenLock, we must release it */
 	LWLockRelease(XidGenLock);
 
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index bfe7d78..015970a 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -512,7 +512,7 @@ RegisterSnapshotInvalidation(Oid dbId, Oid relId)
  * Only the local caches are flushed; this does not transmit the message
  * to other backends.
  */
-static void
+void
 LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
 {
 	if (msg->id >= 0)
@@ -596,7 +596,7 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
  *		since that tells us we've lost some shared-inval messages and hence
  *		don't know what needs to be invalidated.
  */
-static void
+void
 InvalidateSystemCaches(void)
 {
 	int			i;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 44dd0d2..5d304ce 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1601,6 +1601,10 @@ RelationIdGetRelation(Oid relationId)
 		return rd;
 	}
 
+	/* up2date system relations, even during timetravel */
+	if (IsSystemRelationId(relationId))
+		SuspendDecodingSnapshots();
+
 	/*
 	 * no reldesc in the cache, so have RelationBuildDesc() build one and add
 	 * it.
@@ -1608,6 +1612,10 @@ RelationIdGetRelation(Oid relationId)
 	rd = RelationBuildDesc(relationId, true);
 	if (RelationIsValid(rd))
 		RelationIncrementReferenceCount(rd);
+
+	if (IsSystemRelationId(relationId))
+		UnSuspendDecodingSnapshots();
+
 	return rd;
 }
 
@@ -1729,6 +1737,10 @@ RelationReloadIndexInfo(Relation relation)
 		return;
 	}
 
+	/* up2date system relations, even during timetravel */
+	if (IsSystemRelation(relation))
+		SuspendDecodingSnapshots();
+
 	/*
 	 * Read the pg_class row
 	 *
@@ -1796,6 +1808,9 @@ RelationReloadIndexInfo(Relation relation)
 
 	/* Okay, now it's valid again */
 	relation->rd_isvalid = true;
+
+	if (IsSystemRelation(relation))
+		UnSuspendDecodingSnapshots();
 }
 
 /*
@@ -1977,6 +1992,10 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 
+		/* up2date system relations, even during timetravel */
+		if (IsSystemRelation(relation))
+			SuspendDecodingSnapshots();
+
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
 		if (newrel == NULL)
@@ -2046,6 +2065,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 
 		/* And now we can throw away the temporary entry */
 		RelationDestroyRelation(newrel);
+
+		if (IsSystemRelation(relation))
+			UnSuspendDecodingSnapshots();
 	}
 }
 
@@ -3551,7 +3573,10 @@ RelationGetIndexList(Relation relation)
 					Form_pg_attribute attr;
 					/* internal column, like oid */
 					if (attno <= 0)
-						continue;
+					{
+						found = false;
+						break;
+					}
 
 					attr = relation->rd_att->attrs[attno - 1];
 					if (!attr->attnotnull)
@@ -3839,17 +3864,26 @@ RelationGetIndexPredicate(Relation relation)
  * be bms_free'd when not needed anymore.
  */
 Bitmapset *
-RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
+RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
 	Bitmapset  *indexattrs;
-	Bitmapset  *uindexattrs;
+	Bitmapset  *uindexattrs; /* unique keys */
+	Bitmapset  *cindexattrs; /* best candidate key */
 	List	   *indexoidlist;
 	ListCell   *l;
 	MemoryContext oldcxt;
 
 	/* Quick exit if we already computed the result. */
 	if (relation->rd_indexattr != NULL)
-		return bms_copy(keyAttrs ? relation->rd_keyattr : relation->rd_indexattr);
+		switch(attrKind)
+		{
+			case INDEX_ATTR_BITMAP_CANDIDATE_KEY:
+				return bms_copy(relation->rd_ckeyattr);
+			case INDEX_ATTR_BITMAP_KEY:
+				return bms_copy(relation->rd_keyattr);
+			case INDEX_ATTR_BITMAP_ALL:
+				return bms_copy(relation->rd_indexattr);
+		}
 
 	/* Fast path if definitely no indexes */
 	if (!RelationGetForm(relation)->relhasindex)
@@ -3876,13 +3910,16 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 	 */
 	indexattrs = NULL;
 	uindexattrs = NULL;
+	cindexattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
 		Relation	indexDesc;
 		IndexInfo  *indexInfo;
 		int			i;
-		bool		isKey;
+		bool		isCKey;/* candidate or primary key */
+		bool		isKey;/* key member */
+
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -3894,6 +3931,8 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 			indexInfo->ii_Expressions == NIL &&
 			indexInfo->ii_Predicate == NIL;
 
+		isCKey = indexOid == relation->rd_primary;
+
 		/* Collect simple attribute references */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 		{
@@ -3903,6 +3942,11 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 			{
 				indexattrs = bms_add_member(indexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isCKey)
+					cindexattrs = bms_add_member(cindexattrs,
+												 attrnum - FirstLowInvalidHeapAttributeNumber);
+
 				if (isKey)
 					uindexattrs = bms_add_member(uindexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
@@ -3924,10 +3968,21 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	relation->rd_indexattr = bms_copy(indexattrs);
 	relation->rd_keyattr = bms_copy(uindexattrs);
+	relation->rd_ckeyattr = bms_copy(cindexattrs);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
-	return keyAttrs ? uindexattrs : indexattrs;
+	switch(attrKind)
+	{
+		case INDEX_ATTR_BITMAP_CANDIDATE_KEY:
+			return cindexattrs;
+		case INDEX_ATTR_BITMAP_KEY:
+			return uindexattrs;
+		case INDEX_ATTR_BITMAP_ALL:
+			return indexattrs;
+		default:
+			elog(ERROR, "unknown attrKind %u", attrKind);
+	}
 }
 
 /*
@@ -4902,3 +4957,49 @@ unlink_initfile(const char *initfilename)
 			elog(LOG, "could not remove cache file \"%s\": %m", initfilename);
 	}
 }
+
+bool
+RelationIsDoingTimetravelInternal(Relation relation)
+{
+	Assert(wal_level >= WAL_LEVEL_LOGICAL);
+
+	if (!RelationNeedsWAL(relation))
+		return false;
+
+	/*
+	 * XXX: Doing this test instead of using IsSystemNamespace has the
+	 * advantage of classifying a catalog relation's toast tables as a
+	 * timetravel relation as well. This is safe since even a oid wraparound
+	 * will preserve this property (c.f. GetNewObjectId()).
+	 */
+	if (IsSystemRelation(relation))
+		return true;
+
+	/*
+	 * Also log relevant data if we want the table to behave as a catalog
+	 * table, although its not a system provided one.
+	 * XXX: we need to make sure both the relation and its toast relation have
+	 * the flag set!
+	 */
+	if (RelationIsTreatedAsCatalogTable(relation))
+	    return true;
+
+	return false;
+}
+
+bool
+RelationIsLogicallyLoggedInternal(Relation relation)
+{
+	Assert(wal_level >= WAL_LEVEL_LOGICAL);
+	if (!RelationNeedsWAL(relation))
+		return false;
+	/*
+	 * XXX: In addition to the above comment, we could decide to always log
+	 * data even for real system catalogs, although the benefits of that seem
+	 * unclear.
+	 */
+	if (IsSystemRelation(relation))
+		return false;
+
+	return true;
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7d297bc..ced36f6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -57,6 +57,7 @@
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
+#include "replication/logical.h"
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
@@ -2060,6 +2061,17 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
+		/* see max_connections */
+		{"max_logical_slots", PGC_POSTMASTER, REPLICATION_SENDING,
+			gettext_noop("Sets the maximum number of simultaneously defined WAL decoding slots."),
+			NULL
+		},
+		&max_logical_slots,
+		0, 0, MAX_BACKENDS /*?*/,
+		NULL, NULL, NULL
+	},
+
+	{
 		{"wal_sender_timeout", PGC_SIGHUP, REPLICATION_SENDING,
 			gettext_noop("Sets the maximum time to wait for WAL replication."),
 			NULL,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d69a02b..b04291c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -161,7 +161,7 @@
 
 # - Settings -
 
-#wal_level = minimal			# minimal, archive, or hot_standby
+#wal_level = minimal			# minimal, archive, logical or hot_standby
 					# (change requires restart)
 #fsync = on				# turns forced synchronization on or off
 #synchronous_commit = on		# synchronization level;
@@ -208,11 +208,18 @@
 
 # Set these on the master and on any standby that will send replication data.
 
-#max_wal_senders = 0		# max number of walsender processes
+#max_wal_senders = 0		# max number of walsender processes, including
+				# both physical and logical replication senders.
 				# (change requires restart)
 #wal_keep_segments = 0		# in logfile segments, 16MB each; 0 disables
 #wal_sender_timeout = 60s	# in milliseconds; 0 disables
 
+#max_logical_slots = 0		# max number of logical replication sender
+				# and receiver processes. Logical senders
+				# (but not receivers) also consume a
+				# max_wal_senders slot.
+				# (change requires restart)
+
 # - Master Server -
 
 # These settings are ignored on a standby server.
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 584d70c..f63bafa 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -69,7 +69,7 @@
  */
 static SnapshotData CurrentSnapshotData = {HeapTupleSatisfiesMVCC};
 static SnapshotData SecondarySnapshotData = {HeapTupleSatisfiesMVCC};
-static SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
+SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
 
 /* Pointers to valid snapshots */
 static Snapshot CurrentSnapshot = NULL;
@@ -86,13 +86,14 @@ static bool CatalogSnapshotStale = true;
  * for the convenience of TransactionIdIsInProgress: even in bootstrap
  * mode, we don't want it to say that BootstrapTransactionId is in progress.
  *
- * RecentGlobalXmin is initialized to InvalidTransactionId, to ensure that no
+ * RecentGlobal(Data)?Xmin is initialized to InvalidTransactionId, to ensure that no
  * one tries to use a stale value.	Readers should ensure that it has been set
  * to something else before using it.
  */
 TransactionId TransactionXmin = FirstNormalTransactionId;
 TransactionId RecentXmin = FirstNormalTransactionId;
 TransactionId RecentGlobalXmin = InvalidTransactionId;
+TransactionId RecentGlobalDataXmin = InvalidTransactionId;
 
 /*
  * Elements of the active snapshot stack.
@@ -796,7 +797,7 @@ AtEOXact_Snapshot(bool isCommit)
  *		Returns the token (the file name) that can be used to import this
  *		snapshot.
  */
-static char *
+char *
 ExportSnapshot(Snapshot snapshot)
 {
 	TransactionId topXid;
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index ed66c49..28ce805 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -62,6 +62,8 @@
 #include "access/xact.h"
 #include "storage/bufmgr.h"
 #include "storage/procarray.h"
+#include "utils/builtins.h"
+#include "utils/combocid.h"
 #include "utils/tqual.h"
 
 
@@ -70,9 +72,17 @@ SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
 SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
 SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
 
+static Snapshot TimetravelSnapshot;
+/* (table, ctid) => (cmin, cmax) mapping during timetravel */
+static HTAB *tuplecid_data = NULL;
+static int timetravel_suspended = 0;
+
+
 /* local functions */
 static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
+static bool FailsSatisfies(HeapTuple htup, Snapshot snapshot, Buffer buffer);
+static bool RedirectSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
+								 Buffer buffer);
 
 /*
  * SetHintBits()
@@ -1490,3 +1500,261 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 	 */
 	return true;
 }
+
+/*
+ * check whether the transaciont id 'xid' in in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+	               sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+bool
+HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup, Snapshot snapshot,
+                                     Buffer buffer)
+{
+	HeapTupleHeader tuple = htup->t_data;
+	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
+	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (tuple->t_infomask & HEAP_XMIN_INVALID)
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+    /* check if its one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		CommandId cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId cmax = InvalidCommandId;
+
+		/*
+		 * If another transaction deleted this tuple or if cmin/cmax is stored
+		 * in a combocid we need to to lookup the actual values externally. We
+		 * need to do so in the deleted case because the deletion will have
+		 * overwritten the cmin value when setting cmax (c.f. combocid.c).
+		 */
+		if ((!(tuple->t_infomask & HEAP_XMAX_INVALID) &&
+			 !TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt)) ||
+			tuple->t_infomask & HEAP_COMBOCID
+			)
+		{
+			bool resolved;
+
+			resolved = ResolveCminCmaxDuringDecoding(tuplecid_data, htup,
+													 buffer, &cmin, &cmax);
+
+			if (!resolved)
+				elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+		}
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;	/* inserted after scan started */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMIN_COMMITTED &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if(TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+	}
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't
+	 * committed. I.e. invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* why should those be in catalog tables? */
+	Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+    /* check if its one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		CommandId cmin;
+		CommandId cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		if (tuple->t_infomask & HEAP_COMBOCID)
+		{
+			bool resolved;
+
+			resolved = ResolveCminCmaxDuringDecoding(tuplecid_data, htup,
+													 buffer, &cmin, &cmax);
+
+			if (!resolved)
+				elog(ERROR, "could not resolve combocid to cmax");
+		}
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;	/* deleted after scan started */
+		else
+			return false;	/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
+
+/*
+ * Setup a snapshot that replaces normal catalog snapshots that allows catalog
+ * access to behave just like it did at a certain point in the past.
+ *
+ * Needed for after-the-fact WAL decoding.
+ */
+void
+SetupDecodingSnapshots(Snapshot timetravel_snapshot, HTAB *tuplecids)
+{
+	/* prevent recursively setting up decoding snapshots */
+	Assert(CatalogSnapshotData.satisfies != RedirectSatisfiesMVCC);
+
+	CatalogSnapshotData.satisfies = RedirectSatisfiesMVCC;
+	/* make sure normal snapshots aren't used*/
+	SnapshotSelfData.satisfies = FailsSatisfies;
+	SnapshotAnyData.satisfies = FailsSatisfies;
+	SnapshotToastData.satisfies = FailsSatisfies;
+	/* don't overwrite SnapshotToastData, we want that to behave normally */
+
+	/* setup the timetravel snapshot */
+	TimetravelSnapshot = timetravel_snapshot;
+
+	/* setup (cmin, cmax) lookup hash */
+	tuplecid_data = tuplecids;
+
+	timetravel_suspended = 0;
+}
+
+
+/*
+ * Make catalog snapshots behave normally again.
+ */
+void
+RevertFromDecodingSnapshots(void)
+{
+	Assert(timetravel_suspended == 0);
+
+	TimetravelSnapshot = NULL;
+	tuplecid_data = NULL;
+
+	/* rally to restore sanity and/or boredom */
+	CatalogSnapshotData.satisfies = HeapTupleSatisfiesMVCC;
+	SnapshotSelfData.satisfies = HeapTupleSatisfiesSelf;
+	SnapshotAnyData.satisfies = HeapTupleSatisfiesAny;
+	SnapshotToastData.satisfies = HeapTupleSatisfiesToast;
+	timetravel_suspended = 0;
+}
+
+/*
+ * Disable catalog snapshot timetravel and perform old-fashioned access but
+ * make re-enabling cheap.. This is useful for accessing catalog entries which
+ * must stay up2date like the pg_class entries of system relations.
+ *
+ * Can be called several times in a nested fashion since several of it's
+ * callers suspend timetravel access on several code levels.
+ */
+void
+SuspendDecodingSnapshots(void)
+{
+	timetravel_suspended++;
+}
+
+/*
+ * Enable timetravel again, After SuspendDecodingSnapshots it.
+ */
+void
+UnSuspendDecodingSnapshots(void)
+{
+	Assert(timetravel_suspended > 0);
+	timetravel_suspended--;
+}
+
+/*
+ * Error out if a normal snapshot is used. That is neither legal nor expected
+ * during timetravel, so this is just extra assurance.
+ */
+static bool
+FailsSatisfies(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+{
+	elog(ERROR, "Normal snapshots cannot be used during timetravel access.");
+	return false;
+}
+
+
+/*
+ * Call the replacement SatisifiesMVCC with the required Snapshot data.
+ */
+static bool
+RedirectSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+{
+	Assert(TimetravelSnapshot != NULL);
+	if (timetravel_suspended > 0)
+		return HeapTupleSatisfiesMVCC(htup, snapshot, buffer);
+	return HeapTupleSatisfiesMVCCDuringDecoding(htup, TimetravelSnapshot,
+	                                            buffer);
+}
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f66f530..a887035 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -193,7 +193,9 @@ const char *subdirs[] = {
 	"base/1",
 	"pg_tblspc",
 	"pg_stat",
-	"pg_stat_tmp"
+	"pg_stat_tmp",
+	"pg_llog",
+	"pg_llog/snapshots"
 };
 
 
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index fde483a..8c6cf24 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -77,6 +77,8 @@ wal_level_str(WalLevel wal_level)
 			return "archive";
 		case WAL_LEVEL_HOT_STANDBY:
 			return "hot_standby";
+		case WAL_LEVEL_LOGICAL:
+			return "logical";
 	}
 	return _("unrecognized wal_level");
 }
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 4381778..42f3e6b 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -55,6 +55,18 @@
 #define XLOG_HEAP2_VISIBLE		0x40
 #define XLOG_HEAP2_MULTI_INSERT 0x50
 #define XLOG_HEAP2_LOCK_UPDATED 0x60
+#define XLOG_HEAP2_NEW_CID		0x70
+
+/*
+ * xl_heap_* ->flag values
+ */
+/* PD_ALL_VISIBLE was cleared */
+#define XLOG_HEAP_ALL_VISIBLE_CLEARED		(1<<0)
+/* PD_ALL_VISIBLE was cleared in the 2nd page */
+#define XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED	(1<<1)
+#define XLOG_HEAP_CONTAINS_OLD_TUPLE		(1<<2)
+#define XLOG_HEAP_CONTAINS_OLD_KEY			(1<<3)
+#define XLOG_HEAP_CONTAINS_NEW_TUPLE		(1<<4)
 
 /*
  * All what we need to find changed tuple
@@ -78,10 +90,10 @@ typedef struct xl_heap_delete
 	xl_heaptid	target;			/* deleted tuple id */
 	TransactionId xmax;			/* xmax of the deleted tuple */
 	uint8		infobits_set;	/* infomask bits */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
+	uint8		flags;
 } xl_heap_delete;
 
-#define SizeOfHeapDelete	(offsetof(xl_heap_delete, all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapDelete	(offsetof(xl_heap_delete, flags) + sizeof(uint8))
 
 /*
  * We don't store the whole fixed part (HeapTupleHeaderData) of an inserted
@@ -100,15 +112,23 @@ typedef struct xl_heap_header
 
 #define SizeOfHeapHeader	(offsetof(xl_heap_header, t_hoff) + sizeof(uint8))
 
+typedef struct xl_heap_header_len
+{
+	uint16      t_len;
+	xl_heap_header header;
+} xl_heap_header_len;
+
+#define SizeOfHeapHeaderLen	(offsetof(xl_heap_header_len, header) + SizeOfHeapHeader)
+
 /* This is what we need to know about insert */
 typedef struct xl_heap_insert
 {
 	xl_heaptid	target;			/* inserted tuple id */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
+	uint8		flags;
 	/* xl_heap_header & TUPLE DATA FOLLOWS AT END OF STRUCT */
 } xl_heap_insert;
 
-#define SizeOfHeapInsert	(offsetof(xl_heap_insert, all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapInsert	(offsetof(xl_heap_insert, flags) + sizeof(uint8))
 
 /*
  * This is what we need to know about a multi-insert. The record consists of
@@ -120,7 +140,7 @@ typedef struct xl_heap_multi_insert
 {
 	RelFileNode node;
 	BlockNumber blkno;
-	bool		all_visible_cleared;
+	uint8		flags;
 	uint16		ntuples;
 	OffsetNumber offsets[1];
 
@@ -147,13 +167,12 @@ typedef struct xl_heap_update
 	TransactionId old_xmax;		/* xmax of the old tuple */
 	TransactionId new_xmax;		/* xmax of the new tuple */
 	ItemPointerData newtid;		/* new inserted tuple id */
-	uint8		old_infobits_set;		/* infomask bits to set on old tuple */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
-	bool		new_all_visible_cleared;		/* same for the page of newtid */
+	uint8		old_infobits_set;	/* infomask bits to set on old tuple */
+	uint8		flags;
 	/* NEW TUPLE xl_heap_header AND TUPLE DATA FOLLOWS AT END OF STRUCT */
 } xl_heap_update;
 
-#define SizeOfHeapUpdate	(offsetof(xl_heap_update, new_all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapUpdate	(offsetof(xl_heap_update, flags) + sizeof(uint8))
 
 /*
  * This is what we need to know about vacuum page cleanup/redirect
@@ -261,6 +280,28 @@ typedef struct xl_heap_visible
 
 #define SizeOfHeapVisible (offsetof(xl_heap_visible, cutoff_xid) + sizeof(TransactionId))
 
+typedef struct xl_heap_new_cid
+{
+	/*
+	 * store toplevel xid so we don't have to merge cids from different
+	 * transactions
+	 */
+	TransactionId top_xid;
+	CommandId cmin;
+	CommandId cmax;
+	/*
+	 * don't really need the combocid but the padding makes it free and its
+	 * useful for debugging.
+	 */
+	CommandId combocid;
+	/*
+	 * Store the relfilenode/ctid pair to facilitate lookups.
+	 */
+	xl_heaptid target;
+} xl_heap_new_cid;
+
+#define SizeOfHeapNewCid (offsetof(xl_heap_new_cid, target) + SizeOfHeapTid)
+
 extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 									   TransactionId *latestRemovedXid);
 
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 23a41fd..8452ec5 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -63,6 +63,11 @@
 	(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
 	(int32) ((id1) - (id2)) < 0)
 
+/* compare two XIDs already known to be normal; this is a macro for speed */
+#define NormalTransactionIdFollows(id1, id2) \
+	(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
+	(int32) ((id1) - (id2)) > 0)
+
 /* ----------
  *		Object ID (OID) zero is InvalidOid.
  *
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 835f6ac..96502ce 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -215,6 +215,7 @@ extern TransactionId GetCurrentTransactionId(void);
 extern TransactionId GetCurrentTransactionIdIfAny(void);
 extern TransactionId GetStableLatestTransactionId(void);
 extern SubTransactionId GetCurrentSubTransactionId(void);
+extern void MarkCurrentTransactionIdLoggedIfAny(void);
 extern bool SubTransactionIsActive(SubTransactionId subxid);
 extern CommandId GetCurrentCommandId(bool used);
 extern TimestampTz GetCurrentTransactionStartTimestamp(void);
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 002862c..7415a26 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -197,7 +197,8 @@ typedef enum WalLevel
 {
 	WAL_LEVEL_MINIMAL = 0,
 	WAL_LEVEL_ARCHIVE,
-	WAL_LEVEL_HOT_STANDBY
+	WAL_LEVEL_HOT_STANDBY,
+	WAL_LEVEL_LOGICAL
 } WalLevel;
 extern int	wal_level;
 
@@ -210,9 +211,12 @@ extern int	wal_level;
  */
 #define XLogIsNeeded() (wal_level >= WAL_LEVEL_ARCHIVE)
 
-/* Do we need to WAL-log information required only for Hot Standby? */
+/* Do we need to WAL-log information required only for Hot Standby and logical replication? */
 #define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_HOT_STANDBY)
 
+/* Do we need to WAL-log information required only for logical replication? */
+#define XLogLogicalInfoActive() (wal_level >= WAL_LEVEL_LOGICAL)
+
 #ifdef WAL_DEBUG
 extern bool XLOG_DEBUG;
 #endif
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 3829ce2..fdc8cc2 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -19,6 +19,7 @@
 #ifndef XLOGREADER_H
 #define XLOGREADER_H
 
+#include "access/xlog.h"
 #include "access/xlog_internal.h"
 
 typedef struct XLogReaderState XLogReaderState;
@@ -108,10 +109,20 @@ struct XLogReaderState
 	char	   *errormsg_buf;
 };
 
-/* Get a new XLogReader */
+
 extern XLogReaderState *XLogReaderAllocate(XLogPageReadCB pagereadfunc,
 				   void *private_data);
 
+
+typedef struct XLogRecordBuffer
+{
+	XLogRecPtr origptr;
+	XLogRecPtr endptr;
+	XLogRecord record;
+	char *record_data;
+} XLogRecordBuffer;
+
+
 /* Free an XLogReader */
 extern void XLogReaderFree(XLogReaderState *state);
 
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index 44b6f38..a96ed69 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -23,6 +23,7 @@ extern ForkNumber forkname_to_number(char *forkName);
 extern char *GetDatabasePath(Oid dbNode, Oid spcNode);
 
 
+extern bool IsSystemRelationId(Oid relid);
 extern bool IsSystemRelation(Relation relation);
 extern bool IsToastRelation(Relation relation);
 
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f03dd0b..cf9c143 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2621,6 +2621,8 @@ DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f
 DESCR("statistics: information about currently active backends");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{23,25,25,25,25,25,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
+DATA(insert OID = 3457 (  pg_stat_get_logical_decoding_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{25,25,26,16,28,25}" "{o,o,o,o,o,o}" "{slot_name,plugin,database,active,xmin,restart_decoding_lsn}" _null_ pg_stat_get_logical_decoding_slots _null_ _null_ _null_ ));
+DESCR("statistics: information about logical replication slots currently in use");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
 DESCR("statistics: current backend PID");
 DATA(insert OID = 1937 (  pg_stat_get_backend_pid		PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 23 "23" _null_ _null_ _null_ _null_ pg_stat_get_backend_pid _null_ _null_ _null_ ));
@@ -4725,6 +4727,10 @@ DESCR("SP-GiST support for quad tree over range");
 DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
 DESCR("SP-GiST support for quad tree over range");
 
+DATA(insert OID = 3779 (  init_logical_replication PGNSP PGUID 12 1 0 0 0 f f f f f f v 2 0 2249 "19 19" "{19,19,25,25}" "{i,i,o,o}" "{slotname,plugin,slotname,xlog_position}" _null_ init_logical_replication _null_ _null_ _null_ ));
+DESCR("set up a logical replication slot");
+DATA(insert OID = 3780 (  stop_logical_replication PGNSP PGUID 12 1 0 0 0 f f f f f f v 1 0 23 "19" _null_ _null_ _null_ _null_ stop_logical_replication _null_ _null_ _null_ ));
+DESCR("stop logical replication");
 
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d8dd8b0..2616ac1 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -156,7 +156,7 @@ extern void vac_update_relstats(Relation relation,
 					TransactionId frozenxid,
 					MultiXactId minmulti);
 extern void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age,
-					  bool sharedRel,
+					  bool sharedRel, bool catalogRel,
 					  TransactionId *oldestXmin,
 					  TransactionId *freezeLimit,
 					  TransactionId *freezeTableLimit,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..360f98c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -409,6 +409,9 @@ typedef enum NodeTag
 	T_IdentifySystemCmd,
 	T_BaseBackupCmd,
 	T_StartReplicationCmd,
+	T_InitLogicalReplicationCmd,
+	T_StartLogicalReplicationCmd,
+	T_FreeLogicalReplicationCmd,
 	T_TimeLineHistoryCmd,
 
 	/*
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 85b4544..3da8d40 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -52,6 +52,41 @@ typedef struct StartReplicationCmd
 
 
 /* ----------------------
+ *		INIT_LOGICAL_REPLICATION command
+ * ----------------------
+ */
+typedef struct InitLogicalReplicationCmd
+{
+	NodeTag		type;
+	char       *name;
+	char       *plugin;
+} InitLogicalReplicationCmd;
+
+
+/* ----------------------
+ *		START_LOGICAL_REPLICATION command
+ * ----------------------
+ */
+typedef struct StartLogicalReplicationCmd
+{
+	NodeTag		type;
+	char       *name;
+	XLogRecPtr	startpoint;
+	List       *options;
+} StartLogicalReplicationCmd;
+
+/* ----------------------
+ *		FREE_LOGICAL_REPLICATION command
+ * ----------------------
+ */
+typedef struct FreeLogicalReplicationCmd
+{
+	NodeTag		type;
+	char       *name;
+} FreeLogicalReplicationCmd;
+
+
+/* ----------------------
  *		TIMELINE_HISTORY command
  * ----------------------
  */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
new file mode 100644
index 0000000..dd3f2ca
--- /dev/null
+++ b/src/include/replication/decode.h
@@ -0,0 +1,20 @@
+/*-------------------------------------------------------------------------
+ * decode.h
+ *	   PostgreSQL WAL to logical transformation
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DECODE_H
+#define DECODE_H
+
+#include "access/xlogreader.h"
+#include "replication/reorderbuffer.h"
+#include "replication/logical.h"
+
+void DecodeRecordIntoReorderBuffer(LogicalDecodingContext *ctx,
+							  XLogRecordBuffer *buf);
+
+#endif
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
new file mode 100644
index 0000000..971180b
--- /dev/null
+++ b/src/include/replication/logical.h
@@ -0,0 +1,198 @@
+/*-------------------------------------------------------------------------
+ * logical.h
+ *	   PostgreSQL WAL to logical transformation
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGICAL_H
+#define LOGICAL_H
+
+#include "access/xlog.h"
+#include "access/xlogreader.h"
+#include "replication/output_plugin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+
+/*
+ * Shared memory state of a single logical decoding slot
+ */
+typedef struct LogicalDecodingSlot
+{
+	/* lock, on same cacheline as effective_xmin */
+	slock_t		mutex;
+
+	/* on-disk xmin, updated first */
+	TransactionId xmin;
+
+	/* in-memory xmin, updated after syncing to disk */
+	TransactionId effective_xmin;
+
+	/* is this slot defined */
+	bool		in_use;
+
+	/* is somebody streaming out changes for this slot */
+	bool		active;
+
+	/* have we been aborted while ->active */
+	bool		aborted;
+
+	/* ----
+	 * If we shutdown, crash, whatever where do we have to restart decoding
+	 * from to
+	 * a) find a valid & ready snapshot
+	 * b) the complete content for all in-progress xacts
+	 * ----
+	 */
+	XLogRecPtr	restart_decoding;
+
+	/*
+	 * Last location we know the client has confirmed to have safely received
+	 * data to. No earlier data can be decoded after a restart/crash.
+	 */
+	XLogRecPtr	confirmed_flush;
+
+	/* ----
+	 * When the client has confirmed flushes >= candidate_xmin_after we can
+	 * a) advance the pegged xmin
+	 * b) advance restart_decoding_from so we have to read/keep less WAL
+	 * ----
+	 */
+	XLogRecPtr	candidate_lsn;
+	TransactionId candidate_xmin;
+	XLogRecPtr	candidate_restart_decoding;
+
+	/* database the slot is active on */
+	Oid			database;
+
+	/* slot identifier */
+	NameData	name;
+
+	/* plugin name */
+	NameData	plugin;
+} LogicalDecodingSlot;
+
+/*
+ * Shared memory control area for all of logical decoding
+ */
+typedef struct LogicalDecodingCtlData
+{
+	/*
+	 * Xmin across all logical slots.
+	 *
+	 * Protected by ProcArrayLock.
+	 */
+	TransactionId xmin;
+
+	LogicalDecodingSlot logical_slots[FLEXIBLE_ARRAY_MEMBER];
+} LogicalDecodingCtlData;
+
+/*
+ * Pointers to shared memory
+ */
+extern LogicalDecodingCtlData *LogicalDecodingCtl;
+extern LogicalDecodingSlot *MyLogicalDecodingSlot;
+
+struct LogicalDecodingContext;
+
+typedef void (*LogicalOutputPluginWriterWrite) (
+										   struct LogicalDecodingContext *lr,
+															XLogRecPtr Ptr,
+															TransactionId xid
+);
+
+typedef LogicalOutputPluginWriterWrite LogicalOutputPluginWriterPrepareWrite;
+
+/*
+ * Output plugin callbacks
+ */
+typedef struct OutputPluginCallbacks
+{
+	LogicalDecodeInitCB init_cb;
+	LogicalDecodeBeginCB begin_cb;
+	LogicalDecodeChangeCB change_cb;
+	LogicalDecodeCommitCB commit_cb;
+	LogicalDecodeCleanupCB cleanup_cb;
+} OutputPluginCallbacks;
+
+typedef struct LogicalDecodingContext
+{
+	struct XLogReaderState *reader;
+	struct LogicalDecodingSlot *slot;
+	struct ReorderBuffer *reorder;
+	struct SnapBuild *snapshot_builder;
+
+	struct OutputPluginCallbacks callbacks;
+
+	bool		stop_after_consistent;
+
+	/*
+	 * User specified options
+	 */
+	List	   *output_plugin_options;
+
+	/*
+	 * User-Provided callback for writing/streaming out data.
+	 */
+	LogicalOutputPluginWriterPrepareWrite prepare_write;
+	LogicalOutputPluginWriterWrite write;
+
+	/*
+	 * Output buffer.
+	 */
+	StringInfo	out;
+
+	/*
+	 * Private data pointer for the creator of the logical decoding context.
+	 */
+	void	   *owner_private;
+
+	/*
+	 * Private data pointer of the output plugin.
+	 */
+	void	   *output_plugin_private;
+
+	/*
+	 * Private data pointer for the data writer.
+	 */
+	void	   *output_writer_private;
+} LogicalDecodingContext;
+
+/* GUCs */
+extern PGDLLIMPORT int max_logical_slots;
+
+extern Size LogicalDecodingShmemSize(void);
+extern void LogicalDecodingShmemInit(void);
+
+extern void LogicalDecodingAcquireFreeSlot(const char *name, const char *plugin);
+extern void LogicalDecodingReleaseSlot(void);
+extern void LogicalDecodingReAcquireSlot(const char *name);
+extern void LogicalDecodingFreeSlot(const char *name);
+
+extern void ComputeLogicalXmin(void);
+
+/* change logical xmin */
+extern void IncreaseLogicalXminForSlot(XLogRecPtr lsn, TransactionId xmin);
+
+/* change recovery restart location */
+extern void IncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn);
+
+extern void LogicalConfirmReceivedLocation(XLogRecPtr lsn);
+
+extern void CheckLogicalReplicationRequirements(void);
+
+extern void StartupLogicalReplication(XLogRecPtr checkPointRedo);
+
+extern LogicalDecodingContext *CreateLogicalDecodingContext(
+							 LogicalDecodingSlot *slot,
+							 bool is_init,
+							 XLogRecPtr	start_lsn,
+							 List *output_plugin_options,
+							 XLogPageReadCB read_page,
+						 LogicalOutputPluginWriterPrepareWrite prepare_write,
+							 LogicalOutputPluginWriterWrite do_write);
+extern bool LogicalDecodingContextReady(LogicalDecodingContext *ctx);
+extern void FreeLogicalDecodingContext(LogicalDecodingContext *ctx);
+
+#endif
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
new file mode 100644
index 0000000..37f36a5
--- /dev/null
+++ b/src/include/replication/logicalfuncs.h
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ * logicalfuncs.h
+ *	   PostgreSQL WAL to logical transformation support functions
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGICALFUNCS_H
+#define LOGICALFUNCS_H
+
+extern int logical_read_local_xlog_page(XLogReaderState *state,
+							 XLogRecPtr targetPagePtr,
+							 int reqLen, XLogRecPtr targetRecPtr,
+							 char *cur_page, TimeLineID *pageTLI);
+
+extern Datum pg_stat_get_logical_decoding_slots(PG_FUNCTION_ARGS);
+
+#endif
diff --git a/src/include/replication/output_plugin.h b/src/include/replication/output_plugin.h
new file mode 100644
index 0000000..a9fcc2d
--- /dev/null
+++ b/src/include/replication/output_plugin.h
@@ -0,0 +1,70 @@
+/*-------------------------------------------------------------------------
+ * output_plugin.h
+ *	   PostgreSQL Logical Decode Plugin Interface
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OUTPUT_PLUGIN_H
+#define OUTPUT_PLUGIN_H
+
+#include "replication/reorderbuffer.h"
+
+struct LogicalDecodingContext;
+
+/*
+ * Callback that gets called in a user-defined plugin. ctx->private_data can
+ * be set to some private data.
+ *
+ * "is_init" will be set to "true" if the decoding slot just got defined. When
+ * the same slot is used from there one, it will be "false".
+ *
+ * Gets looked up via the library symbol pg_decode_init.
+ */
+typedef void (*LogicalDecodeInitCB) (
+										  struct LogicalDecodingContext *ctx,
+												 bool is_init
+);
+
+/*
+ * Callback called for every BEGIN of a successful transaction.
+ *
+ * Gets looked up via the library symbol pg_decode_begin_txn.
+ */
+typedef void (*LogicalDecodeBeginCB) (
+											 struct LogicalDecodingContext *,
+												  ReorderBufferTXN *txn);
+
+/*
+ * Callback for every individual change in a successful transaction.
+ *
+ * Gets looked up via the library symbol pg_decode_change.
+ */
+typedef void (*LogicalDecodeChangeCB) (
+											 struct LogicalDecodingContext *,
+												   ReorderBufferTXN *txn,
+												   Relation relation,
+												   ReorderBufferChange *change
+);
+
+/*
+ * Called for every COMMIT of a successful transaction.
+ *
+ * Gets looked up via the library symbol pg_decode_commit_txn.
+ */
+typedef void (*LogicalDecodeCommitCB) (
+											 struct LogicalDecodingContext *,
+												   ReorderBufferTXN *txn,
+												   XLogRecPtr commit_lsn);
+
+/*
+ * Called to cleanup the state of an output plugin.
+ *
+ * Gets looked up via the library symbol pg_decode_cleanup.
+ */
+typedef void (*LogicalDecodeCleanupCB) (
+											  struct LogicalDecodingContext *
+);
+
+#endif   /* OUTPUT_PLUGIN_H */
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
new file mode 100644
index 0000000..7a4e046
--- /dev/null
+++ b/src/include/replication/reorderbuffer.h
@@ -0,0 +1,342 @@
+/*
+ * reorderbuffer.h
+ *
+ * PostgreSQL logical replay buffer management
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * src/include/replication/reorderbuffer.h
+ */
+#ifndef REORDERBUFFER_H
+#define REORDERBUFFER_H
+
+#include "access/htup_details.h"
+#include "utils/hsearch.h"
+#include "utils/rel.h"
+
+#include "lib/ilist.h"
+
+#include "storage/sinval.h"
+
+#include "utils/snapshot.h"
+
+/* an individual tuple, stored in one chunk of memory */
+typedef struct ReorderBufferTupleBuf
+{
+	/* position in preallocated list */
+	slist_node	node;
+
+	/* tuple, stored sequentially */
+	HeapTupleData tuple;
+	HeapTupleHeaderData header;
+	char		data[MaxHeapTupleSize];
+} ReorderBufferTupleBuf;
+
+/* types of the change passed to a 'change' callback */
+enum ReorderBufferChangeType
+{
+	REORDER_BUFFER_CHANGE_INSERT,
+	REORDER_BUFFER_CHANGE_UPDATE,
+	REORDER_BUFFER_CHANGE_DELETE
+};
+
+/*
+ * a single 'change', can be an insert (with one tuple), an update (old, new),
+ * or a delete (old).
+ *
+ * The same struct is also used internally for other purposes but that should
+ * never be visible outside reorderbuffer.c.
+ */
+typedef struct ReorderBufferChange
+{
+	XLogRecPtr	lsn;
+
+	/* type of change */
+	union
+	{
+		enum ReorderBufferChangeType action;
+		/* do not leak internal enum values to the outside */
+		int			action_internal;
+	};
+
+	/*
+	 * Context data for the change, which part of the union is valid depends
+	 * on action/action_internal.
+	 */
+	union
+	{
+		/* old, new tuples when action == *_INSERT|UPDATE|DELETE */
+		struct
+		{
+			/* relation that has been changed */
+			RelFileNode relnode;
+			/* valid for DELETE || UPDATE */
+			ReorderBufferTupleBuf *oldtuple;
+			/* valid for INSERT || UPDATE */
+			ReorderBufferTupleBuf *newtuple;
+		};
+
+		/* new snapshot */
+		Snapshot	snapshot;
+
+		/* new command id for existing snapshot in a catalog changing tx */
+		CommandId	command_id;
+
+		/* new cid mapping for catalog changing transaction */
+		struct
+		{
+			RelFileNode node;
+			ItemPointerData tid;
+			CommandId	cmin;
+			CommandId	cmax;
+			CommandId	combocid;
+		}			tuplecid;
+	};
+
+	/*
+	 * While in use this is how a change is linked into a transactions,
+	 * otherwise it's the preallocated list.
+	 */
+	dlist_node	node;
+} ReorderBufferChange;
+
+typedef struct ReorderBufferTXN
+{
+	/*
+	 * The transactions transaction id, can be a toplevel or sub xid.
+	 */
+	TransactionId xid;
+
+	/*
+	 * LSN of the first data carrying, WAL record with knowledge about this
+	 * xid. This is allowed to *not* be first record adorned with this xid, if
+	 * the previous records aren't relevant for logical decoding.
+	 */
+	XLogRecPtr	first_lsn;
+
+	/* ----
+	 * LSN of the record that lead to this xact to be committed or
+	 * aborted. This can be a
+	 * * plain commit record
+	 * * plain commit record, of a parent transaction
+	 * * prepared transaction commit
+	 * * plain abort record
+	 * * prepared transaction abort
+	 * * error during decoding
+	 * ----
+	 */
+	XLogRecPtr	final_lsn;
+
+	/*
+	 * LSN pointing to the end of the commit record + 1.
+	 */
+	XLogRecPtr	end_lsn;
+
+	/*
+	 * LSN of the last lsn at which snapshot information reside, so we can
+	 * restart decoding from there and fully recover this transaction from
+	 * WAL.
+	 */
+	XLogRecPtr	restart_decoding_lsn;
+
+	/*
+	 * Base snapshot or NULL.
+	 */
+	Snapshot	base_snapshot;
+
+	/* did the TX have catalog changes */
+	bool		does_timetravel;
+
+	/*
+	 * Do we know this is a subxact?
+	 */
+	bool		is_known_as_subxact;
+
+	/*
+	 * How many ReorderBufferChange's do we have in this txn.
+	 *
+	 * Changes in subtransactions are *not* included but tracked separately.
+	 */
+	Size		nentries;
+
+	/*
+	 * How many of the above entries are stored in memory in contrast to being
+	 * spilled to disk.
+	 */
+	Size		nentries_mem;
+
+	/*
+	 * List of ReorderBufferChange structs, including new Snapshots and new
+	 * CommandIds
+	 */
+	dlist_head	changes;
+
+	/*
+	 * List of (relation, ctid) => (cmin, cmax) mappings for catalog tuples.
+	 * Those are always assigned to the toplevel transaction. (Keep track of
+	 * #entries to create a hash of the right size)
+	 */
+	dlist_head	tuplecids;
+	size_t		ntuplecids;
+
+	/*
+	 * On-demand built hash for looking up the above values.
+	 */
+	HTAB	   *tuplecid_hash;
+
+	/*
+	 * Hash containing (potentially partial) toast entries. NULL if no toast
+	 * tuples have been found for the current change.
+	 */
+	HTAB	   *toast_hash;
+
+	/*
+	 * non-hierarchical list of subtransactions that are *not* aborted. Only
+	 * used in toplevel transactions.
+	 */
+	dlist_head	subtxns;
+	size_t		nsubtxns;
+
+	/* ---
+	 * Position in one of three lists:
+	 * * list of subtransactions if we are *known* to be subxact
+	 * * list of toplevel xacts (can be a as-yet unknown subxact)
+	 * * list of preallocated ReorderBufferTXNs
+	 * ---
+	 */
+	dlist_node	node;
+
+	/*
+	 * Stored cache invalidations. This is not a linked list because we get
+	 * all the invalidations at once.
+	 */
+	SharedInvalidationMessage *invalidations;
+	size_t		ninvalidations;
+
+} ReorderBufferTXN;
+
+/* so we can define the callbacks used inside struct ReorderBuffer itself */
+typedef struct ReorderBuffer ReorderBuffer;
+
+/* change callback signature */
+typedef void (*ReorderBufferApplyChangeCB) (
+														ReorderBuffer *rb,
+														ReorderBufferTXN *txn,
+														Relation relation,
+												ReorderBufferChange *change);
+
+/* begin callback signature */
+typedef void (*ReorderBufferBeginCB) (
+												  ReorderBuffer *rb,
+												  ReorderBufferTXN *txn);
+
+/* commit callback signature */
+typedef void (*ReorderBufferCommitCB) (
+												   ReorderBuffer *rb,
+												   ReorderBufferTXN *txn,
+												   XLogRecPtr commit_lsn);
+
+struct ReorderBuffer
+{
+	/*
+	 * xid => ReorderBufferTXN lookup table
+	 */
+	HTAB	   *by_txn;
+
+	/*
+	 * Transactions that could be a toplevel xact, ordered by LSN of the first
+	 * record bearing that xid..
+	 */
+	dlist_head	toplevel_by_lsn;
+
+	/*
+	 * one-entry sized cache for by_txn. Very frequently the same txn gets
+	 * looked up over and over again.
+	 */
+	TransactionId by_txn_last_xid;
+	ReorderBufferTXN *by_txn_last_txn;
+
+	/*
+	 * Callacks to be called when a transactions commits.
+	 */
+	ReorderBufferBeginCB begin;
+	ReorderBufferApplyChangeCB apply_change;
+	ReorderBufferCommitCB commit;
+
+	/*
+	 * Pointer that will be passed untouched to the callbacks.
+	 */
+	void	   *private_data;
+
+	/*
+	 * Private memory context.
+	 */
+	MemoryContext context;
+
+	/*
+	 * Data structure slab cache.
+	 *
+	 * We allocate/deallocate some structures very frequently, to avoid bigger
+	 * overhead we cache some unused ones here.
+	 *
+	 * The maximum number of cached entries is controlled by const variables
+	 * ontop of reorderbuffer.c
+	 */
+
+	/* cached ReorderBufferTXNs */
+	dlist_head	cached_transactions;
+	Size		nr_cached_transactions;
+
+	/* cached ReorderBufferChanges */
+	dlist_head	cached_changes;
+	Size		nr_cached_changes;
+
+	/* cached ReorderBufferTupleBufs */
+	slist_head	cached_tuplebufs;
+	Size		nr_cached_tuplebufs;
+
+	XLogRecPtr	current_restart_decoding_lsn;
+
+	/* buffer for disk<->memory conversions */
+	char	   *outbuf;
+	Size		outbufsize;
+};
+
+
+ReorderBuffer *ReorderBufferAllocate(void);
+void		ReorderBufferFree(ReorderBuffer *);
+
+ReorderBufferTupleBuf *ReorderBufferGetTupleBuf(ReorderBuffer *);
+void		ReorderBufferReturnTupleBuf(ReorderBuffer *, ReorderBufferTupleBuf *tuple);
+ReorderBufferChange *ReorderBufferGetChange(ReorderBuffer *);
+void		ReorderBufferReturnChange(ReorderBuffer *, ReorderBufferChange *);
+
+void		ReorderBufferQueueChange(ReorderBuffer *, TransactionId, XLogRecPtr lsn, ReorderBufferChange *);
+void		ReorderBufferCommit(ReorderBuffer *, TransactionId,
+								XLogRecPtr commit_lsn, XLogRecPtr end_lsn);
+void		ReorderBufferAssignChild(ReorderBuffer *, TransactionId, TransactionId, XLogRecPtr commit_lsn);
+void		ReorderBufferCommitChild(ReorderBuffer *, TransactionId, TransactionId,
+									 XLogRecPtr commit_lsn, XLogRecPtr end_lsn);
+void		ReorderBufferAbort(ReorderBuffer *, TransactionId, XLogRecPtr lsn);
+
+void		ReorderBufferSetBaseSnapshot(ReorderBuffer *, TransactionId, XLogRecPtr lsn, struct SnapshotData *snap);
+void		ReorderBufferAddSnapshot(ReorderBuffer *, TransactionId, XLogRecPtr lsn, struct SnapshotData *snap);
+void ReorderBufferAddNewCommandId(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+							 CommandId cid);
+void ReorderBufferAddNewTupleCids(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+							 RelFileNode node, ItemPointerData pt,
+						 CommandId cmin, CommandId cmax, CommandId combocid);
+void ReorderBufferAddInvalidations(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+							  Size nmsgs, SharedInvalidationMessage *msgs);
+bool		ReorderBufferIsXidKnown(ReorderBuffer *, TransactionId xid);
+void		ReorderBufferXidSetTimetravel(ReorderBuffer *, TransactionId xid, XLogRecPtr lsn);
+bool		ReorderBufferXidDoesTimetravel(ReorderBuffer *, TransactionId xid);
+bool		ReorderBufferXidHasBaseSnapshot(ReorderBuffer *, TransactionId xid);
+
+ReorderBufferTXN *ReorderBufferGetOldestTXN(ReorderBuffer *);
+
+void		ReorderBufferSetRestartPoint(ReorderBuffer *, XLogRecPtr ptr);
+
+void		ReorderBufferStartup(void);
+
+#endif
diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h
new file mode 100644
index 0000000..ff369c5
--- /dev/null
+++ b/src/include/replication/snapbuild.h
@@ -0,0 +1,79 @@
+/*-------------------------------------------------------------------------
+ *
+ * snapbuild.h
+ *	  Exports from replication/logical/snapbuild.c.
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * src/include/replication/snapbuild.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SNAPBUILD_H
+#define SNAPBUILD_H
+
+#include "access/xlogdefs.h"
+
+typedef enum
+{
+	/*
+	 * Initial state, we can't do much yet.
+	 */
+	SNAPBUILD_START,
+
+	/*
+	 * We have collected enough information to decode tuples in transactions
+	 * that started after this.
+	 *
+	 * Once we reached this we start to collect changes. We cannot apply them
+	 * yet because the might be based on transactions that were still running
+	 * when we reached them yet.
+	 */
+	SNAPBUILD_FULL_SNAPSHOT,
+
+	/*
+	 * Found a point after hitting built_full_snapshot where all transactions
+	 * that were running at that point finished. Till we reach that we hold
+	 * off calling any commit callbacks.
+	 */
+	SNAPBUILD_CONSISTENT
+} SnapBuildState;
+
+/* forward declare so we don't have to expose the struct to the public */
+struct SnapBuild;
+typedef struct SnapBuild SnapBuild;
+
+/* forward declare so we don't have to include xlogreader.h */
+struct XLogRecordBuffer;
+
+extern SnapBuild *AllocateSnapshotBuilder(ReorderBuffer *cache,
+						  TransactionId xmin_horizon, XLogRecPtr start_lsn);
+extern void FreeSnapshotBuilder(SnapBuild *cache);
+
+extern void SnapBuildSnapDecRefcount(Snapshot snap);
+
+extern const char *SnapBuildExportSnapshot(SnapBuild *snapstate);
+extern void SnapBuildClearExportedSnapshot(void);
+
+extern SnapBuildState SnapBuildCurrentState(SnapBuild *snapstate);
+
+extern bool SnapBuildXactNeedsSkip(SnapBuild *snapstate, XLogRecPtr ptr);
+
+/* don't want to include heapam_xlog.h */
+struct xl_heap_new_cid;
+struct xl_running_xacts;
+
+extern void SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn,
+							   TransactionId xid, int nsubxacts,
+							   TransactionId *subxacts);
+extern void SnapBuildAbortTxn(SnapBuild *builder, TransactionId xid,
+							  int nsubxacts, TransactionId *subxacts);
+extern bool SnapBuildProcessChange(SnapBuild *builder, TransactionId xid,
+								   XLogRecPtr lsn);
+extern void SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
+								   XLogRecPtr lsn, struct xl_heap_new_cid *cid);
+extern void SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn,
+										 struct xl_running_xacts *running);
+extern void SnapBuildSerializationPoint(SnapBuild *builder, XLogRecPtr lsn);
+
+#endif   /* SNAPBUILD_H */
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 7eaa21b..daae320 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -66,6 +66,7 @@ typedef struct WalSnd
 
 extern WalSnd *MyWalSnd;
 
+
 /* There is one WalSndCtl struct for the whole database cluster */
 typedef struct
 {
@@ -93,7 +94,6 @@ typedef struct
 
 extern WalSndCtlData *WalSndCtl;
 
-
 extern void WalSndSetState(WalSndState state);
 
 /*
@@ -108,4 +108,8 @@ extern void replication_scanner_finish(void);
 
 extern Node *replication_parse_result;
 
+/* logical wal sender data gathering functions */
+extern XLogRecPtr WalSndWaitForWal(XLogRecPtr loc);
+
+
 #endif   /* _WALSENDER_PRIVATE_H */
diff --git a/src/include/storage/itemptr.h b/src/include/storage/itemptr.h
index e0eb184..75c56a9 100644
--- a/src/include/storage/itemptr.h
+++ b/src/include/storage/itemptr.h
@@ -116,6 +116,9 @@ typedef ItemPointerData *ItemPointer;
 /*
  * ItemPointerCopy
  *		Copies the contents of one disk item pointer to another.
+ *
+ * Should there ever be padding in an ItemPointer this would need to be handled
+ * differently as it's used as hash key.
  */
 #define ItemPointerCopy(fromPointer, toPointer) \
 ( \
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 39415a3..a33d6cf 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -80,6 +80,7 @@ typedef enum LWLockId
 	OldSerXidLock,
 	SyncRepLock,
 	BackgroundWorkerLock,
+	LogicalReplicationCtlLock,
 	/* Individual lock IDs end here */
 	FirstBufMappingLock,
 	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5f58b4..744317e 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -50,7 +50,7 @@ extern RunningTransactions GetRunningTransactionData(void);
 
 extern bool TransactionIdIsInProgress(TransactionId xid);
 extern bool TransactionIdIsActive(TransactionId xid);
-extern TransactionId GetOldestXmin(bool allDbs, bool ignoreVacuum);
+extern TransactionId GetOldestXmin(bool allDbs, bool ignoreVacuum, bool systable, bool alreadyLocked);
 extern TransactionId GetOldestActiveTransactionId(void);
 
 extern VirtualTransactionId *GetVirtualXIDsDelayingChkpt(int *nvxids);
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 7e70e57..5448818 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -147,4 +147,6 @@ extern void ProcessCommittedInvalidationMessages(SharedInvalidationMessage *msgs
 									 int nmsgs, bool RelcacheInitFileInval,
 									 Oid dbid, Oid tsid);
 
+extern void LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg);
+
 #endif   /* SINVAL_H */
diff --git a/src/include/utils/inval.h b/src/include/utils/inval.h
index 6fd6e1e..5424912 100644
--- a/src/include/utils/inval.h
+++ b/src/include/utils/inval.h
@@ -64,4 +64,5 @@ extern void CacheRegisterRelcacheCallback(RelcacheCallbackFunction func,
 
 extern void CallSyscacheCallbacks(int cacheid, uint32 hashvalue);
 
+extern void InvalidateSystemCaches(void);
 #endif   /* INVAL_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0281b4b..6a4d2d5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -104,6 +104,7 @@ typedef struct RelationData
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
+	Bitmapset  *rd_ckeyattr;	/* cols that are included ref'd by pkey */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
 	LockInfoData rd_lockInfo;	/* lock mgr's info for locking relation */
 	RuleLock   *rd_rules;		/* rewrite rules */
@@ -221,6 +222,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		security_barrier;		/* for views */
 	int			check_option_offset;	/* for views */
+	bool        treat_as_catalog_table; /* treat as timetraveleable table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -290,6 +292,15 @@ typedef struct StdRdOptions
 			"cascaded") == 0 : false)
 
 /*
+ * RelationIsTreatedAsCatalogTable
+ *		Returns whether the relation should be treated as a catalog table
+ *      from the pov of logical decoding.
+ */
+#define RelationIsTreatedAsCatalogTable(relation)	\
+	((relation)->rd_options ?				\
+	 ((StdRdOptions *) (relation)->rd_options)->treat_as_catalog_table : false)
+
+/*
  * RelationIsValid
  *		True iff relation descriptor is valid.
  */
@@ -441,7 +452,6 @@ typedef struct StdRdOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
-
 /*
  * RelationIsScannable
  *		Currently can only be false for a materialized view which has not been
@@ -458,6 +468,24 @@ typedef struct StdRdOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+/*
+ * RelationIsDoingTimetravel
+ *		True if we need to log enough information to provide timetravel access
+ */
+#define RelationIsDoingTimetravel(relation) \
+	(wal_level >= WAL_LEVEL_LOGICAL && \
+	 RelationIsDoingTimetravelInternal(relation))
+
+/*
+ * RelationIsLogicallyLogged
+ *		True if we need to log enough information to provide timetravel access
+ */
+#define RelationIsLogicallyLogged(relation) \
+	(wal_level >= WAL_LEVEL_LOGICAL && \
+	 RelationIsLogicallyLoggedInternal(relation))
+
+extern bool RelationIsDoingTimetravelInternal(Relation relation);
+extern bool RelationIsLogicallyLoggedInternal(Relation relation);
 
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 8ac2549..cfeded8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -41,7 +41,16 @@ extern List *RelationGetIndexList(Relation relation);
 extern Oid	RelationGetOidIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
-extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs);
+
+typedef enum IndexAttrBitmapKind {
+	INDEX_ATTR_BITMAP_ALL,
+	INDEX_ATTR_BITMAP_KEY,
+	INDEX_ATTR_BITMAP_CANDIDATE_KEY
+}  IndexAttrBitmapKind;
+
+extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
+											 IndexAttrBitmapKind keyAttrs);
+
 extern void RelationGetExclusionInfo(Relation indexRelation,
 						 Oid **operators,
 						 Oid **procs,
diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h
index 81a286c..2187f58 100644
--- a/src/include/utils/snapmgr.h
+++ b/src/include/utils/snapmgr.h
@@ -23,6 +23,7 @@ extern bool FirstSnapshotSet;
 extern TransactionId TransactionXmin;
 extern TransactionId RecentXmin;
 extern TransactionId RecentGlobalXmin;
+extern TransactionId RecentGlobalDataXmin;
 
 extern Snapshot GetTransactionSnapshot(void);
 extern Snapshot GetLatestSnapshot(void);
@@ -53,4 +54,6 @@ extern bool XactHasExportedSnapshots(void);
 extern void DeleteAllExportedSnapshotFiles(void);
 extern bool ThereAreNoPriorRegisteredSnapshots(void);
 
+extern char *ExportSnapshot(Snapshot snapshot);
+
 #endif   /* SNAPMGR_H */
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 19f56e4..873f170 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -22,6 +22,7 @@
 extern PGDLLIMPORT SnapshotData SnapshotSelfData;
 extern PGDLLIMPORT SnapshotData SnapshotAnyData;
 extern PGDLLIMPORT SnapshotData SnapshotToastData;
+extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 
 #define SnapshotSelf		(&SnapshotSelfData)
 #define SnapshotAny			(&SnapshotAnyData)
@@ -37,7 +38,8 @@ extern PGDLLIMPORT SnapshotData SnapshotToastData;
 
 /* This macro encodes the knowledge of which snapshots are MVCC-safe */
 #define IsMVCCSnapshot(snapshot)  \
-	((snapshot)->satisfies == HeapTupleSatisfiesMVCC)
+	((snapshot)->satisfies == HeapTupleSatisfiesMVCC || \
+	 (snapshot)->satisfies == HeapTupleSatisfiesMVCCDuringDecoding)
 
 /*
  * HeapTupleSatisfiesVisibility
@@ -86,4 +88,20 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 
+/* Support for catalog timetravel */
+extern bool HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup,
+                                                 Snapshot snapshot, Buffer buffer);
+extern void SetupDecodingSnapshots(Snapshot snapshot_now, HTAB *tuplecids);
+extern void RevertFromDecodingSnapshots(void);
+extern void SuspendDecodingSnapshots(void);
+extern void UnSuspendDecodingSnapshots(void);
+
+/*
+ * To avoid leaking to much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+extern bool ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data, HeapTuple htup,
+										  Buffer buffer,
+										  CommandId *cmin, CommandId *cmax);
+
 #endif   /* TQUAL_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 8f24c51..d49e499 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1679,6 +1679,13 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
                                  |     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,                                                                                                                                               +
                                  |     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock                                                                                                                                          +
                                  |    FROM pg_database d;
+ pg_stat_logical_decoding        |  SELECT l.slot_name,                                                                                                                                                                                           +
+                                 |     l.plugin,                                                                                                                                                                                                  +
+                                 |     l.database,                                                                                                                                                                                                +
+                                 |     l.active,                                                                                                                                                                                                  +
+                                 |     l.xmin,                                                                                                                                                                                                    +
+                                 |     l.restart_decoding_lsn                                                                                                                                                                                     +
+                                 |    FROM pg_stat_get_logical_decoding_slots() l(slot_name, plugin, database, active, xmin, restart_decoding_lsn);
  pg_stat_replication             |  SELECT s.pid,                                                                                                                                                                                                 +
                                  |     s.usesysid,                                                                                                                                                                                                +
                                  |     u.rolname AS usename,                                                                                                                                                                                      +
@@ -2142,7 +2149,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
                                  |    FROM tv;
  tvvmv                           |  SELECT tvvm.grandtot                                                                                                                                                                                          +
                                  |    FROM tvvm;
-(64 rows)
+(65 rows)
 
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b20eb0d..648caa0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -621,6 +621,7 @@ Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
 FormatNode
+FreeLogicalReplicationCmd
 FromCharDateMode
 FromExpr
 FuncCall
@@ -791,6 +792,7 @@ IdentifySystemCmd
 IncrementVarSublevelsUp_context
 Index
 IndexArrayKeyInfo
+IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
 IndexBulkDeleteCallback
@@ -818,6 +820,7 @@ IndxInfo
 InfoItem
 InhInfo
 InhOption
+InitLogicalReplicationCmd
 InheritableSocket
 InlineCodeBlock
 InsertStmt
@@ -937,6 +940,17 @@ LockTupleMode
 LockingClause
 LogOpts
 LogStmtLevel
+LogicalDecodeBeginCB
+LogicalDecodeChangeCB
+LogicalDecodeCleanupCB
+LogicalDecodeCommitCB
+LogicalDecodeInitCB
+LogicalDecodingCheckpointData
+LogicalDecodingContext
+LogicalDecodingCtlData
+LogicalDecodingSlot
+LogicalOutputPluginWriterPrepareWrite
+LogicalOutputPluginWriterWrite
 LogicalTape
 LogicalTapeSet
 MAGIC
@@ -1050,6 +1064,7 @@ OprInfo
 OprProofCacheEntry
 OprProofCacheKey
 OutputContext
+OutputPluginCallbacks
 OverrideSearchPath
 OverrideStackEntry
 PACE_HEADER
@@ -1464,6 +1479,21 @@ Relids
 RelocationBufferInfo
 RenameStmt
 ReopenPtr
+ReorderBuffer
+ReorderBufferApplyChangeCB
+ReorderBufferBeginCB
+ReorderBufferChange
+ReorderBufferChangeTypeInternal
+ReorderBufferCommitCB
+ReorderBufferDiskChange
+ReorderBufferIterTXNEntry
+ReorderBufferIterTXNState
+ReorderBufferToastEnt
+ReorderBufferTupleBuf
+ReorderBufferTupleCidEnt
+ReorderBufferTupleCidKey
+ReorderBufferTXN
+ReorderBufferTXNByIdEnt
 ReplaceVarsFromTargetList_context
 ReplaceVarsNoMatchOption
 ResTarget
@@ -1518,6 +1548,8 @@ SID_NAME_USE
 SISeg
 SMgrRelation
 SMgrRelationData
+SnapBuildAction
+SnapBuildState
 SOCKADDR
 SOCKET
 SPELL
@@ -1609,6 +1641,8 @@ SlruSharedData
 Snapshot
 SnapshotData
 SnapshotSatisfiesFunc
+Snapstate
+SnapstateOnDisk
 SockAddr
 Sort
 SortBy
@@ -1651,6 +1685,7 @@ StandardChunkHeader
 StartBlobPtr
 StartBlobsPtr
 StartDataPtr
+StartLogicalReplicationCmd
 StartReplicationCmd
 StartupPacket
 StatEntry
@@ -1874,6 +1909,7 @@ WalRcvData
 WalRcvState
 WalSnd
 WalSndCtlData
+WalSndSendData
 WalSndState
 WholeRowVarExprState
 WindowAgg
@@ -1925,6 +1961,7 @@ XLogReaderState
 XLogRecData
 XLogRecPtr
 XLogRecord
+XLogRecordBuffer
 XLogSegNo
 XLogSource
 XLogwrtResult
@@ -2347,6 +2384,7 @@ symbol
 tablespaceinfo
 teReqs
 teSection
+TestDecodingData
 temp_tablespaces_extra
 text
 timeKEY
@@ -2419,11 +2457,13 @@ xl_heap_cleanup_info
 xl_heap_delete
 xl_heap_freeze
 xl_heap_header
+xl_heap_header_len
 xl_heap_inplace
 xl_heap_insert
 xl_heap_lock
 xl_heap_lock_updated
 xl_heap_multi_insert
+xl_heap_new_cid
 xl_heap_newpage
 xl_heap_update
 xl_heap_visible
-- 
1.8.4.21.g992c386.dirty

0005-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patchtext/x-patch; charset=us-asciiDownload
>From 09e8610644e76c7b6391df3981fa2431064c9744 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 5/8] wal_decoding: test_decoding: Add a simple decoding module
 in contrib

This is mostly useful for testing, demonstration and documentation purposes.
---
 contrib/Makefile                      |   1 +
 contrib/test_decoding/Makefile        |  16 ++
 contrib/test_decoding/test_decoding.c | 322 ++++++++++++++++++++++++++++++++++
 3 files changed, 339 insertions(+)
 create mode 100644 contrib/test_decoding/Makefile
 create mode 100644 contrib/test_decoding/test_decoding.c

diff --git a/contrib/Makefile b/contrib/Makefile
index 8a2a937..6d2fe32 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,6 +50,7 @@ SUBDIRS = \
 		tablefunc	\
 		tcn		\
 		test_parser	\
+		test_decoding	\
 		tsearch2	\
 		unaccent	\
 		vacuumlo	\
diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
new file mode 100644
index 0000000..2ac9653
--- /dev/null
+++ b/contrib/test_decoding/Makefile
@@ -0,0 +1,16 @@
+# contrib/test_decoding/Makefile
+
+MODULE_big = test_decoding
+OBJS = test_decoding.o
+
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/test_decoding
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
new file mode 100644
index 0000000..fb9a240
--- /dev/null
+++ b/contrib/test_decoding/test_decoding.c
@@ -0,0 +1,322 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_decoding.c
+ *		  example output plugin for the logical replication functionality
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/test_decoding/test_decoding.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sysattr.h"
+
+#include "catalog/pg_class.h"
+#include "catalog/pg_type.h"
+#include "catalog/index.h"
+
+#include "nodes/parsenodes.h"
+
+#include "replication/output_plugin.h"
+#include "replication/logical.h"
+
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/relcache.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+
+PG_MODULE_MAGIC;
+
+void		_PG_init(void);
+
+typedef struct
+{
+	MemoryContext context;
+	bool		include_xids;
+} TestDecodingData;
+
+/* These must be available to pg_dlsym() */
+extern void pg_decode_init(LogicalDecodingContext *ctx, bool is_init);
+extern void pg_decode_begin_txn(LogicalDecodingContext *ctx,
+					ReorderBufferTXN *txn);
+extern void pg_decode_commit_txn(LogicalDecodingContext *ctx,
+					 ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
+extern void pg_decode_change(LogicalDecodingContext *ctx,
+				 ReorderBufferTXN *txn, Relation rel,
+				 ReorderBufferChange *change);
+
+void
+_PG_init(void)
+{
+}
+
+/* initialize this plugin */
+void
+pg_decode_init(LogicalDecodingContext *ctx, bool is_init)
+{
+	ListCell   *option;
+	TestDecodingData *data;
+
+	AssertVariableIsOfType(&pg_decode_init, LogicalDecodeInitCB);
+
+	data = palloc(sizeof(TestDecodingData));
+	data->context = AllocSetContextCreate(TopMemoryContext,
+										  "text conversion context",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+	data->include_xids = true;
+
+	ctx->output_plugin_private = data;
+
+	foreach(option, ctx->output_plugin_options)
+	{
+		DefElem    *elem = lfirst(option);
+
+		Assert(elem->arg == NULL || IsA(elem->arg, String));
+
+		if (strcmp(elem->defname, "hide-xids") == 0)
+		{
+			/* FIXME: parse argument */
+			data->include_xids = false;
+		}
+		else
+		{
+			elog(WARNING, "option %s = %s is unknown",
+				 elem->defname, elem->arg ? strVal(elem->arg) : "(null)");
+		}
+	}
+}
+
+/* BEGIN callback */
+void
+pg_decode_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+	TestDecodingData *data = ctx->output_plugin_private;
+
+	AssertVariableIsOfType(&pg_decode_begin_txn, LogicalDecodeBeginCB);
+
+	ctx->prepare_write(ctx, txn->end_lsn, txn->xid);
+	if (data->include_xids)
+		appendStringInfo(ctx->out, "BEGIN %u", txn->xid);
+	else
+		appendStringInfoString(ctx->out, "BEGIN");
+	ctx->write(ctx, txn->end_lsn, txn->xid);
+}
+
+/* COMMIT callback */
+void
+pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	TestDecodingData *data = ctx->output_plugin_private;
+
+	AssertVariableIsOfType(&pg_decode_commit_txn, LogicalDecodeCommitCB);
+
+	ctx->prepare_write(ctx, txn->end_lsn, txn->xid);
+	if (data->include_xids)
+		appendStringInfo(ctx->out, "COMMIT %u", txn->xid);
+	else
+		appendStringInfoString(ctx->out, "COMMIT");
+	ctx->write(ctx, txn->end_lsn, txn->xid);
+}
+
+/* print the tuple 'tuple' into the StringInfo s */
+static void
+tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple)
+{
+	int			natt;
+	Oid			oid;
+
+	/* print oid of tuple, it's not included in the TupleDesc */
+	if ((oid = HeapTupleHeaderGetOid(tuple->t_data)) != InvalidOid)
+	{
+		appendStringInfo(s, " oid[oid]:%u", oid);
+	}
+
+	/* print all columns individually */
+	for (natt = 0; natt < tupdesc->natts; natt++)
+	{
+		Form_pg_attribute attr; /* the attribute itself */
+		Oid			typid;		/* type of current attribute */
+		HeapTuple	type_tuple; /* information about a type */
+		Form_pg_type type_form;
+		Oid			typoutput;	/* output function */
+		bool		typisvarlena;
+		Datum		origval;	/* possibly toasted Datum */
+		Datum		val;		/* definitely detoasted Datum */
+		char	   *outputstr = NULL;
+		bool		isnull;		/* column is null? */
+
+		attr = tupdesc->attrs[natt];
+
+		/*
+		 * don't print dropped columns, we can't be sure everything is
+		 * available for them
+		 */
+		if (attr->attisdropped)
+			continue;
+
+		/*
+		 * Don't print system columns, oid will already have been printed if
+		 * present.
+		 */
+		if (attr->attnum < 0)
+			continue;
+
+		typid = attr->atttypid;
+
+		/* gather type name */
+		type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+		if (!HeapTupleIsValid(type_tuple))
+			elog(ERROR, "cache lookup failed for type %u", typid);
+		type_form = (Form_pg_type) GETSTRUCT(type_tuple);
+
+		/* print attribute name */
+		appendStringInfoChar(s, ' ');
+		appendStringInfoString(s, NameStr(attr->attname));
+
+		/* print attribute type */
+		appendStringInfoChar(s, '[');
+		appendStringInfoString(s, NameStr(type_form->typname));
+		appendStringInfoChar(s, ']');
+
+		/* query output function */
+		getTypeOutputInfo(typid,
+						  &typoutput, &typisvarlena);
+
+		ReleaseSysCache(type_tuple);
+
+		/* get Datum from tuple */
+		origval = fastgetattr(tuple, natt + 1, tupdesc, &isnull);
+
+		if (isnull)
+			outputstr = "(null)";
+		else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval))
+			outputstr = "(unchanged-toast-datum)";
+		else if (typisvarlena)
+			val = PointerGetDatum(PG_DETOAST_DATUM(origval));
+		else
+			val = origval;
+
+		/* call output function if necessary */
+		if (outputstr == NULL)
+			outputstr = OidOutputFunctionCall(typoutput, val);
+
+		/* print data */
+		appendStringInfoChar(s, ':');
+		appendStringInfoString(s, outputstr);
+	}
+}
+
+/*
+ * callback for individual changed tuples
+ */
+void
+pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+				 Relation relation, ReorderBufferChange *change)
+{
+	TestDecodingData *data;
+	Form_pg_class class_form;
+	TupleDesc	tupdesc;
+	MemoryContext old;
+
+	AssertVariableIsOfType(&pg_decode_change, LogicalDecodeChangeCB);
+
+	data = ctx->output_plugin_private;
+	class_form = RelationGetForm(relation);
+	tupdesc = RelationGetDescr(relation);
+
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	ctx->prepare_write(ctx, change->lsn, txn->xid);
+
+	appendStringInfoString(ctx->out, "table \"");
+	appendStringInfoString(ctx->out, NameStr(class_form->relname));
+	appendStringInfoString(ctx->out, "\":");
+
+	switch (change->action)
+	{
+		case REORDER_BUFFER_CHANGE_INSERT:
+			appendStringInfoString(ctx->out, " INSERT:");
+			if (change->newtuple == NULL)
+				appendStringInfoString(ctx->out, " (no-tuple-data)");
+			else
+				tuple_to_stringinfo(ctx->out, tupdesc, &change->newtuple->tuple);
+			break;
+		case REORDER_BUFFER_CHANGE_UPDATE:
+			appendStringInfoString(ctx->out, " UPDATE:");
+			if (change->oldtuple != NULL)
+			{
+				Relation	indexrel;
+				TupleDesc	indexdesc;
+
+				appendStringInfoString(ctx->out, " old-pkey:");
+				RelationGetIndexList(relation);
+
+				if (!OidIsValid(relation->rd_primary))
+				{
+					elog(LOG, "tuple in table with oid: %u without primary key",
+						 RelationGetRelid(relation));
+					break;
+				}
+
+				indexrel = RelationIdGetRelation(relation->rd_primary);
+
+				indexdesc = RelationGetDescr(indexrel);
+
+				tuple_to_stringinfo(ctx->out, indexdesc, &change->oldtuple->tuple);
+
+				RelationClose(indexrel);
+				appendStringInfoString(ctx->out, " new-tuple:");
+			}
+
+			if (change->newtuple == NULL)
+				appendStringInfoString(ctx->out, " (no-tuple-data)");
+			else
+				tuple_to_stringinfo(ctx->out, tupdesc, &change->newtuple->tuple);
+
+			break;
+		case REORDER_BUFFER_CHANGE_DELETE:
+			appendStringInfoString(ctx->out, " DELETE:");
+
+			/* if there was no PK, we only know that a delete happened */
+			if (change->oldtuple == NULL)
+				appendStringInfoString(ctx->out, " (no-tuple-data)");
+			/* In DELETE, only the PK is present; display that */
+			else
+			{
+				Relation	indexrel;
+
+				/* make sure rd_primary is set */
+				RelationGetIndexList(relation);
+
+				if (!OidIsValid(relation->rd_primary))
+				{
+					elog(LOG, "tuple in table with oid: %u without primary key",
+						 RelationGetRelid(relation));
+					break;
+				}
+
+				indexrel = RelationIdGetRelation(relation->rd_primary);
+
+				tuple_to_stringinfo(ctx->out, RelationGetDescr(indexrel),
+									&change->oldtuple->tuple);
+
+				RelationClose(indexrel);
+			}
+			break;
+	}
+
+	MemoryContextSwitchTo(old);
+	MemoryContextReset(data->context);
+
+	ctx->write(ctx, change->lsn, txn->xid);
+}
-- 
1.8.4.21.g992c386.dirty

0006-wal_decoding-pg_receivellog-Introduce-pg_receivexlog.patchtext/x-patch; charset=us-asciiDownload
>From d4d15827ea16281927ed9727fc4a5f472569995b Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 6/8] wal_decoding: pg_receivellog: Introduce pg_receivexlog
 equivalent for logical changes

---
 src/backend/utils/cache/relcache.c     |   3 +
 src/bin/pg_basebackup/.gitignore       |   1 +
 src/bin/pg_basebackup/Makefile         |  11 +-
 src/bin/pg_basebackup/pg_receivellog.c | 860 +++++++++++++++++++++++++++++++++
 src/bin/pg_basebackup/receivelog.c     | 137 +-----
 src/bin/pg_basebackup/receivelog.h     |   2 +
 src/bin/pg_basebackup/streamutil.c     | 123 ++++-
 src/bin/pg_basebackup/streamutil.h     |  10 +
 8 files changed, 1023 insertions(+), 124 deletions(-)
 create mode 100644 src/bin/pg_basebackup/pg_receivellog.c

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5d304ce..1b66e64 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1577,6 +1577,9 @@ RelationIdGetRelation(Oid relationId)
 {
 	Relation	rd;
 
+	/* Make sure we're in a xact, even if this ends up being a cache hit */
+	Assert(IsTransactionState());
+
 	/*
 	 * first try to find reldesc in the cache
 	 */
diff --git a/src/bin/pg_basebackup/.gitignore b/src/bin/pg_basebackup/.gitignore
index 1334a1f..eb2978c 100644
--- a/src/bin/pg_basebackup/.gitignore
+++ b/src/bin/pg_basebackup/.gitignore
@@ -1,2 +1,3 @@
 /pg_basebackup
 /pg_receivexlog
+/pg_receivellog
diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile
index a707c93..c251249 100644
--- a/src/bin/pg_basebackup/Makefile
+++ b/src/bin/pg_basebackup/Makefile
@@ -20,7 +20,7 @@ override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 
 OBJS=receivelog.o streamutil.o $(WIN32RES)
 
-all: pg_basebackup pg_receivexlog
+all: pg_basebackup pg_receivexlog pg_receivellog
 
 pg_basebackup: pg_basebackup.o $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) pg_basebackup.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
@@ -28,9 +28,13 @@ pg_basebackup: pg_basebackup.o $(OBJS) | submake-libpq submake-libpgport
 pg_receivexlog: pg_receivexlog.o $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) pg_receivexlog.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
+pg_receivellog: pg_receivellog.o $(OBJS) | submake-libpq submake-libpgport
+	$(CC) $(CFLAGS) pg_receivellog.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
 install: all installdirs
 	$(INSTALL_PROGRAM) pg_basebackup$(X) '$(DESTDIR)$(bindir)/pg_basebackup$(X)'
 	$(INSTALL_PROGRAM) pg_receivexlog$(X) '$(DESTDIR)$(bindir)/pg_receivexlog$(X)'
+	$(INSTALL_PROGRAM) pg_receivellog$(X) '$(DESTDIR)$(bindir)/pg_receivellog$(X)'
 
 installdirs:
 	$(MKDIR_P) '$(DESTDIR)$(bindir)'
@@ -38,6 +42,9 @@ installdirs:
 uninstall:
 	rm -f '$(DESTDIR)$(bindir)/pg_basebackup$(X)'
 	rm -f '$(DESTDIR)$(bindir)/pg_receivexlog$(X)'
+	rm -f '$(DESTDIR)$(bindir)/pg_receivellog$(X)'
 
 clean distclean maintainer-clean:
-	rm -f pg_basebackup$(X) pg_receivexlog$(X) $(OBJS) pg_basebackup.o pg_receivexlog.o
+	rm -f pg_basebackup$(X) pg_receivexlog$(X) pg_receivellog$(X) \
+		pg_basebackup.o pg_receivexlog.o pg_receivellog.o \
+		$(OBJS)
diff --git a/src/bin/pg_basebackup/pg_receivellog.c b/src/bin/pg_basebackup/pg_receivellog.c
new file mode 100644
index 0000000..fc81608
--- /dev/null
+++ b/src/bin/pg_basebackup/pg_receivellog.c
@@ -0,0 +1,860 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_receivellog.c - receive streaming logical log data and write it
+ *					  to a local file.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/bin/pg_basebackup/pg_receivellog.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "streamutil.h"
+
+#include "getopt_long.h"
+
+#include "libpq-fe.h"
+#include "libpq/pqsignal.h"
+
+#include "access/xlog_internal.h"
+#include "common/fe_memutils.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Time to sleep between reconnection attempts */
+#define RECONNECT_SLEEP_TIME 5
+
+/* Global Options */
+static char    *outfile = NULL;
+static int		verbose = 0;
+static int		noloop = 0;
+static int		standby_message_timeout = 10 * 1000;		/* 10 sec = default */
+static const char *slot = NULL;
+static XLogRecPtr startpos = InvalidXLogRecPtr;
+static bool 	do_init_slot = false;
+static bool 	do_start_slot = false;
+static bool 	do_stop_slot = false;
+
+/* filled pairwise with option, value. value may be NULL */
+static char	  **options;
+static size_t	noptions = 0;
+static const char *plugin = "test_decoding";
+
+/* Global State */
+static int		outfd = -1;
+static volatile bool time_to_abort = false;
+
+static void usage(void);
+static void StreamLog();
+
+static void
+usage(void)
+{
+	printf(_("%s receives PostgreSQL logical change stream.\n\n"),
+		   progname);
+	printf(_("Usage:\n"));
+	printf(_("  %s [OPTION]...\n"), progname);
+	printf(_("\nOptions:\n"));
+	printf(_("  -f, --file=FILE        receive log into this file. - for stdout\n"));
+	printf(_("  -n, --no-loop          do not loop on connection lost\n"));
+	printf(_("  -v, --verbose          output verbose messages\n"));
+	printf(_("  -V, --version          output version information, then exit\n"));
+	printf(_("  -?, --help             show this help, then exit\n"));
+	printf(_("\nConnection options:\n"));
+	printf(_("  -d, --database=DBNAME  database to connect to\n"));
+	printf(_("  -h, --host=HOSTNAME    database server host or socket directory\n"));
+	printf(_("  -p, --port=PORT        database server port number\n"));
+	printf(_("  -U, --username=NAME    connect as specified database user\n"));
+	printf(_("  -w, --no-password      never prompt for password\n"));
+	printf(_("  -W, --password         force password prompt (should happen automatically)\n"));
+	printf(_("\nReplication options:\n"));
+	printf(_("  -o, --option=NAME[=VALUE]\n"
+			 "                         Specify option NAME with optional value VAL, to be passed\n"
+			 "                         to the output plugin\n"));
+	printf(_("  -P, --plugin=PLUGIN    use output plugin PLUGIN (defaults to test_decoding)\n"));
+	printf(_("  -s, --status-interval=INTERVAL\n"
+			 "                         time between status packets sent to server (in seconds)\n"));
+	printf(_("  -S, --slot=SLOT        use existing replication slot SLOT instead of starting a new one\n"));
+	printf(_("  -I, --startpos=PTR     Where in an existing slot should the streaming start"));
+	printf(_("\nAction to be performed:\n"));
+	printf(_("      --init             initiate a new replication slot (for the slotname see --slot)\n"));
+	printf(_("      --start            start streaming in a replication slot (for the slotname see --slot)\n"));
+	printf(_("      --stop             stop the replication slot (for the slotname see --slot)\n"));
+	printf(_("\nReport bugs to <pgsql-bugs@postgresql.org>.\n"));
+}
+
+/*
+ * Send a Standby Status Update message to server.
+ */
+static bool
+sendFeedback(PGconn *conn, XLogRecPtr blockpos, int64 now, bool force, bool replyRequested)
+{
+	char		replybuf[1 + 8 + 8 + 8 + 8 + 1];
+	int			len = 0;
+
+	/*
+	 * we normally don't want to send superflous feedbacks, but if
+	 * it's because of a timeout we need to, otherwise
+	 * replication_timeout will kill us.
+	 */
+	if (blockpos == startpos && !force)
+		return true;
+
+	if (verbose)
+		fprintf(stderr,
+				_("%s: confirming flush up to %X/%X (slot %s)\n"),
+				progname, (uint32) (blockpos >> 32), (uint32) blockpos,
+				slot);
+
+	replybuf[len] = 'r';
+	len += 1;
+	fe_sendint64(blockpos, &replybuf[len]);		/* write */
+	len += 8;
+	fe_sendint64(blockpos, &replybuf[len]);		/* flush */
+	len += 8;
+	fe_sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* apply */
+	len += 8;
+	fe_sendint64(now, &replybuf[len]);		/* sendTime */
+	len += 8;
+	replybuf[len] = replyRequested ? 1 : 0;		/* replyRequested */
+	len += 1;
+
+	startpos = blockpos;
+
+	if (PQputCopyData(conn, replybuf, len) <= 0 || PQflush(conn))
+	{
+		fprintf(stderr, _("%s: could not send feedback packet: %s"),
+				progname, PQerrorMessage(conn));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Start the log streaming
+ */
+static void
+StreamLog(void)
+{
+	PGresult   *res;
+	char		query[512];
+	char	   *copybuf = NULL;
+	int64		last_status = -1;
+	XLogRecPtr	logoff = InvalidXLogRecPtr;
+	int			written;
+	int			i;
+
+	/*
+	 * Connect in replication mode to the server
+	 */
+	if (!conn)
+		conn = GetConnection();
+	if (!conn)
+		/* Error message already written in GetConnection() */
+		return;
+
+	/*
+	 * Start the replication
+	 */
+	if (verbose)
+		fprintf(stderr,
+				_("%s: starting log streaming at %X/%X (slot %s)\n"),
+				progname, (uint32) (startpos >> 32), (uint32) startpos,
+				slot);
+
+	/* Initiate the replication stream at specified location */
+	written = snprintf(query, sizeof(query), "START_LOGICAL_REPLICATION \"%s\" %X/%X",
+			 slot, (uint32) (startpos >> 32), (uint32) startpos);
+
+	/*
+	 * add options to string, if present
+	 * Oh, if we just had stringinfo in src/common...
+	 */
+	if (noptions)
+		written += snprintf(query + written, sizeof(query) - written, " (");
+
+	for (i = 0; i < noptions; i++)
+	{
+		/* separator */
+		if (i > 0)
+			written += snprintf(query + written, sizeof(query) - written, ", ");
+
+		/* write option name */
+		written += snprintf(query + written, sizeof(query) - written, "\"%s\"",
+							options[(i * 2)]);
+
+		if (written >= sizeof(query) - 1)
+		{
+			fprintf(stderr, _("%s: option string too long\n"), progname);
+			exit(1); /* no point in retrying, fatal error */
+		}
+
+		/* write option name if specified */
+		if (options[(i * 2) + 1] != NULL)
+		{
+			written += snprintf(query + written, sizeof(query) - written, " '%s'",
+								options[(i * 2) + 1]);
+
+			if (written >= sizeof(query) - 1)
+			{
+				fprintf(stderr, _("%s: option string too long\n"), progname);
+				exit(1); /* no point in retrying, fatal error */
+			}
+		}
+	}
+
+	if (noptions)
+	{
+		written += snprintf(query + written, sizeof(query) - written, ")");
+		if (written >= sizeof(query) - 1)
+		{
+			fprintf(stderr, _("%s: option string too long\n"), progname);
+			exit(1); /* no point in retrying, fatal error */
+		}
+	}
+
+	res = PQexec(conn, query);
+	if (PQresultStatus(res) != PGRES_COPY_BOTH)
+	{
+		fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+				progname, query, PQresultErrorMessage(res));
+		PQclear(res);
+		goto error;
+	}
+	PQclear(res);
+
+	if (verbose)
+		fprintf(stderr,
+				_("%s: initiated streaming\n"),
+				progname);
+
+	while (!time_to_abort)
+	{
+		int			r;
+		int			bytes_left;
+		int			bytes_written;
+		int64		now;
+		int			hdr_len;
+
+		if (copybuf != NULL)
+		{
+			PQfreemem(copybuf);
+			copybuf = NULL;
+		}
+
+		/*
+		 * Potentially send a status message to the master
+		 */
+		now = feGetCurrentTimestamp();
+		if (standby_message_timeout > 0 &&
+			feTimestampDifferenceExceeds(last_status, now,
+										 standby_message_timeout))
+		{
+			/* Time to send feedback! */
+			if (!sendFeedback(conn, logoff, now, true, false))
+				goto error;
+
+			last_status = now;
+		}
+
+		r = PQgetCopyData(conn, &copybuf, 1);
+		if (r == 0)
+		{
+			/*
+			 * In async mode, and no data available. We block on reading but
+			 * not more than the specified timeout, so that we can send a
+			 * response back to the client.
+			 */
+			fd_set		input_mask;
+			struct timeval timeout;
+			struct timeval *timeoutptr;
+
+			FD_ZERO(&input_mask);
+			FD_SET(PQsocket(conn), &input_mask);
+			if (standby_message_timeout)
+			{
+				int64		targettime;
+				long		secs;
+				int			usecs;
+
+				targettime = last_status + (standby_message_timeout - 1) *
+					((int64) 1000);
+				feTimestampDifference(now,
+									  targettime,
+									  &secs,
+									  &usecs);
+				if (secs <= 0)
+					timeout.tv_sec = 1; /* Always sleep at least 1 sec */
+				else
+					timeout.tv_sec = secs;
+				timeout.tv_usec = usecs;
+				timeoutptr = &timeout;
+			}
+			else
+				timeoutptr = NULL;
+
+			r = select(PQsocket(conn) + 1, &input_mask, NULL, NULL, timeoutptr);
+			if (r == 0 || (r < 0 && errno == EINTR))
+			{
+				/*
+				 * Got a timeout or signal. Continue the loop and either
+				 * deliver a status packet to the server or just go back into
+				 * blocking.
+				 */
+				continue;
+			}
+			else if (r < 0)
+			{
+				fprintf(stderr, _("%s: select() failed: %s\n"),
+						progname, strerror(errno));
+				goto error;
+			}
+			/* Else there is actually data on the socket */
+			if (PQconsumeInput(conn) == 0)
+			{
+				fprintf(stderr,
+						_("%s: could not receive data from WAL stream: %s"),
+						progname, PQerrorMessage(conn));
+				goto error;
+			}
+			continue;
+		}
+		if (r == -1)
+			/* End of copy stream */
+			break;
+		if (r == -2)
+		{
+			fprintf(stderr, _("%s: could not read COPY data: %s"),
+					progname, PQerrorMessage(conn));
+			goto error;
+		}
+
+		/* Check the message type. */
+		if (copybuf[0] == 'k')
+		{
+			int			pos;
+			bool		replyRequested;
+
+			/*
+			 * Parse the keepalive message, enclosed in the CopyData message.
+			 * We just check if the server requested a reply, and ignore the
+			 * rest.
+			 */
+			pos = 1;			/* skip msgtype 'k' */
+			pos += 8;			/* skip walEnd */
+			pos += 8;			/* skip sendTime */
+
+			if (r < pos + 1)
+			{
+				fprintf(stderr, _("%s: streaming header too small: %d\n"),
+						progname, r);
+				goto error;
+			}
+			replyRequested = copybuf[pos];
+
+			/* If the server requested an immediate reply, send one. */
+			if (replyRequested)
+			{
+				now = feGetCurrentTimestamp();
+				if (!sendFeedback(conn, logoff, now, false, false))
+					goto error;
+				last_status = now;
+			}
+			continue;
+		}
+		else if (copybuf[0] != 'w')
+		{
+			fprintf(stderr, _("%s: unrecognized streaming header: \"%c\"\n"),
+					progname, copybuf[0]);
+			goto error;
+		}
+
+
+		/*
+		 * Read the header of the XLogData message, enclosed in the CopyData
+		 * message. We only need the WAL location field (dataStart), the rest
+		 * of the header is ignored.
+		 */
+		hdr_len = 1;			/* msgtype 'w' */
+		hdr_len += 8;			/* dataStart */
+		hdr_len += 8;			/* walEnd */
+		hdr_len += 8;			/* sendTime */
+		if (r < hdr_len + 1)
+		{
+			fprintf(stderr, _("%s: streaming header too small: %d\n"),
+					progname, r);
+			goto error;
+		}
+
+		/* Extract WAL location for this block */
+		{
+			XLogRecPtr	temp = fe_recvint64(&copybuf[1]);
+
+			logoff = Max(temp, logoff);
+		}
+
+		if (outfd == -1 && strcmp(outfile, "-") == 0)
+		{
+			outfd = fileno(stdout);
+		}
+		else if (outfd == -1)
+		{
+			outfd = open(outfile, O_CREAT | O_APPEND | O_WRONLY | PG_BINARY,
+						 S_IRUSR | S_IWUSR);
+			if (outfd == -1)
+			{
+				fprintf(stderr,
+						_("%s: could not open log file \"%s\": %s\n"),
+						progname, outfile, strerror(errno));
+				goto error;
+			}
+		}
+
+		bytes_left = r - hdr_len;
+		bytes_written = 0;
+
+
+		while (bytes_left)
+		{
+			int			ret;
+
+			ret = write(outfd,
+						copybuf + hdr_len + bytes_written,
+						bytes_left);
+
+			if (ret < 0)
+			{
+				fprintf(stderr,
+				  _("%s: could not write %u bytes to log file \"%s\": %s\n"),
+						progname, bytes_left, outfile,
+						strerror(errno));
+				goto error;
+			}
+
+			/* Write was successful, advance our position */
+			bytes_written += ret;
+			bytes_left -= ret;
+		}
+
+		if (write(outfd, "\n", 1) != 1)
+		{
+			fprintf(stderr,
+				  _("%s: could not write %u bytes to log file \"%s\": %s\n"),
+					progname, 1, outfile,
+					strerror(errno));
+			goto error;
+		}
+	}
+
+	res = PQgetResult(conn);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr,
+				_("%s: unexpected termination of replication stream: %s"),
+				progname, PQresultErrorMessage(res));
+		goto error;
+	}
+	PQclear(res);
+
+	if (copybuf != NULL)
+		PQfreemem(copybuf);
+
+	if (outfd != -1 && close(outfd) != 0)
+		fprintf(stderr, _("%s: could not close file \"%s\": %s\n"),
+				progname, outfile, strerror(errno));
+	outfd = -1;
+error:
+	PQfinish(conn);
+	conn = NULL;
+}
+
+/*
+ * When sigint is called, just tell the system to exit at the next possible
+ * moment.
+ */
+#ifndef WIN32
+
+static void
+sigint_handler(int signum)
+{
+	time_to_abort = true;
+}
+#endif
+
+int
+main(int argc, char **argv)
+{
+	PGresult   *res;
+	static struct option long_options[] = {
+/* general options */
+		{"file", required_argument, NULL, 'f'},
+		{"no-loop", no_argument, NULL, 'n'},
+		{"verbose", no_argument, NULL, 'v'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, '?'},
+/* connnection options */
+		{"database", required_argument, NULL, 'd'},
+		{"host", required_argument, NULL, 'h'},
+		{"port", required_argument, NULL, 'p'},
+		{"username", required_argument, NULL, 'U'},
+		{"no-password", no_argument, NULL, 'w'},
+		{"password", no_argument, NULL, 'W'},
+/* replication options */
+		{"option", required_argument, NULL, 'o'},
+		{"plugin", required_argument, NULL, 'P'},
+		{"status-interval", required_argument, NULL, 's'},
+		{"slot", required_argument, NULL, 'S'},
+		{"startpos", required_argument, NULL, 'I'},
+/* action */
+		{"init", no_argument, NULL, 1},
+		{"start", no_argument, NULL, 2},
+		{"stop", no_argument, NULL, 3},
+		{NULL, 0, NULL, 0}
+	};
+	int			c;
+	int			option_index;
+	uint32		hi,
+				lo;
+
+	progname = get_progname(argv[0]);
+	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_receivellog"));
+
+	if (argc > 1)
+	{
+		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+		{
+			usage();
+			exit(0);
+		}
+		else if (strcmp(argv[1], "-V") == 0 ||
+				 strcmp(argv[1], "--version") == 0)
+		{
+			puts("pg_receivellog (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
+	}
+
+	while ((c = getopt_long(argc, argv, "f:nvd:h:o:p:U:wWP:s:S:",
+							long_options, &option_index)) != -1)
+	{
+		switch (c)
+		{
+/* general options */
+			case 'f':
+				outfile = pg_strdup(optarg);
+				break;
+			case 'n':
+				noloop = 1;
+				break;
+			case 'v':
+				verbose++;
+				break;
+/* connnection options */
+			case 'd':
+				dbname = pg_strdup(optarg);
+				break;
+			case 'h':
+				dbhost = pg_strdup(optarg);
+				break;
+			case 'p':
+				if (atoi(optarg) <= 0)
+				{
+					fprintf(stderr, _("%s: invalid port number \"%s\"\n"),
+							progname, optarg);
+					exit(1);
+				}
+				dbport = pg_strdup(optarg);
+				break;
+			case 'U':
+				dbuser = pg_strdup(optarg);
+				break;
+			case 'w':
+				dbgetpassword = -1;
+				break;
+			case 'W':
+				dbgetpassword = 1;
+				break;
+/* replication options */
+			case 'o':
+				{
+					char *data = pg_strdup(optarg);
+					char *val = strchr(data, '=');
+
+					if (val != NULL)
+					{
+						/* remove =; separate data from val */
+						*val = '\0';
+						val++;
+					}
+
+					noptions += 1;
+					options = pg_realloc(options, sizeof(char*) * noptions * 2);
+
+					options[(noptions - 1) * 2] = data;
+					options[(noptions - 1) * 2 + 1] = val;
+				}
+
+				break;
+			case 'P':
+				plugin = pg_strdup(optarg);
+				break;
+			case 's':
+				standby_message_timeout = atoi(optarg) * 1000;
+				if (standby_message_timeout < 0)
+				{
+					fprintf(stderr, _("%s: invalid status interval \"%s\"\n"),
+							progname, optarg);
+					exit(1);
+				}
+				break;
+			case 'S':
+				slot = pg_strdup(optarg);
+				break;
+			case 'I':
+				if (sscanf(optarg, "%X/%X", &hi, &lo) != 2)
+				{
+					fprintf(stderr,
+							_("%s: could not parse start position \"%s\"\n"),
+							progname, optarg);
+					exit(1);
+				}
+				startpos = ((uint64) hi) << 32 | lo;
+				break;
+/* action */
+			case 1:
+				do_init_slot = true;
+				break;
+			case 2:
+				do_start_slot = true;
+				break;
+			case 3:
+				do_stop_slot = true;
+				break;
+
+			default:
+
+				/*
+				 * getopt_long already emitted a complaint
+				 */
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+						progname);
+				exit(1);
+		}
+	}
+
+	/*
+	 * Any non-option arguments?
+	 */
+	if (optind < argc)
+	{
+		fprintf(stderr,
+				_("%s: too many command-line arguments (first is \"%s\")\n"),
+				progname, argv[optind]);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	/*
+	 * Required arguments
+	 */
+	if (slot == NULL)
+	{
+		fprintf(stderr, _("%s: no slot specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (!do_stop_slot && outfile == NULL)
+	{
+		fprintf(stderr, _("%s: no target file specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (!do_stop_slot && dbname == NULL)
+	{
+		fprintf(stderr, _("%s: no database specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (!do_stop_slot && !do_init_slot && !do_start_slot)
+	{
+		fprintf(stderr, _("%s: at least one action needs to be specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (do_stop_slot && (do_init_slot || do_start_slot))
+	{
+		fprintf(stderr, _("%s: --stop cannot be combined with --init or --start\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (startpos && (do_init_slot || do_stop_slot))
+	{
+		fprintf(stderr, _("%s: --startpos cannot be combined with --init or --stop\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+#ifndef WIN32
+	pqsignal(SIGINT, sigint_handler);
+#endif
+
+	/*
+	 * don't really need this but it actually helps to get more precise error
+	 * messages about authentication, required GUCs and such without starting
+	 * to loop around connection attempts lateron.
+	 */
+	{
+		conn = GetConnection();
+		if (!conn)
+			/* Error message already written in GetConnection() */
+			exit(1);
+
+		/*
+		 * Run IDENTIFY_SYSTEM so we can get the timeline and current xlog
+		 * position.
+		 */
+		res = PQexec(conn, "IDENTIFY_SYSTEM");
+		if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		{
+			fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
+					progname, "IDENTIFY_SYSTEM", PQerrorMessage(conn));
+			disconnect_and_exit(1);
+		}
+
+		if (PQntuples(res) != 1 || PQnfields(res) != 4)
+		{
+			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, 4);
+			disconnect_and_exit(1);
+		}
+		PQclear(res);
+	}
+
+
+	/*
+	 * stop a replication slot
+	 */
+	if (do_stop_slot)
+	{
+		char		query[256];
+
+		if (verbose)
+			fprintf(stderr,
+					_("%s: init replication slot \"%s\"\n"),
+					progname, slot);
+
+		snprintf(query, sizeof(query), "FREE_LOGICAL_REPLICATION \"%s\"",
+				 slot);
+		res = PQexec(conn, query);
+		if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
+					progname, query, PQerrorMessage(conn));
+			disconnect_and_exit(1);
+		}
+
+		if (PQntuples(res) != 0 || PQnfields(res) != 0)
+		{
+			fprintf(stderr,
+					_("%s: could not stop logical rep: got %d rows and %d fields, expected %d rows and %d fields\n"),
+					progname, PQntuples(res), PQnfields(res), 0, 0);
+			disconnect_and_exit(1);
+		}
+
+		PQclear(res);
+		disconnect_and_exit(0);
+	}
+
+	/*
+	 * init a replication slot
+	 */
+	if (do_init_slot)
+	{
+		char		query[256];
+
+		if (verbose)
+			fprintf(stderr,
+					_("%s: init replication slot \"%s\"\n"),
+					progname, slot);
+
+		snprintf(query, sizeof(query), "INIT_LOGICAL_REPLICATION \"%s\" \"%s\"",
+				 slot, plugin);
+
+		res = PQexec(conn, query);
+		if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		{
+			fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
+					progname, query, PQerrorMessage(conn));
+			disconnect_and_exit(1);
+		}
+
+		if (PQntuples(res) != 1 || PQnfields(res) != 4)
+		{
+			fprintf(stderr,
+					_("%s: could not init logical rep: got %d rows and %d fields, expected %d rows and %d fields\n"),
+					progname, PQntuples(res), PQnfields(res), 1, 4);
+			disconnect_and_exit(1);
+		}
+
+		if (sscanf(PQgetvalue(res, 0, 1), "%X/%X", &hi, &lo) != 2)
+		{
+			fprintf(stderr,
+					_("%s: could not parse log location \"%s\"\n"),
+					progname, PQgetvalue(res, 0, 1));
+			disconnect_and_exit(1);
+		}
+		startpos = ((uint64) hi) << 32 | lo;
+
+		slot = strdup(PQgetvalue(res, 0, 0));
+		PQclear(res);
+	}
+
+
+	if (!do_start_slot)
+		disconnect_and_exit(0);
+
+	while (true)
+	{
+		StreamLog();
+		if (time_to_abort)
+		{
+			/*
+			 * We've been Ctrl-C'ed. That's not an error, so exit without an
+			 * errorcode.
+			 */
+			disconnect_and_exit(0);
+		}
+		else if (noloop)
+		{
+			fprintf(stderr, _("%s: disconnected.\n"), progname);
+			exit(1);
+		}
+		else
+		{
+			fprintf(stderr,
+			/* translator: check source for value for %d */
+					_("%s: disconnected. Waiting %d seconds to try again.\n"),
+					progname, RECONNECT_SLEEP_TIME);
+			pg_usleep(RECONNECT_SLEEP_TIME * 1000000);
+		}
+	}
+}
diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c
index 22a5340..f027e1e 100644
--- a/src/bin/pg_basebackup/receivelog.c
+++ b/src/bin/pg_basebackup/receivelog.c
@@ -11,21 +11,18 @@
  *		  src/bin/pg_basebackup/receivelog.c
  *-------------------------------------------------------------------------
  */
+
 #include "postgres_fe.h"
 
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <unistd.h>
-/* for ntohl/htonl */
-#include <netinet/in.h>
-#include <arpa/inet.h>
+/* local includes */
+#include "receivelog.h"
+#include "streamutil.h"
 
 #include "libpq-fe.h"
 #include "access/xlog_internal.h"
 
-#include "receivelog.h"
-#include "streamutil.h"
+#include <sys/stat.h>
+#include <unistd.h>
 
 
 /* fd and filename for currently open WAL file */
@@ -193,63 +190,6 @@ close_walfile(char *basedir, char *partial_suffix)
 
 
 /*
- * Local version of GetCurrentTimestamp(), since we are not linked with
- * backend code. The protocol always uses integer timestamps, regardless of
- * server setting.
- */
-static int64
-localGetCurrentTimestamp(void)
-{
-	int64		result;
-	struct timeval tp;
-
-	gettimeofday(&tp, NULL);
-
-	result = (int64) tp.tv_sec -
-		((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
-
-	result = (result * USECS_PER_SEC) + tp.tv_usec;
-
-	return result;
-}
-
-/*
- * Local version of TimestampDifference(), since we are not linked with
- * backend code.
- */
-static void
-localTimestampDifference(int64 start_time, int64 stop_time,
-						 long *secs, int *microsecs)
-{
-	int64		diff = stop_time - start_time;
-
-	if (diff <= 0)
-	{
-		*secs = 0;
-		*microsecs = 0;
-	}
-	else
-	{
-		*secs = (long) (diff / USECS_PER_SEC);
-		*microsecs = (int) (diff % USECS_PER_SEC);
-	}
-}
-
-/*
- * Local version of TimestampDifferenceExceeds(), since we are not
- * linked with backend code.
- */
-static bool
-localTimestampDifferenceExceeds(int64 start_time,
-								int64 stop_time,
-								int msec)
-{
-	int64		diff = stop_time - start_time;
-
-	return (diff >= msec * INT64CONST(1000));
-}
-
-/*
  * Check if a timeline history file exists.
  */
 static bool
@@ -369,47 +309,6 @@ writeTimeLineHistoryFile(char *basedir, TimeLineID tli, char *filename, char *co
 }
 
 /*
- * Converts an int64 to network byte order.
- */
-static void
-sendint64(int64 i, char *buf)
-{
-	uint32		n32;
-
-	/* High order half first, since we're doing MSB-first */
-	n32 = (uint32) (i >> 32);
-	n32 = htonl(n32);
-	memcpy(&buf[0], &n32, 4);
-
-	/* Now the low order half */
-	n32 = (uint32) i;
-	n32 = htonl(n32);
-	memcpy(&buf[4], &n32, 4);
-}
-
-/*
- * Converts an int64 from network byte order to native format.
- */
-static int64
-recvint64(char *buf)
-{
-	int64		result;
-	uint32		h32;
-	uint32		l32;
-
-	memcpy(&h32, buf, 4);
-	memcpy(&l32, buf + 4, 4);
-	h32 = ntohl(h32);
-	l32 = ntohl(l32);
-
-	result = h32;
-	result <<= 32;
-	result |= l32;
-
-	return result;
-}
-
-/*
  * Send a Standby Status Update message to server.
  */
 static bool
@@ -420,13 +319,13 @@ sendFeedback(PGconn *conn, XLogRecPtr blockpos, int64 now, bool replyRequested)
 
 	replybuf[len] = 'r';
 	len += 1;
-	sendint64(blockpos, &replybuf[len]);		/* write */
+	fe_sendint64(blockpos, &replybuf[len]);		/* write */
 	len += 8;
-	sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* flush */
+	fe_sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* flush */
 	len += 8;
-	sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* apply */
+	fe_sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* apply */
 	len += 8;
-	sendint64(now, &replybuf[len]);		/* sendTime */
+	fe_sendint64(now, &replybuf[len]);		/* sendTime */
 	len += 8;
 	replybuf[len] = replyRequested ? 1 : 0;		/* replyRequested */
 	len += 1;
@@ -828,9 +727,9 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 		/*
 		 * Potentially send a status message to the master
 		 */
-		now = localGetCurrentTimestamp();
+		now = feGetCurrentTimestamp();
 		if (still_sending && standby_message_timeout > 0 &&
-			localTimestampDifferenceExceeds(last_status, now,
+			feTimestampDifferenceExceeds(last_status, now,
 											standby_message_timeout))
 		{
 			/* Time to send feedback! */
@@ -859,10 +758,10 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 				int			usecs;
 
 				targettime = last_status + (standby_message_timeout - 1) * ((int64) 1000);
-				localTimestampDifference(now,
-										 targettime,
-										 &secs,
-										 &usecs);
+				feTimestampDifference(now,
+									  targettime,
+									  &secs,
+									  &usecs);
 				if (secs <= 0)
 					timeout.tv_sec = 1; /* Always sleep at least 1 sec */
 				else
@@ -966,7 +865,7 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 			/* If the server requested an immediate reply, send one. */
 			if (replyRequested && still_sending)
 			{
-				now = localGetCurrentTimestamp();
+				now = feGetCurrentTimestamp();
 				if (!sendFeedback(conn, blockpos, now, false))
 					goto error;
 				last_status = now;
@@ -996,7 +895,7 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 						progname, r);
 				goto error;
 			}
-			blockpos = recvint64(&copybuf[1]);
+			blockpos = fe_recvint64(&copybuf[1]);
 
 			/* Extract WAL location for this block */
 			xlogoff = blockpos % XLOG_SEG_SIZE;
diff --git a/src/bin/pg_basebackup/receivelog.h b/src/bin/pg_basebackup/receivelog.h
index 7c983cd..f4789a5 100644
--- a/src/bin/pg_basebackup/receivelog.h
+++ b/src/bin/pg_basebackup/receivelog.h
@@ -1,3 +1,5 @@
+#include "libpq-fe.h"
+
 #include "access/xlogdefs.h"
 
 /*
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1dfb80f..c8d436d 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -11,17 +11,35 @@
  *-------------------------------------------------------------------------
  */
 
-#include "postgres_fe.h"
+/*
+ * We have to use postgres.h not postgres_fe.h here, because there's
+ * backend-only stuff in the datetime include files we need.  But we need a
+ * frontend-ish environment otherwise. Hence this ugly hack.
+ */
+#define FRONTEND 1
+#include "postgres.h"
+
 #include "streamutil.h"
 
+#include "common/fe_memutils.h"
+#include "utils/datetime.h"
+
 #include <stdio.h>
 #include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* for ntohl/htonl */
+#include <netinet/in.h>
+#include <arpa/inet.h>
 
 const char *progname;
 char	   *connection_string = NULL;
 char	   *dbhost = NULL;
 char	   *dbuser = NULL;
 char	   *dbport = NULL;
+char	   *dbname = NULL;
 int			dbgetpassword = 0;	/* 0=auto, -1=never, 1=always */
 static char *dbpassword = NULL;
 PGconn	   *conn = NULL;
@@ -86,10 +104,10 @@ GetConnection(void)
 	}
 
 	keywords[i] = "dbname";
-	values[i] = "replication";
+	values[i] = dbname == NULL ? "replication" : dbname;
 	i++;
 	keywords[i] = "replication";
-	values[i] = "true";
+	values[i] = dbname == NULL ? "true" : "database";
 	i++;
 	keywords[i] = "fallback_application_name";
 	values[i] = progname;
@@ -210,3 +228,102 @@ GetConnection(void)
 		return tmpconn;
 	}
 }
+
+
+/*
+ * Frontend version of GetCurrentTimestamp(), since we are not linked with
+ * backend code. The protocol always uses integer timestamps, regardless of
+ * server setting.
+ */
+int64
+feGetCurrentTimestamp(void)
+{
+	int64		result;
+	struct timeval tp;
+
+	gettimeofday(&tp, NULL);
+
+	result = (int64) tp.tv_sec -
+		((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
+
+	result = (result * USECS_PER_SEC) + tp.tv_usec;
+
+	return result;
+}
+
+/*
+ * Frontend version of TimestampDifference(), since we are not linked with
+ * backend code.
+ */
+void
+feTimestampDifference(int64 start_time, int64 stop_time,
+						 long *secs, int *microsecs)
+{
+	int64		diff = stop_time - start_time;
+
+	if (diff <= 0)
+	{
+		*secs = 0;
+		*microsecs = 0;
+	}
+	else
+	{
+		*secs = (long) (diff / USECS_PER_SEC);
+		*microsecs = (int) (diff % USECS_PER_SEC);
+	}
+}
+
+/*
+ * Frontend version of TimestampDifferenceExceeds(), since we are not
+ * linked with backend code.
+ */
+bool
+feTimestampDifferenceExceeds(int64 start_time,
+								int64 stop_time,
+								int msec)
+{
+	int64		diff = stop_time - start_time;
+
+	return (diff >= msec * INT64CONST(1000));
+}
+
+/*
+ * Converts an int64 to network byte order.
+ */
+void
+fe_sendint64(int64 i, char *buf)
+{
+	uint32		n32;
+
+	/* High order half first, since we're doing MSB-first */
+	n32 = (uint32) (i >> 32);
+	n32 = htonl(n32);
+	memcpy(&buf[0], &n32, 4);
+
+	/* Now the low order half */
+	n32 = (uint32) i;
+	n32 = htonl(n32);
+	memcpy(&buf[4], &n32, 4);
+}
+
+/*
+ * Converts an int64 from network byte order to native format.
+ */
+int64
+fe_recvint64(char *buf)
+{
+	int64		result;
+	uint32		h32;
+	uint32		l32;
+
+	memcpy(&h32, buf, 4);
+	memcpy(&l32, buf + 4, 4);
+	h32 = ntohl(h32);
+	l32 = ntohl(l32);
+
+	result = h32;
+	result <<= 32;
+	result |= l32;
+
+	return result;
+}
diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h
index 77d6b86..4286df8 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -5,6 +5,7 @@ extern char *connection_string;
 extern char *dbhost;
 extern char *dbuser;
 extern char *dbport;
+extern char *dbname;
 extern int	dbgetpassword;
 
 /* Connection kept global so we can disconnect easily */
@@ -17,3 +18,12 @@ extern PGconn *conn;
 	}
 
 extern PGconn *GetConnection(void);
+
+extern int64 feGetCurrentTimestamp(void);
+extern void feTimestampDifference(int64 start_time, int64 stop_time,
+									 long *secs, int *microsecs);
+
+extern bool feTimestampDifferenceExceeds(int64 start_time, int64 stop_time,
+											int msec);
+extern void fe_sendint64(int64 i, char *buf);
+extern int64 fe_recvint64(char *buf);
-- 
1.8.4.21.g992c386.dirty

0007-wal_decoding-test_logical_decoding-Add-extension-for.patchtext/x-patch; charset=us-asciiDownload
From 94bf1588665cd89dde0135b877614ca22a2104dd Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 19 Aug 2013 13:24:31 +0200
Subject: [PATCH 7/8] wal_decoding: test_logical_decoding: Add extension for
 easier testing of logical decoding

This extension provides three functions for manipulating replication slots:
* init_logical_replication - initiate a replication slot and wait for consistent state
* start_logical_replication - return all changes since the last call up to now, without blocking
* free_logical_replication - free the logical slot again

Those are pretty direct synonyms for the replication connection commands.

Due to questions about how to integrate logical replication tests this module
also contains the current tests of logical replication itself.

Author: Abhijit Menon-Sen
---
 contrib/Makefile                                   |   1 +
 contrib/test_logical_decoding/Makefile             |  33 ++
 contrib/test_logical_decoding/expected/ddl.out     | 625 +++++++++++++++++++++
 contrib/test_logical_decoding/expected/rewrite.out |  70 +++
 contrib/test_logical_decoding/logical.conf         |   2 +
 contrib/test_logical_decoding/sql/ddl.sql          | 316 +++++++++++
 contrib/test_logical_decoding/sql/rewrite.sql      |  29 +
 .../test_logical_decoding--1.0.sql                 |   6 +
 .../test_logical_decoding/test_logical_decoding.c  | 238 ++++++++
 .../test_logical_decoding.control                  |   5 +
 10 files changed, 1325 insertions(+)
 create mode 100644 contrib/test_logical_decoding/Makefile
 create mode 100644 contrib/test_logical_decoding/expected/ddl.out
 create mode 100644 contrib/test_logical_decoding/expected/rewrite.out
 create mode 100644 contrib/test_logical_decoding/logical.conf
 create mode 100644 contrib/test_logical_decoding/sql/ddl.sql
 create mode 100644 contrib/test_logical_decoding/sql/rewrite.sql
 create mode 100644 contrib/test_logical_decoding/test_logical_decoding--1.0.sql
 create mode 100644 contrib/test_logical_decoding/test_logical_decoding.c
 create mode 100644 contrib/test_logical_decoding/test_logical_decoding.control

diff --git a/contrib/Makefile b/contrib/Makefile
index 6d2fe32..41cb892 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -51,6 +51,7 @@ SUBDIRS = \
 		tcn		\
 		test_parser	\
 		test_decoding	\
+		test_logical_decoding \
 		tsearch2	\
 		unaccent	\
 		vacuumlo	\
diff --git a/contrib/test_logical_decoding/Makefile b/contrib/test_logical_decoding/Makefile
new file mode 100644
index 0000000..f1990d3
--- /dev/null
+++ b/contrib/test_logical_decoding/Makefile
@@ -0,0 +1,33 @@
+MODULE_big = test_logical_decoding
+OBJS = test_logical_decoding.o
+
+EXTENSION = test_logical_decoding
+DATA = test_logical_decoding--1.0.sql
+
+# Note: because we don't tell the Makefile there are any regression tests,
+# we have to clean those result files explicitly
+EXTRA_CLEAN = -r $(pg_regress_clean_files)
+
+subdir = contrib/test_logical_decoding
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+
+# Disabled because these tests require "wal_level=logical", which
+# typical installcheck users do not have (e.g. buildfarm clients).
+installcheck:;
+
+submake-regress:
+	$(MAKE) -C $(top_builddir)/src/test/regress
+
+submake-test_decoding:
+	$(MAKE) -C $(top_builddir)/contrib/test_decoding
+
+check: all | submake-regress submake-test_decoding
+	$(pg_regress_check) --temp-config $(top_srcdir)/contrib/test_logical_decoding/logical.conf \
+	    --temp-install=./tmp_check \
+	    --extra-install=contrib/test_decoding \
+	    --extra-install=contrib/test_logical_decoding \
+	    ddl rewrite
+
+PHONY: submake-test_decoding submake-regress
diff --git a/contrib/test_logical_decoding/expected/ddl.out b/contrib/test_logical_decoding/expected/ddl.out
new file mode 100644
index 0000000..c161a43
--- /dev/null
+++ b/contrib/test_logical_decoding/expected/ddl.out
@@ -0,0 +1,625 @@
+CREATE EXTENSION test_logical_decoding;
+-- predictability
+SET synchronous_commit = on;
+-- faster startup
+CHECKPOINT;
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+-- fail because of an already existing slot
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ERROR:  There already is a logical slot named "regression_slot"
+-- succeed once
+SELECT stop_logical_replication('regression_slot');
+ stop_logical_replication 
+--------------------------
+                        0
+(1 row)
+
+-- fail
+SELECT stop_logical_replication('regression_slot');
+ERROR:  couldn't find logical slot "regression_slot"
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+/* check whether status function reports us, only reproduceable columns */
+SELECT slot_name, plugin, active,
+    xmin::xid IS NOT NULL,
+    pg_xlog_location_diff(restart_decoding_lsn, '0/01000000') > 0
+FROM pg_stat_logical_decoding;
+    slot_name    |    plugin     | active | ?column? | ?column? 
+-----------------+---------------+--------+----------+----------
+ regression_slot | test_decoding | f      | t        | t
+(1 row)
+
+/*
+ * Check that changes are handled correctly when interleaved with ddl
+ */
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+COMMIT;
+ALTER TABLE replication_example ADD COLUMN bar int;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4);
+BEGIN;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL);
+COMMIT;
+ALTER TABLE replication_example DROP COLUMN bar;
+INSERT INTO replication_example(somedata, text) VALUES (3, 1);
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (3, 2);
+INSERT INTO replication_example(somedata, text) VALUES (3, 3);
+COMMIT;
+ALTER TABLE replication_example RENAME COLUMN text TO somenum;
+INSERT INTO replication_example(somedata, somenum) VALUES (4, 1);
+-- collect all changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                               data                                                
+---------------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:1 somedata[int4]:1 text[varchar]:1
+ table "replication_example": INSERT: id[int4]:2 somedata[int4]:1 text[varchar]:2
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:3 somedata[int4]:2 text[varchar]:1 bar[int4]:4
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:4 somedata[int4]:2 text[varchar]:2 bar[int4]:4
+ table "replication_example": INSERT: id[int4]:5 somedata[int4]:2 text[varchar]:3 bar[int4]:4
+ table "replication_example": INSERT: id[int4]:6 somedata[int4]:2 text[varchar]:4 bar[int4]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:7 somedata[int4]:3 text[varchar]:1
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:8 somedata[int4]:3 text[varchar]:2
+ table "replication_example": INSERT: id[int4]:9 somedata[int4]:3 text[varchar]:3
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:10 somedata[int4]:4 somenum[varchar]:1
+ COMMIT
+(30 rows)
+
+ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING (somenum::int4);
+-- throw away changes, they contain oids
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ count 
+-------
+    12
+(1 row)
+
+INSERT INTO replication_example(somedata, somenum) VALUES (5, 1);
+BEGIN;
+INSERT INTO replication_example(somedata, somenum) VALUES (6, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod1 int;
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 2, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod2 int;
+INSERT INTO replication_example(somedata, somenum, zaphod2) VALUES (6, 3, 1);
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 4, 2);
+COMMIT;
+/*
+ * check whether the correct indexes are chosen for deletions
+ */
+CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
+INSERT INTO tr_unique(data) VALUES(10);
+--show deletion with unique index
+DELETE FROM tr_unique;
+ALTER TABLE tr_unique RENAME TO tr_pkey;
+-- show changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                          data                                                          
+------------------------------------------------------------------------------------------------------------------------
+ BEGIN
+ table "replication_example": INSERT: id[int4]:11 somedata[int4]:5 somenum[int4]:1
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:12 somedata[int4]:6 somenum[int4]:1
+ table "replication_example": INSERT: id[int4]:13 somedata[int4]:6 somenum[int4]:2 zaphod1[int4]:1
+ table "replication_example": INSERT: id[int4]:14 somedata[int4]:6 somenum[int4]:3 zaphod1[int4]:(null) zaphod2[int4]:1
+ table "replication_example": INSERT: id[int4]:15 somedata[int4]:6 somenum[int4]:4 zaphod1[int4]:2 zaphod2[int4]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "tr_unique": INSERT: id2[int4]:1 data[int4]:10
+ COMMIT
+ BEGIN
+ table "tr_unique": DELETE: id2[int4]:1
+ COMMIT
+ BEGIN
+ COMMIT
+(19 rows)
+
+-- hide changes bc of oid visible in full table rewrites
+ALTER TABLE tr_pkey ADD COLUMN id serial primary key;
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ count 
+-------
+     2
+(1 row)
+
+INSERT INTO tr_pkey(data) VALUES(1);
+--show deletion with primary key
+DELETE FROM tr_pkey;
+/* display results */
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                             data                             
+--------------------------------------------------------------
+ BEGIN
+ table "tr_pkey": INSERT: id2[int4]:2 data[int4]:1 id[int4]:1
+ COMMIT
+ BEGIN
+ table "tr_pkey": DELETE: id[int4]:1
+ COMMIT
+(6 rows)
+
+/*
+ * check that disk spooling works
+ */
+BEGIN;
+CREATE TABLE tr_etoomuch (id serial primary key, data int);
+INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i);
+DELETE FROM tr_etoomuch WHERE id < 5000;
+UPDATE tr_etoomuch SET data = - data WHERE id > 5000;
+COMMIT;
+/* display results, but hide most of the output */
+SELECT count(*), min(data), max(data)
+FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1')
+GROUP BY substring(data, 1, 24)
+ORDER BY 1;
+ count |                              min                              |                             max                             
+-------+---------------------------------------------------------------+-------------------------------------------------------------
+     1 | COMMIT                                                        | COMMIT
+     1 | BEGIN                                                         | BEGIN
+  4999 | table "tr_etoomuch": DELETE: id[int4]:1                       | table "tr_etoomuch": DELETE: id[int4]:999
+  5234 | table "tr_etoomuch": UPDATE: id[int4]:10000 data[int4]:-10000 | table "tr_etoomuch": UPDATE: id[int4]:9999 data[int4]:-9999
+ 10234 | table "tr_etoomuch": INSERT: id[int4]:10000 data[int4]:10000  | table "tr_etoomuch": INSERT: id[int4]:9 data[int4]:9
+(5 rows)
+
+/*
+ * check whether we subtransactions correctly in relation with each other
+ */
+CREATE TABLE tr_sub (id serial primary key, path text);
+-- toplevel, subtxn, toplevel, subtxn, subtxn
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('1-top-#1');
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#2');
+RELEASE SAVEPOINT a;
+SAVEPOINT b;
+SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#2');
+RELEASE SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-#1');
+RELEASE SAVEPOINT b;
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                            data                            
+------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "tr_sub": INSERT: id[int4]:1 path[text]:1-top-#1
+ table "tr_sub": INSERT: id[int4]:2 path[text]:1-top-1-#1
+ table "tr_sub": INSERT: id[int4]:3 path[text]:1-top-1-#2
+ table "tr_sub": INSERT: id[int4]:4 path[text]:1-top-2-1-#1
+ table "tr_sub": INSERT: id[int4]:5 path[text]:1-top-2-1-#2
+ table "tr_sub": INSERT: id[int4]:6 path[text]:1-top-2-#1
+ COMMIT
+(10 rows)
+
+-- check that we handle xlog assignments correctly
+BEGIN;
+-- nest 80 subtxns
+SAVEPOINT subtop;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+-- assign xid by inserting
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#1');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#2');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#3');
+RELEASE SAVEPOINT subtop;
+INSERT INTO tr_sub(path) VALUES ('2-top-#1');
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                             data                             
+--------------------------------------------------------------
+ BEGIN
+ table "tr_sub": INSERT: id[int4]:7 path[text]:2-top-1...--#1
+ table "tr_sub": INSERT: id[int4]:8 path[text]:2-top-1...--#2
+ table "tr_sub": INSERT: id[int4]:9 path[text]:2-top-1...--#3
+ table "tr_sub": INSERT: id[int4]:10 path[text]:2-top-#1
+ COMMIT
+(6 rows)
+
+-- make sure rollbacked subtransactions aren't decoded
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#1');
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-1-#1');
+SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-2-#1');
+ROLLBACK TO SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#2');
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                            data                             
+-------------------------------------------------------------
+ BEGIN
+ table "tr_sub": INSERT: id[int4]:11 path[text]:3-top-2-#1
+ table "tr_sub": INSERT: id[int4]:12 path[text]:3-top-2-1-#1
+ table "tr_sub": INSERT: id[int4]:14 path[text]:3-top-2-#2
+ COMMIT
+(5 rows)
+
+-- test whether a known, but not yet logged toplevel xact, followed by a
+-- subxact commit is handled correctly
+BEGIN;
+SELECT txid_current() != 0; -- so no fixed xid apears in the outfile
+ ?column? 
+----------
+ t
+(1 row)
+
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('4-top-1-#1');
+RELEASE SAVEPOINT a;
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ data 
+------
+(0 rows)
+
+/*
+ * Check whether treating a table as a catalog table works somewhat
+ */
+CREATE TABLE replication_metadata (
+    id serial primary key,
+    relation name NOT NULL,
+    options text[]
+)
+WITH (treat_as_catalog_table = true)
+;
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+Options: treat_as_catalog_table=true
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('foo', ARRAY['a', 'b']);
+ALTER TABLE replication_metadata RESET (treat_as_catalog_table);
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('bar', ARRAY['a', 'b']);
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = true);
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+Options: treat_as_catalog_table=true
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('blub', NULL);
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = false);
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+Options: treat_as_catalog_table=false
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('zaphod', NULL);
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                             data                                             
+----------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:1 relation[name]:foo options[_text]:{a,b}
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:2 relation[name]:bar options[_text]:{a,b}
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:3 relation[name]:blub options[_text]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:4 relation[name]:zaphod options[_text]:(null)
+ COMMIT
+(20 rows)
+
+/*
+ * check whether we handle updates/deletes correct with & without a pkey
+ */
+/* we should handle the case without a key at all more gracefully */
+CREATE TABLE table_without_key(id serial, data int);
+INSERT INTO table_without_key(data) VALUES(1),(2);
+DELETE FROM table_without_key WHERE data = 1;
+UPDATE table_without_key SET data = 3 WHERE data = 2;
+UPDATE table_without_key SET id = -id;
+UPDATE table_without_key SET id = -id;
+DELETE FROM table_without_key WHERE data = 3;
+CREATE TABLE table_with_pkey(id serial primary key, data int);
+INSERT INTO table_with_pkey(data) VALUES(1), (2);
+DELETE FROM table_with_pkey WHERE data = 1;
+UPDATE table_with_pkey SET data = 3 WHERE data = 2;
+UPDATE table_with_pkey SET id = -id;
+UPDATE table_with_pkey SET id = -id;
+DELETE FROM table_with_pkey WHERE data = 3;
+CREATE TABLE table_with_unique(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id DROP NOT NULL;
+INSERT INTO table_with_unique(data) VALUES(1), (2);
+DELETE FROM table_with_unique WHERE data = 1;
+UPDATE table_with_unique SET data = 3 WHERE data = 2;
+UPDATE table_with_unique SET id = -id;
+UPDATE table_with_unique SET id = -id;
+DELETE FROM table_with_unique WHERE data = 3;
+CREATE TABLE table_with_unique_not_null(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id SET NOT NULL; --already set
+INSERT INTO table_with_unique_not_null(data) VALUES(1), (2);
+DELETE FROM table_with_unique_not_null WHERE data = 1;
+UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2;
+UPDATE table_with_unique_not_null SET id = -id;
+UPDATE table_with_unique_not_null SET id = -id;
+DELETE FROM table_with_unique_not_null WHERE data = 3;
+CREATE TABLE table_with_oid(id serial, data int) WITH oids;
+CREATE UNIQUE INDEX table_with_oid_oid ON table_with_oid(oid);
+INSERT INTO table_with_oid(data) VALUES(1), (2);
+DELETE FROM table_with_oid WHERE data = 1;
+UPDATE table_with_oid SET data = 3 WHERE data = 2;
+DELETE FROM table_with_oid WHERE data = 3;
+UPDATE table_with_oid SET id = -id;
+UPDATE table_with_oid SET id = -id;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                 data                                                 
+------------------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_without_key": INSERT: id[int4]:1 data[int4]:1
+ table "table_without_key": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_without_key": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ table "table_without_key": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_without_key": UPDATE: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_without_key": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_without_key": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_pkey": INSERT: id[int4]:1 data[int4]:1
+ table "table_with_pkey": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_pkey": DELETE: id[int4]:1
+ COMMIT
+ BEGIN
+ table "table_with_pkey": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_pkey": UPDATE: old-pkey: id[int4]:2 new-tuple: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_pkey": UPDATE: old-pkey: id[int4]:-2 new-tuple: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_pkey": DELETE: id[int4]:2
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_unique": INSERT: id[int4]:1 data[int4]:1
+ table "table_with_unique": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_unique": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ table "table_with_unique": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique": UPDATE: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": INSERT: id[int4]:1 data[int4]:1
+ table "table_with_unique_not_null": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": DELETE: id[int4]:1
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": UPDATE: old-pkey: id[int4]:2 new-tuple: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": UPDATE: old-pkey: id[int4]:-2 new-tuple: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": DELETE: id[int4]:2
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_oid": INSERT: oid[oid]:16484 id[int4]:1 data[int4]:1
+ table "table_with_oid": INSERT: oid[oid]:16485 id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_oid": DELETE: oid[oid]:16484
+ COMMIT
+ BEGIN
+ table "table_with_oid": UPDATE: oid[oid]:16485 id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_oid": DELETE: oid[oid]:16485
+ COMMIT
+(105 rows)
+
+-- check toast support
+SELECT setseed(0);
+ setseed 
+---------
+ 
+(1 row)
+
+CREATE TABLE toasttable(
+       id serial primary key,
+       toasted_col1 text,
+       rand1 float8 DEFAULT random(),
+       toasted_col2 text,
+       rand2 float8 DEFAULT random()
+       );
+-- uncompressed external toast data
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+-- compressed external toast data
+INSERT INTO toasttable(toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i);
+-- update of existing column
+UPDATE toasttable
+    SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "toasttable": INSERT: id[int4]:1 toasted_col1[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand1[float8]:0.840187716763467 toasted_col2[text]:(null) rand2[float8]:0.394382926635444
+ COMMIT
+ BEGIN
+ table "toasttable": INSERT: id[int4]:2 toasted_col1[text]:(null) rand1[float8]:0.783099223393947 toasted_col2[text]:0001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500 rand2[float8]:0.798440033104271
+ COMMIT
+ BEGIN
+ table "toasttable": UPDATE: id[int4]:1 toasted_col1[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand1[float8]:0.840187716763467 toasted_col2[text]:(null) rand2[float8]:0.394382926635444
+ COMMIT
+(11 rows)
+
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+-- update of second column, first column unchanged
+UPDATE toasttable
+    SET toasted_col2 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+-- make sure we decode correctly even if the toast table is gone
+DROP TABLE toasttable;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ BEGIN
+ table "toasttable": INSERT: id[int4]:3 toasted_col1[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand1[float8]:0.911647357512265 toasted_col2[text]:(null) rand2[float8]:0.197551369201392
+ COMMIT
+ BEGIN
+ table "toasttable": UPDATE: id[int4]:1 toasted_col1[text]:(unchanged-toast-datum) rand1[float8]:0.840187716763467 toasted_col2[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand2[float8]:0.394382926635444
+ COMMIT
+ BEGIN
+ COMMIT
+(8 rows)
+
+-- done, free logical replication slot
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ data 
+------
+(0 rows)
+
+SELECT stop_logical_replication('regression_slot');
+ stop_logical_replication 
+--------------------------
+                        0
+(1 row)
+
+/* check whether we aren't visible anymore now */
+SELECT * FROM pg_stat_logical_decoding;
+ slot_name | plugin | database | active | xmin | restart_decoding_lsn 
+-----------+--------+----------+--------+------+----------------------
+(0 rows)
+
diff --git a/contrib/test_logical_decoding/expected/rewrite.out b/contrib/test_logical_decoding/expected/rewrite.out
new file mode 100644
index 0000000..392e465
--- /dev/null
+++ b/contrib/test_logical_decoding/expected/rewrite.out
@@ -0,0 +1,70 @@
+CREATE EXTENSION test_logical_decoding;
+ERROR:  extension "test_logical_decoding" already exists
+-- predictability
+SET synchronous_commit = on;
+DROP TABLE IF EXISTS replication_example;
+-- faster startup
+CHECKPOINT;
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+INSERT INTO replication_example(somedata) VALUES (1);
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                         data                                          
+---------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:1 somedata[int4]:1 text[varchar]:(null)
+ COMMIT
+(5 rows)
+
+INSERT INTO replication_example(somedata) VALUES (2);
+VACUUM FULL pg_am;
+VACUUM FULL pg_amop;
+VACUUM FULL pg_proc;
+VACUUM FULL pg_opclass;
+VACUUM FULL pg_class;
+VACUUM FULL pg_type;
+VACUUM FULL pg_index;
+VACUUM FULL pg_database;
+INSERT INTO replication_example(somedata) VALUES (3);
+-- make old files go away
+CHECKPOINT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                         data                                          
+---------------------------------------------------------------------------------------
+ BEGIN
+ table "replication_example": INSERT: id[int4]:2 somedata[int4]:2 text[varchar]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:3 somedata[int4]:3 text[varchar]:(null)
+ COMMIT
+(22 rows)
+
+SELECT stop_logical_replication('regression_slot');
+ stop_logical_replication 
+--------------------------
+                        0
+(1 row)
+
diff --git a/contrib/test_logical_decoding/logical.conf b/contrib/test_logical_decoding/logical.conf
new file mode 100644
index 0000000..a7c6c86
--- /dev/null
+++ b/contrib/test_logical_decoding/logical.conf
@@ -0,0 +1,2 @@
+wal_level = logical
+max_logical_slots = 4
diff --git a/contrib/test_logical_decoding/sql/ddl.sql b/contrib/test_logical_decoding/sql/ddl.sql
new file mode 100644
index 0000000..b1eee39
--- /dev/null
+++ b/contrib/test_logical_decoding/sql/ddl.sql
@@ -0,0 +1,316 @@
+CREATE EXTENSION test_logical_decoding;
+-- predictability
+SET synchronous_commit = on;
+
+-- faster startup
+CHECKPOINT;
+
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+-- fail because of an already existing slot
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+-- succeed once
+SELECT stop_logical_replication('regression_slot');
+-- fail
+SELECT stop_logical_replication('regression_slot');
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+
+/* check whether status function reports us, only reproduceable columns */
+SELECT slot_name, plugin, active,
+    xmin::xid IS NOT NULL,
+    pg_xlog_location_diff(restart_decoding_lsn, '0/01000000') > 0
+FROM pg_stat_logical_decoding;
+
+/*
+ * Check that changes are handled correctly when interleaved with ddl
+ */
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+COMMIT;
+
+ALTER TABLE replication_example ADD COLUMN bar int;
+
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL);
+COMMIT;
+
+ALTER TABLE replication_example DROP COLUMN bar;
+INSERT INTO replication_example(somedata, text) VALUES (3, 1);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (3, 2);
+INSERT INTO replication_example(somedata, text) VALUES (3, 3);
+COMMIT;
+
+ALTER TABLE replication_example RENAME COLUMN text TO somenum;
+
+INSERT INTO replication_example(somedata, somenum) VALUES (4, 1);
+
+-- collect all changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING (somenum::int4);
+-- throw away changes, they contain oids
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO replication_example(somedata, somenum) VALUES (5, 1);
+
+BEGIN;
+INSERT INTO replication_example(somedata, somenum) VALUES (6, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod1 int;
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 2, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod2 int;
+INSERT INTO replication_example(somedata, somenum, zaphod2) VALUES (6, 3, 1);
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 4, 2);
+COMMIT;
+
+/*
+ * check whether the correct indexes are chosen for deletions
+ */
+
+CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
+INSERT INTO tr_unique(data) VALUES(10);
+--show deletion with unique index
+DELETE FROM tr_unique;
+
+ALTER TABLE tr_unique RENAME TO tr_pkey;
+
+-- show changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- hide changes bc of oid visible in full table rewrites
+ALTER TABLE tr_pkey ADD COLUMN id serial primary key;
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO tr_pkey(data) VALUES(1);
+--show deletion with primary key
+DELETE FROM tr_pkey;
+
+/* display results */
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+/*
+ * check that disk spooling works
+ */
+BEGIN;
+CREATE TABLE tr_etoomuch (id serial primary key, data int);
+INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i);
+DELETE FROM tr_etoomuch WHERE id < 5000;
+UPDATE tr_etoomuch SET data = - data WHERE id > 5000;
+COMMIT;
+
+/* display results, but hide most of the output */
+SELECT count(*), min(data), max(data)
+FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1')
+GROUP BY substring(data, 1, 24)
+ORDER BY 1;
+
+/*
+ * check whether we subtransactions correctly in relation with each other
+ */
+CREATE TABLE tr_sub (id serial primary key, path text);
+
+-- toplevel, subtxn, toplevel, subtxn, subtxn
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('1-top-#1');
+
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#2');
+RELEASE SAVEPOINT a;
+
+SAVEPOINT b;
+SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#2');
+RELEASE SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-#1');
+RELEASE SAVEPOINT b;
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- check that we handle xlog assignments correctly
+BEGIN;
+-- nest 80 subtxns
+SAVEPOINT subtop;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+-- assign xid by inserting
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#1');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#2');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#3');
+RELEASE SAVEPOINT subtop;
+INSERT INTO tr_sub(path) VALUES ('2-top-#1');
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- make sure rollbacked subtransactions aren't decoded
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#1');
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-1-#1');
+SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-2-#1');
+ROLLBACK TO SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#2');
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- test whether a known, but not yet logged toplevel xact, followed by a
+-- subxact commit is handled correctly
+BEGIN;
+SELECT txid_current() != 0; -- so no fixed xid apears in the outfile
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('4-top-1-#1');
+RELEASE SAVEPOINT a;
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+
+/*
+ * Check whether treating a table as a catalog table works somewhat
+ */
+CREATE TABLE replication_metadata (
+    id serial primary key,
+    relation name NOT NULL,
+    options text[]
+)
+WITH (treat_as_catalog_table = true)
+;
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('foo', ARRAY['a', 'b']);
+
+ALTER TABLE replication_metadata RESET (treat_as_catalog_table);
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('bar', ARRAY['a', 'b']);
+
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = true);
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('blub', NULL);
+
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = false);
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('zaphod', NULL);
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+/*
+ * check whether we handle updates/deletes correct with & without a pkey
+ */
+
+/* we should handle the case without a key at all more gracefully */
+CREATE TABLE table_without_key(id serial, data int);
+INSERT INTO table_without_key(data) VALUES(1),(2);
+DELETE FROM table_without_key WHERE data = 1;
+UPDATE table_without_key SET data = 3 WHERE data = 2;
+UPDATE table_without_key SET id = -id;
+UPDATE table_without_key SET id = -id;
+DELETE FROM table_without_key WHERE data = 3;
+
+CREATE TABLE table_with_pkey(id serial primary key, data int);
+INSERT INTO table_with_pkey(data) VALUES(1), (2);
+DELETE FROM table_with_pkey WHERE data = 1;
+UPDATE table_with_pkey SET data = 3 WHERE data = 2;
+UPDATE table_with_pkey SET id = -id;
+UPDATE table_with_pkey SET id = -id;
+DELETE FROM table_with_pkey WHERE data = 3;
+
+CREATE TABLE table_with_unique(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id DROP NOT NULL;
+INSERT INTO table_with_unique(data) VALUES(1), (2);
+DELETE FROM table_with_unique WHERE data = 1;
+UPDATE table_with_unique SET data = 3 WHERE data = 2;
+UPDATE table_with_unique SET id = -id;
+UPDATE table_with_unique SET id = -id;
+DELETE FROM table_with_unique WHERE data = 3;
+
+CREATE TABLE table_with_unique_not_null(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id SET NOT NULL; --already set
+INSERT INTO table_with_unique_not_null(data) VALUES(1), (2);
+DELETE FROM table_with_unique_not_null WHERE data = 1;
+UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2;
+UPDATE table_with_unique_not_null SET id = -id;
+UPDATE table_with_unique_not_null SET id = -id;
+DELETE FROM table_with_unique_not_null WHERE data = 3;
+
+CREATE TABLE table_with_oid(id serial, data int) WITH oids;
+CREATE UNIQUE INDEX table_with_oid_oid ON table_with_oid(oid);
+INSERT INTO table_with_oid(data) VALUES(1), (2);
+DELETE FROM table_with_oid WHERE data = 1;
+UPDATE table_with_oid SET data = 3 WHERE data = 2;
+DELETE FROM table_with_oid WHERE data = 3;
+UPDATE table_with_oid SET id = -id;
+UPDATE table_with_oid SET id = -id;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- check toast support
+SELECT setseed(0);
+CREATE TABLE toasttable(
+       id serial primary key,
+       toasted_col1 text,
+       rand1 float8 DEFAULT random(),
+       toasted_col2 text,
+       rand2 float8 DEFAULT random()
+       );
+
+-- uncompressed external toast data
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+
+-- compressed external toast data
+INSERT INTO toasttable(toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i);
+
+-- update of existing column
+UPDATE toasttable
+    SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+
+-- update of second column, first column unchanged
+UPDATE toasttable
+    SET toasted_col2 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+
+-- make sure we decode correctly even if the toast table is gone
+DROP TABLE toasttable;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- done, free logical replication slot
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+SELECT stop_logical_replication('regression_slot');
+
+/* check whether we aren't visible anymore now */
+SELECT * FROM pg_stat_logical_decoding;
diff --git a/contrib/test_logical_decoding/sql/rewrite.sql b/contrib/test_logical_decoding/sql/rewrite.sql
new file mode 100644
index 0000000..2400fe3
--- /dev/null
+++ b/contrib/test_logical_decoding/sql/rewrite.sql
@@ -0,0 +1,29 @@
+CREATE EXTENSION test_logical_decoding;
+-- predictability
+SET synchronous_commit = on;
+
+DROP TABLE IF EXISTS replication_example;
+
+-- faster startup
+CHECKPOINT;
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+INSERT INTO replication_example(somedata) VALUES (1);
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO replication_example(somedata) VALUES (2);
+VACUUM FULL pg_am;
+VACUUM FULL pg_amop;
+VACUUM FULL pg_proc;
+VACUUM FULL pg_opclass;
+VACUUM FULL pg_class;
+VACUUM FULL pg_type;
+VACUUM FULL pg_index;
+VACUUM FULL pg_database;
+INSERT INTO replication_example(somedata) VALUES (3);
+
+-- make old files go away
+CHECKPOINT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+SELECT stop_logical_replication('regression_slot');
diff --git a/contrib/test_logical_decoding/test_logical_decoding--1.0.sql b/contrib/test_logical_decoding/test_logical_decoding--1.0.sql
new file mode 100644
index 0000000..b6e048c
--- /dev/null
+++ b/contrib/test_logical_decoding/test_logical_decoding--1.0.sql
@@ -0,0 +1,6 @@
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_logical_decoding" to load this file. \quit
+
+CREATE FUNCTION start_logical_replication (slotname name, pos text, VARIADIC options text[] DEFAULT '{}', OUT location text, OUT xid bigint, OUT data text) RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'start_logical_replication'
+LANGUAGE C IMMUTABLE STRICT;
diff --git a/contrib/test_logical_decoding/test_logical_decoding.c b/contrib/test_logical_decoding/test_logical_decoding.c
new file mode 100644
index 0000000..26ecdfa
--- /dev/null
+++ b/contrib/test_logical_decoding/test_logical_decoding.c
@@ -0,0 +1,238 @@
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalfuncs.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "storage/fd.h"
+#include "miscadmin.h"
+#include "funcapi.h"
+
+PG_MODULE_MAGIC;
+
+Datum		start_logical_replication(PG_FUNCTION_ARGS);
+
+static Tuplestorestate *tupstore = NULL;
+static TupleDesc tupdesc;
+
+static void
+LogicalOutputPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	resetStringInfo(ctx->out);
+}
+
+static void
+LogicalOutputWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	Datum		values[3];
+	bool		nulls[3];
+	char		buf[60];
+
+	sprintf(buf, "%X/%X", (uint32) (lsn >> 32), (uint32) lsn);
+
+	memset(nulls, 0, sizeof(nulls));
+	values[0] = CStringGetTextDatum(buf);
+	values[1] = Int64GetDatum(xid);
+	values[2] = CStringGetTextDatum(ctx->out->data);
+
+	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+}
+
+PG_FUNCTION_INFO_V1(start_logical_replication);
+
+Datum
+start_logical_replication(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	XLogRecPtr	now;
+	XLogRecPtr	startptr;
+	XLogRecPtr	rp;
+
+	LogicalDecodingContext *ctx;
+
+	ResourceOwner old_resowner = CurrentResourceOwner;
+	ArrayType  *arr;
+	Size		ndim;
+	List	   *options = NIL;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	arr = PG_GETARG_ARRAYTYPE_P(2);
+	ndim = ARR_NDIM(arr);
+
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	if (ndim > 1)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("start_logical_replication only accept one dimension of arguments")));
+	}
+	else if (array_contains_nulls(arr))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			  errmsg("start_logical_replication expects NOT NULL options")));
+	}
+	else if (ndim == 1)
+	{
+		int			nelems;
+		Datum	   *datum_opts;
+		int			i;
+
+		Assert(ARR_ELEMTYPE(arr) == TEXTOID);
+
+		deconstruct_array(arr, TEXTOID, -1, false, 'i',
+						  &datum_opts, NULL, &nelems);
+
+		if (nelems % 2 != 0)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("options need to be specified pairwise")));
+		}
+
+		for (i = 0; i < nelems; i += 2)
+		{
+			char	   *name = VARDATA(DatumGetTextP(datum_opts[i]));
+			char	   *opt = VARDATA(DatumGetTextP(datum_opts[i + 1]));
+
+			options = lappend(options, makeDefElem(name, (Node *) makeString(opt)));
+		}
+	}
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * XXX: It's impolite to ignore our argument and keep decoding until the
+	 * current position.
+	 */
+	now = GetFlushRecPtr();
+
+	/*
+	 * We need to create a normal_snapshot_reader, but adjust it to use our
+	 * page_read callback, and also make its reorder buffer use our callback
+	 * wrappers that don't depend on walsender.
+	 */
+
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingReAcquireSlot(NameStr(*name));
+
+	ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, false,
+									   MyLogicalDecodingSlot->confirmed_flush,
+									   options,
+									   logical_read_local_xlog_page,
+									   LogicalOutputPrepareWrite,
+									   LogicalOutputWrite);
+
+	startptr = MyLogicalDecodingSlot->restart_decoding;
+
+	elog(DEBUG1, "Starting logical replication from %X/%X to %X/%X",
+		 (uint32) (MyLogicalDecodingSlot->restart_decoding >> 32),
+		 (uint32) MyLogicalDecodingSlot->restart_decoding,
+		 (uint32) (now >> 32), (uint32) now);
+
+	CurrentResourceOwner = ResourceOwnerCreate(CurrentResourceOwner, "logical decoding");
+
+	/* invalidate non-timetravel entries */
+	InvalidateSystemCaches();
+
+	PG_TRY();
+	{
+
+		while ((startptr != InvalidXLogRecPtr && startptr < now) ||
+			   (ctx->reader->EndRecPtr && ctx->reader->EndRecPtr < now))
+		{
+			XLogRecord *record;
+			char	   *errm = NULL;
+
+			record = XLogReadRecord(ctx->reader, startptr, &errm);
+			if (errm)
+				elog(ERROR, "%s", errm);
+
+			startptr = InvalidXLogRecPtr;
+
+			if (record != NULL)
+			{
+				XLogRecordBuffer buf;
+
+				buf.origptr = ctx->reader->ReadRecPtr;
+				buf.endptr = ctx->reader->EndRecPtr;
+				buf.record = *record;
+				buf.record_data = XLogRecGetData(record);
+
+				/*
+				 * The {begin_txn,change,commit_txn}_wrapper callbacks above
+				 * will store the description into our tuplestore.
+				 */
+				DecodeRecordIntoReorderBuffer(ctx, &buf);
+			}
+		}
+	}
+	PG_CATCH();
+	{
+		LogicalDecodingReleaseSlot();
+
+		/*
+		 * clear timetravel entries: XXX allowed in aborted TXN?
+		 */
+		InvalidateSystemCaches();
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	rp = ctx->reader->EndRecPtr;
+	if (rp >= now)
+	{
+		elog(DEBUG1, "Reached endpoint (wanted: %X/%X, got: %X/%X)",
+			 (uint32) (now >> 32), (uint32) now,
+			 (uint32) (rp >> 32), (uint32) rp);
+	}
+
+	tuplestore_donestoring(tupstore);
+
+	CurrentResourceOwner = old_resowner;
+
+	/*
+	 * Next time, start where we left off. (Hunting things, the family
+	 * business..)
+	 */
+	MyLogicalDecodingSlot->confirmed_flush = ctx->reader->EndRecPtr;
+
+	LogicalDecodingReleaseSlot();
+
+	return (Datum) 0;
+}
diff --git a/contrib/test_logical_decoding/test_logical_decoding.control b/contrib/test_logical_decoding/test_logical_decoding.control
new file mode 100644
index 0000000..0dce19f
--- /dev/null
+++ b/contrib/test_logical_decoding/test_logical_decoding.control
@@ -0,0 +1,5 @@
+# test_logical_decoding extension
+comment = 'test logical decoding'
+default_version = '1.0'
+module_pathname = '$libdir/test_logical_decoding'
+relocatable = true
-- 
1.8.4.21.g992c386.dirty

0008-wal_decoding-design-document-v2.4-and-snapshot-build.patchtext/x-patch; charset=us-asciiDownload
>From 667d52abc1599416a7190e00599eba536d890500 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:31 +0200
Subject: [PATCH 8/8] wal_decoding: design document v2.4 and snapshot building
 design doc v0.5

---
 src/backend/replication/logical/DESIGN.txt         | 593 +++++++++++++++++++++
 src/backend/replication/logical/Makefile           |   6 +
 .../replication/logical/README.SNAPBUILD.txt       | 241 +++++++++
 3 files changed, 840 insertions(+)
 create mode 100644 src/backend/replication/logical/DESIGN.txt
 create mode 100644 src/backend/replication/logical/README.SNAPBUILD.txt

diff --git a/src/backend/replication/logical/DESIGN.txt b/src/backend/replication/logical/DESIGN.txt
new file mode 100644
index 0000000..d76fdb4
--- /dev/null
+++ b/src/backend/replication/logical/DESIGN.txt
@@ -0,0 +1,593 @@
+//-*- mode: adoc -*-
+= High Level Design for Logical Replication in Postgres =
+:copyright: PostgreSQL Global Development Group 2012
+:author: Andres Freund, 2ndQuadrant Ltd.
+:email: andres@2ndQuadrant.com
+
+== Introduction ==
+
+This document aims to first explain why we think postgres needs another
+replication solution and what that solution needs to offer in our opinion. Then
+it sketches out our proposed implementation.
+
+In contrast to an earlier version of the design document which talked about the
+implementation of four parts of replication solutions:
+
+1. Source data generation
+1. Transportation of that data
+1. Applying the changes
+1. Conflict resolution
+
+this version only plans to talk about the first part in detail as it is an
+independent and complex part usable for a wide range of use cases which we want
+to get included into postgres in a first step.
+
+=== Previous discussions ===
+
+There are two rather large threads discussing several parts of the initial
+prototype and proposed architecture:
+
+- http://archives.postgresql.org/message-id/201206131327.24092.andres@2ndquadrant.com[Logical Replication/BDR prototype and architecture]
+- http://archives.postgresql.org/message-id/201206211341.25322.andres@2ndquadrant.com[Catalog/Metadata consistency during changeset extraction from WAL]
+
+Those discussions lead to some fundamental design changes which are presented in this document.
+
+=== Changes from v1 ===
+* At least a partial decoding step required/possible on the source system
+* No intermediate ("schema only") instances required
+* DDL handling, without event triggers
+* A very simple text conversion is provided for debugging/demo purposes
+* Smaller scope
+
+== Existing approaches to replication in Postgres ==
+
+If any currently used approach to replication can be made to support every
+use-case/feature we need, it likely is not a good idea to implement something
+different. Currently three basic approaches are in use in/around postgres
+today:
+
+. Trigger based
+. Recovery based/Physical footnote:[Often referred to by terms like Hot Standby, Streaming Replication, Point In Time Recovery]
+. Statement based
+
+Statement based replication has obvious and known problems with consistency and
+correctness making it hard to use in the general case so we will not further
+discuss it here.
+
+Lets have a look at the advantages/disadvantages of the other approaches:
+
+=== Trigger based Replication ===
+
+This variant has a multitude of significant advantages:
+
+* implementable in userspace
+* easy to customize
+* just about everything can be made configurable
+* cross version support
+* cross architecture support
+* can feed into systems other than postgres
+* no overhead from writes to non-replicated tables
+* writable standbys
+* mature solutions
+* multimaster implementations possible & existing
+
+But also a number of disadvantages, some of them very hard to solve:
+
+* essentially duplicates the amount of writes (or even more!)
+* synchronous replication hard or impossible to implement
+* noticeable CPU overhead
+** trigger functions
+** text conversion of data
+* complex parts implemented in several solutions
+* not in core
+
+Especially the higher amount of writes might seem easy to solve at a first
+glance but a solution not using a normal transactional table for its log/queue
+has to solve a lot of problems. The major ones are:
+
+* crash safety, restartability & spilling to disk
+* consistency with the commit status of transactions
+* only a minimal amount of synchronous work should be done inside individual
+transactions
+
+In our opinion those problems are restricting progress/wider distribution of
+these class of solutions. It is our aim though that existing solutions in this
+space - most prominently slony and londiste - can benefit from the work we are
+doing & planning to do by incorporating at least parts of the changeset
+generation infrastructure.
+
+=== Recovery based Replication ===
+
+This type of solution, being built into postgres and of increasing popularity,
+has and will have its use cases and we do not aim to replace but to complement
+it. We plan to reuse some of the infrastructure and to make it possible to mix
+both modes of replication
+
+Advantages:
+
+* builtin
+* built on existing infrastructure from crash recovery
+* efficient
+** minimal CPU, memory overhead on primary
+** low amount of additional writes
+* synchronous operation mode
+* low maintenance once setup
+* handles DDL
+
+Disadvantages:
+
+* standbys are read only
+* no cross version support
+* no cross architecture support
+* no replication into foreign systems
+* hard to customize
+* not configurable on the level of database, tables, ...
+
+== Goals ==
+
+As seen in the previous short survey of the two major interesting classes of
+replication solution there is a significant gap between those. Our aim is to
+make it smaller.
+
+We aim for:
+
+* in core
+* low CPU overhead
+* low storage overhead
+* asynchronous, optionally synchronous operation modes
+* robust
+* modular
+* basis for other technologies (sharding, replication into other DBMS's, ...)
+* basis for at least one multi-master solution
+* make the implementation as unintrusive as possible, but not more
+
+== New Architecture ==
+
+=== Overview ===
+
+Our proposal is to reuse the basic principle of WAL based replication, namely
+reusing data that already needs to be written for another purpose, and extend
+it to allow most, but not all, the flexibility of trigger based solutions.
+We want to do that by decoding the WAL back into a non-physical form.
+
+To get the flexibility we and others want we propose that the last step of
+changeset generation, transforming it into a format that can be used by the
+replication consumer, is done in an extensible manner. In the schema the part
+that does that is described as 'Output Plugin'. To keep the amount of
+duplication between different plugins as low as possible the plugin should only
+do a a very limited amount of work.
+
+The following paragraphs contain reasoning for the individual design decisions
+made and their highlevel design.
+
+=== Schematics ===
+
+The basic proposed architecture for changeset extraction is presented in the
+following diagram. The first part should look familiar to anyone knowing
+postgres' architecture. The second is where most of the new magic happens.
+
+[[basic-schema]]
+.Architecture Schema
+["ditaa"]
+------------------------------------------------------------------------------
+        Traditional Stuff
+
+ +---------+---------+---------+---------+----+
+ | Backend | Backend | Backend | Autovac | ...|
+ +----+----+---+-----+----+----+----+----+-+--+
+      |        |          |         |      |
+      +------+ | +--------+         |      |
+    +-+      | | | +----------------+      |
+    |        | | | |                       |
+    |        v v v v                       |
+    |     +------------+                   |
+    |     | WAL writer |<------------------+
+    |     +------------+
+    |       | | | | |
+    v       v v v v v       +-------------------+
++--------+ +---------+   +->| Startup/Recovery  |
+|{s}     | |{s}      |   |  +-------------------+
+|Catalog | |   WAL   |---+->| SR/Hot Standby    |
+|        | |         |   |  +-------------------+
++--------+ +---------+   +->| Point in Time     |
+    ^          |            +-------------------+
+ ---|----------|--------------------------------
+    |       New Stuff
++---+          |
+|              v            Running separately
+| +----------------+  +=-------------------------+
+| | Walsender  |   |  |                          |
+| |            v   |  |    +-------------------+ |
+| +-------------+  |  | +->| Logical Rep.      | |
+| |     WAL     |  |  | |  +-------------------+ |
++-|  decoding   |  |  | +->| Multimaster       | |
+| +------+------/  |  | |  +-------------------+ |
+| |            |   |  | +->| Slony             | |
+| |            v   |  | |  +-------------------+ |
+| +-------------+  |  | +->| Auditing          | |
+| |     TX      |  |  | |  +-------------------+ |
++-| reassembly  |  |  | +->| Mysql/...         | |
+| +-------------/  |  | |  +-------------------+ |
+| |            |   |  | +->| Custom Solutions  | |
+| |            v   |  | |  +-------------------+ |
+| +-------------+  |  | +->| Debugging         | |
+| |   Output    |  |  | |  +-------------------+ |
++-|   Plugin    |--|--|-+->| Data Recovery     | |
+  +-------------/  |  |    +-------------------+ |
+  |                |  |                          |
+  +----------------+  +--------------------------|
+------------------------------------------------------------------------------
+
+=== WAL enrichement ===
+
+To be able to decode individual WAL records at the very minimal they need to
+contain enough information to reconstruct what has happened to which row. The
+action is already encoded in the WAL records header in most of the cases.
+
+As an example of missing data, the WAL record emitted when a row gets deleted,
+only contains its physical location. At the very least we need a way to
+identify the deleted row: in a relational database the minimal amount of data
+that does that should be the primary key footnote:[Yes, there are use cases
+where the whole row is needed, or where no primary key can be found].
+
+We propose that for now it is enough to extend the relevant WAL record with
+additional data when the newly introduced 'WAL_level = logical' is set.
+
+Previously it has been argued on the hackers mailing list that a generic 'WAL
+record annotation' mechanism might be a good thing. That mechanism would allow
+to attach arbitrary data to individual wal records making it easier to extend
+postgres to support something like what we propose.. While we don't oppose that
+idea we think it is largely orthogonal issue to this proposal as a whole
+because the format of a WAL records is version dependent by nature and the
+necessary changes for our easy way are small, so not much effort is lost.
+
+A full annotation capability is a complex endeavour on its own as the parts of
+the code generating the relevant WAL records has somewhat complex requirements
+and cannot easily be configured from the outside.
+
+Currently this is contained in the http://archives.postgresql.org/message-id/1347669575-14371-6-git-send-email-andres@2ndquadrant.com[Log enough data into the wal to reconstruct logical changes from it] patch.
+
+=== WAL parsing & decoding ===
+
+The main complexity when reading the WAL as stored on disk is that the format
+is somewhat complex and the existing parser is too deeply integrated in the
+recovery system to be directly reusable. Once a reusable parser exists decoding
+the binary data into individual WAL records is a small problem.
+
+Currently two competing proposals for this module exist, each having its own
+merits. In the grand scheme of this proposal it is irrelevant which one gets
+picked as long as the functionality gets integrated.
+
+The mailing list post
+http:http://archives.postgresql.org/message-id/1347669575-14371-3-git-send-email-andres@2ndquadrant.com[Add
+support for a generic wal reading facility dubbed XLogReader] contains both
+competing patches and discussion around which one is preferable.
+
+Once the WAL has been decoded into individual records two major issues exist:
+
+1. records from different transactions and even individual user level actions
+are intermingled
+1. the data attached to records cannot be interpreted on its own, it is only
+meaningful with a lot of required information (including table, columns, types
+and more)
+
+The solution to the first issue is described in the next section: <<tx-reassembly>>
+
+The second problem is probably the reason why no mature solution to reuse the
+WAL for logical changeset generation exists today. See the <<snapbuilder>>
+paragraph for some details.
+
+As decoding, Transaction reassembly and Snapshot building are interdependent
+they currently are implemented in the same patch:
+http://archives.postgresql.org/message-id/1347669575-14371-8-git-send-email-andres@2ndquadrant.com[Introduce
+wal decoding via catalog timetravel]
+
+That patch also includes a small demonstration that the approach works in the
+presence of DDL:
+
+[[example-of-decoding]]
+.Decoding example
+[NOTE]
+---------------------------
+/* just so we keep a sensible xmin horizon */
+ROLLBACK PREPARED 'f';
+BEGIN;
+CREATE TABLE keepalive();
+PREPARE TRANSACTION 'f';
+
+DROP TABLE IF EXISTS replication_example;
+
+SELECT pg_current_xlog_insert_location();
+CHECKPOINT;
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text
+varchar(120));
+begin;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+commit;
+
+
+ALTER TABLE replication_example ADD COLUMN bar int;
+
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL);
+COMMIT;
+
+/* slightly more complex schema change, still no table rewrite */
+ALTER TABLE replication_example DROP COLUMN bar;
+INSERT INTO replication_example(somedata, text) VALUES (3, 1);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (3, 2);
+INSERT INTO replication_example(somedata, text) VALUES (3, 3);
+commit;
+
+ALTER TABLE replication_example RENAME COLUMN text TO somenum;
+
+INSERT INTO replication_example(somedata, somenum) VALUES (4, 1);
+
+/* complex schema change, changing types of existing column, rewriting the table */
+ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING
+(somenum::int4);
+
+INSERT INTO replication_example(somedata, somenum) VALUES (5, 1);
+
+SELECT pg_current_xlog_insert_location();
+
+/* now decode what has been written to the WAL during that time */
+
+SELECT decode_xlog('0/1893D78', '0/18BE398');
+
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:1 somedata[int4]:1 text[varchar]:1
+WARNING:  tuple is: id[int4]:2 somedata[int4]:1 text[varchar]:2
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:3 somedata[int4]:2 text[varchar]:1 bar[int4]:4
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:4 somedata[int4]:2 text[varchar]:2 bar[int4]:4
+WARNING:  tuple is: id[int4]:5 somedata[int4]:2 text[varchar]:3 bar[int4]:4
+WARNING:  tuple is: id[int4]:6 somedata[int4]:2 text[varchar]:4 bar[int4]:
+(null)
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:7 somedata[int4]:3 text[varchar]:1
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:8 somedata[int4]:3 text[varchar]:2
+WARNING:  tuple is: id[int4]:9 somedata[int4]:3 text[varchar]:3
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:10 somedata[int4]:4 somenum[varchar]:1
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:11 somedata[int4]:5 somenum[int4]:1
+WARNING:  COMMIT
+
+---------------------------
+
+[[tx-reassembly]]
+=== TX reassembly ===
+
+In order to make usage of the decoded stream easy we want to present the user
+level code with a correctly ordered image of individual transactions at once
+because otherwise every user will have to reassemble transactions themselves.
+
+Transaction reassembly needs to solve several problems:
+
+1. changes inside a transaction can be interspersed with other transactions
+1. a top level transaction only knows which subtransactions belong to it when
+it reads the commit record
+1. individual user level actions can be smeared over multiple records (TOAST)
+
+Our proposed module solves 1) and 2) by building individual streams of records
+split by xid. While not fully implemented yet we plan to spill those individual
+xid streams to disk after a certain amount of memory is used. This can be
+implemented without any change in the external interface.
+
+As all the individual streams are already sorted by LSN by definition - we
+build them from the wal in a FIFO manner, and the position in the WAL is the
+definition of the LSN footnote:[the LSN is just the byte position int the WAL
+stream] - the individual changes can be merged efficiently by a k-way merge
+(without sorting!) by keeping the individual streams in a binary heap.
+
+To manipulate the binary heap a generic implementation is proposed. Several
+independent implementations of binary heaps already exist in the postgres code,
+but none of them is generic.  The patch is available at
+http://archives.postgresql.org/message-id/1347669575-14371-2-git-send-email-andres@2ndquadrant.com[Add
+minimal binary heap implementation].
+
+[NOTE]
+============
+The reassembly component was previously coined ApplyCache because it was
+proposed to run on replication consumers just before applying changes. This is
+not the case anymore.
+
+It is still called that way in the source of the patch recently submitted.
+============
+
+[[snapbuilder]]
+=== Snapshot building  ===
+
+To decode the contents of wal records describing data changes we need to decode
+and transform their contents. A single tuple is stored in a data structure
+called HeapTuple. As stored on disk that structure doesn't contain any
+information about the format of its contents.
+
+The basic problem is twofold:
+
+1. The wal records only contain the relfilenode not the relation oid of a table
+11. The relfilenode changes when an action performing a full table rewrite is performed
+1. To interpret a HeapTuple correctly the exact schema definition from back
+when the wal record was inserted into the wal stream needs to be available
+
+We chose to implement timetraveling access to the system catalog using
+postgres' MVCC nature & implementation because of the following advantages:
+
+* low amount of additional data in wal
+* genericity
+* similarity of implementation to Hot Standby, quite a bit of the infrastructure is reusable
+* all kinds of DDL can be handled in reliable manner
+* extensibility to user defined catalog like tables
+
+Timetravel access to the catalog means that we are able to look at the catalog
+just as it looked when changes were generated. That allows us to get the
+correct information about the contents of the aforementioned HeapTuple's so we
+can decode them reliably.
+
+Other solutions we thought about that fell through:
+* catalog only proxy instances that apply schema changes exactly to the point
+  were decoding using ``old fashioned'' wal replay
+* do the decoding on a 2nd machine, replicating all DDL exactly, rely on the catalog there
+* do not allow DDL at all
+* always add enough data into the WAL to allow decoding
+* build a fully versioned catalog
+
+The email thread available under
+http://archives.postgresql.org/message-id/201206211341.25322.andres@2ndquadrant.com[Catalog/Metadata
+consistency during changeset extraction from WAL] contains some details,
+advantages and disadvantages about the different possible implementations.
+
+How we build snapshots is somewhat intricate and complicated and seems to be
+out of scope for this document. We will provide a second document discussing
+the implementation in detail. Let's just assume it is possible from here on.
+
+[NOTE]
+Some details are already available in comments inside 'src/backend/replication/logical/snapbuild.{c,h}'.
+
+=== Output Plugin ===
+
+As already mentioned previously our aim is to make the implementation of output
+plugins as simple and non-redundant as possible as we expect several different
+ones with different use cases to emerge quickly. See <<basic-schema>> for a
+list of possible output plugins that we think might emerge.
+
+Although we for now only plan to tackle logical replication and based on that a
+multi-master implementation in the near future we definitely aim to provide all
+use-cases with something easily useable!
+
+To decode and translate local transaction an output plugin needs to be able to
+transform transactions as a whole so it can apply them as a meaningful
+transaction at the other side.
+
+What we do to provide that is, that very time we find a transaction commit and
+thus have completed reassembling the transaction we start to provide the
+individual changes to the output plugin. It currently only has to fill out 3
+callbacks:
+[options="header"]
+|=====================================================================================================================================
+|Callback |Passed Parameters                    |Called per TX  | Use
+|begin    |xid                                  |once           |Begin of a reassembled transaction
+|change   |xid, subxid, change, mvcc snapshot   |every change   |Gets passed every change so it can transform it to the target format
+|commit   |xid                                  |once           |End of a reassembled transaction
+|=====================================================================================================================================
+
+During each of those callback an appropriate timetraveling SnapshotNow snapshot
+is setup so the callbacks can perform all read-only catalog accesses they need,
+including using the sys/rel/catcache. For obvious reasons only read access is
+allowed.
+
+The snapshot guarantees that the result of lookups are be the same as they
+were/would have been when the change was originally created.
+
+Additionally they get passed a MVCC snapshot, to e.g. run sql queries on
+catalogs or similar.
+
+[IMPORTANT]
+============
+At the moment none of these snapshots can be used to access normal user
+tables. Adding additional tables to the allowed set is easy implementation
+wise, but every transaction changing such tables incurs a noticeably higher
+overhead.
+============
+
+For now transactions won't be decoded/output in parallel. There are ideas to
+improve on this, but we don't think the complexity is appropriate for the first
+release of this feature.
+
+This is an adoption barrier for databases where large amounts of data get
+loaded/written in one transaction.
+
+=== Setup of replication nodes ===
+
+When setting up a new standby/consumer of a primary some problem exist
+independent of the implementation of the consumer. The gist of the problem is
+that when making a base backup and starting to stream all changes since that
+point transactions that were running during all this cannot be included:
+
+* Transaction that have not committed before starting to dump a database are
+  invisible to the dumping process
+
+* Transactions that began before the point from which on the WAL is being
+  decoded are incomplete and cannot be replayed
+
+Our proposal for a solution to this is to detect points in the WAL stream where we can provide:
+
+. A snapshot exported similarly to pg_export_snapshot() footnote:[http://www.postgresql.org/docs/devel/static/functions-admin.html#FUNCTIONS-SNAPSHOT-SYNCHRONIZATION] that can be imported with +SET TRANSACTION SNAPSHOT+ footnote:[http://www.postgresql.org/docs/devel/static/sql-set-transaction.html]
+. A stream of changes that will include the complete data of all transactions seen as running by the snapshot generated in 1)
+
+See the diagram.
+
+[[setup-schema]]
+.Control flow during setup of a new node
+["ditaa",scaling="0.7"]
+------------------------------------------------------------------------------
++----------------+
+| Walsender  |   |                               +------------+
+|            v   |                               | Consumer   |
++-------------+  |<--IDENTIFY_SYSTEM-------------|            |
+|     WAL     |  |                               |            |
+|  decoding   |  |----....---------------------->|            |
++------+------/  |                               |            |
+|            |   |                               |            |
+|            v   |                               |            |
++-------------+  |<--INIT_LOGICAL $PLUGIN--------|            |
+|     TX      |  |                               |            |
+| reassembly  |  |---FOUND_STARTING %X/%X------->|            |
++-------------/  |                               |            |
+|            |   |---FOUND_CONSISTENT %X/%X----->|            |
+|            v   |---pg_dump snapshot----------->|            |
++-------------+  |---replication slot %P-------->|            |
+|   Output    |  |                               |            |
+|   Plugin    |  |    ^                          |            |
++-------------/  |    |                          |            |
+|                |    +-run pg_dump separately --|            |
+|                |                               |            |
+|                |<--STREAM_DATA-----------------|            |
+|                |                               |            |
+|                |---data ---------------------->|            |
+|                |                               |            |
+|                |                               |            |
+|                |  ---- SHUTDOWN -------------  |            |
+|                |                               |            |
+|                |                               |            |
+|                |<--RESTART_LOGICAL $PLUGIN %P--|            |
+|                |                               |            |
+|                |---data----------------------->|            |
+|                |                               |            |
+|                |                               |            |
++----------------+                               +------------+
+
+------------------------------------------------------------------------------
+
+=== Disadvantages of the approach ===
+
+* somewhat intricate code for snapshot timetravel
+* output plugins/walsenders need to work per database as they access the catalog
+* when sending to multiple standbys some work is done multiple times
+* decoding/applying multiple transactions in parallel is somewhat hard
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 310a45c..6fae278 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -17,3 +17,9 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 OBJS = decode.o logical.o logicalfuncs.o reorderbuffer.o snapbuild.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+DESIGN.pdf: DESIGN.txt
+	a2x -v --fop -f pdf -D $(shell pwd) $<
+
+README.SNAPBUILD.pdf: README.SNAPBUILD.txt
+	a2x -v --fop -f pdf -D $(shell pwd) $<
diff --git a/src/backend/replication/logical/README.SNAPBUILD.txt b/src/backend/replication/logical/README.SNAPBUILD.txt
new file mode 100644
index 0000000..b6c7470
--- /dev/null
+++ b/src/backend/replication/logical/README.SNAPBUILD.txt
@@ -0,0 +1,241 @@
+= Snapshot Building =
+:author: Andres Freund, 2nQuadrant Ltd
+
+== Why do we need timetravel catalog access ==
+
+When doing WAL decoding (see DESIGN.txt for reasons to do so), we need to know
+how the catalog looked at the point a record was inserted into the WAL, because
+without that information we don't know much more about the record other than
+its length.  It's just an arbitrary bunch of bytes without further information.
+Unfortunately, due the possibility that the table definition might change we
+cannot just access a newer version of the catalog and assume the table
+definition continues to be the same.
+
+If only the type information were required, it might be enough to annotate the
+wal records with a bit more information (table oid, table name, column name,
+column type) --- but as we want to be able to convert the output to more useful
+formats such as text, we additionally need to be able to call output functions.
+Those need a normal environment including the usual caches and normal catalog
+access to lookup operators, functions and other types.
+
+Our solution to this is to add the capability to access the catalog such as it
+was at the time the record was inserted into the WAL. The locking used during
+WAL generation guarantees the catalog is/was in a consistent state at that
+point.  We call this 'time-travel catalog access'.
+
+Interesting cases include:
+
+- enums
+- composite types
+- extension types
+- non-C functions
+- relfilenode to table OID mapping
+
+Due to postgres' non-overwriting storage manager, regular modifications of a
+table's content are theoretically non-destructive. The problem is that there is
+no way to access an arbitrary point in time even if the data for it is there.
+
+This module adds the capability to do so in the very limited set of
+circumstances we need it in for WAL decoding. It does *not* provide a general
+time-travelling facility.
+
+A 'Snapshot' is the data structure used in postgres to describe which tuples
+are visible and which are not. We need to build a Snapshot which can be used to
+access the catalog the way it looked when the wal record was inserted.
+
+Restrictions:
+
+- Only works for catalog tables or tables explicitly marked as such.
+- Snapshot modifications are somewhat expensive
+- it cannot build initial visibility information for every point in time, it
+  needs a specific circumstances to start.
+
+== How are time-travel snapshots built ==
+
+'Hot Standby' added infrastructure to build snapshots from WAL during recovery in
+the 9.0 release. Most of that can be reused for our purposes.
+
+We cannot reuse all of the hot standby infrastructure because:
+
+- we are not in recovery
+- we need to look at interim states *inside* a transaction
+- we need the capability to have multiple different snapshots arround at the same time
+
+Normally the catalog is accessed using SnapshotNow which can legally be
+replaced by SnapshotMVCC that has been taken at the start of a scan. So catalog
+timetravel contains infrastructure to make SnapshotNow catalog access use
+appropriate MVCC snapshots. They aren't generated with GetSnapshotData()
+though, but reassembled from WAL contents.
+
+We collect our data in a normal struct SnapshotData, repurposing some fields
+creatively:
+
+- +Snapshot->xip+ contains all transaction we consider committed
+- +Snapshot->subxip+ contains all transactions belonging to our transaction,
+  including the toplevel one
+- +Snapshot->active_count+ is used as a refcount
+
+The meaning of +xip+ is inverted in comparison with non-timetravel snapshots in
+the sense that members of the array are the committed transactions, not the in
+progress ones. Because usually only a tiny percentage of comitted transactions
+will have modified the catalog between xmin and xmax this allows us to keep the
+array small in the usual cases. It also makes subtransaction handling easier
+since we neither need to query pg_subtrans (which we couldn't anyway since it's
+truncated at restart) nor have problems with suboverflowed snapshots.
+
+== Building of initial snapshot ==
+
+We can start building an initial snapshot as soon as we find either an
++XLOG_RUNNING_XACTS+ or an +XLOG_CHECKPOINT_SHUTDOWN+ record because they allow us
+to know how many transactions are running.
+
+We need to know which transactions were running when we start to build a
+snapshot/start decoding as we don't have enough information about them (they
+could have done catalog modifications before we started watching). Also, we
+wouldn't have the complete contents of those transactions, because we started
+reading after they began.  (The latter is also important when building
+snapshots that can be used to build a consistent initial clone.)
+
+There also is the problem that +XLOG_RUNNING_XACT+ records can be
+'suboverflowed' which means there were more running subtransactions than
+fitting into shared memory. In that case we use the same incremental building
+trick hot standby uses which is either
+
+1. wait till further +XLOG_RUNNING_XACT+ records have a running->oldestRunningXid
+after the initial xl_runnign_xacts->nextXid
+2. wait for a further +XLOG_RUNNING_XACT+ that is not overflowed or
+a +XLOG_CHECKPOINT_SHUTDOWN+
+
+When we start building a snapshot we are in the +SNAPBUILD_START+ state. As
+soon as we find any visibility information, even if incomplete, we change to
++SNAPBUILD_INITIAL_POINT+.
+
+When we have collected enough information to decode any transaction starting
+after that point in time we fall over to +SNAPBUILD_FULL_SNAPSHOT+. If those
+transactions commit before the next state is reached, we throw their complete
+contents away.
+
+As soon as all transactions that were running when we switched over to
++SNAPBUILD_FULL_SNAPSHOT+ commit, we change state to +SNAPBUILD_CONSISTENT+.
+Every transaction that commits from now on gets handed to the output plugin.
+When doing the switch to +SNAPBUILD_CONSISTENT+ we optionally export a snapshot
+which makes all transactions that committed up to this point visible.  This
+exported snapshot can be used to run pg_dump; replaying all changes emitted
+by the output plugin on a database restored from such a dump will result in
+a consistent clone.
+
+["ditaa",scaling="0.8"]
+---------------
+
+        +-------------------------+
+   +----|SNAPBUILD_START          |-------------+
+   |    +-------------------------+             |
+   |                 |                          |
+   |                 |                          |
+   |     running_xacts with running xacts       |
+   |                 |                          |
+   |                 |                          |
+   |                 v                          |
+   |    +-------------------------+             v
+   |    |SNAPBUILD_FULL_SNAPSHOT  |------------>|
+   |    +-------------------------+             |
+XLOG_RUNNING_XACTS   |                      saved snapshot
+  with zero xacts    |                 at running_xacts's lsn
+   |                 |                          |
+   |     all running toplevel TXNs finished     |
+   |                 |                          |
+   |                 v                          |
+   |    +-------------------------+             |
+   +--->|SNAPBUILD_CONSISTENT     |<------------+
+        +-------------------------+
+
+---------------
+
+== Snapshot Management ==
+
+Whenever a transaction is detected as having started during decoding in
++SNAPBUILD_FULL_SNAPSHOT+ state, we distribute the currently maintained
+snapshot to it (i.e. call ReorderBufferSetBaseSnapshot). This serves as its
+initial snapshot. Unless there are concurrent catalog changes that snapshot
+will be used for the decoding the entire transaction's changes.
+
+Whenever a transaction-with-catalog-changes commits, we iterate over all
+concurrently active transactions and add a new SnapshotNow to it
+(ReorderBufferAddSnapshot(current_lsn)). This is required because any row
+written from now that point on will have used the changed catalog contents.
+
+When decoding a transaction that made catalog changes itself we tell that
+transaction that (ReorderBufferAddNewCommandId(current_lsn)) which will cause
+the decoding to use the appropriate command id from that point on.
+
+SnapshotNow's need to be setup globally so the syscache and other pieces access
+it transparently. This is done using two new tqual.h functions:
+SetupDecodingSnapshots() and RevertFromDecodingSnapshots().
+
+== Catalog/User Table Detection ==
+
+Since we only want to store committed transactions that actually modified the
+catalog we need a way to detect that from WAL:
+
+Right now, we assume that every transaction that commits before we reach
++SNAPBUILD_CONSISTENT+ state has made catalog modifications since we can't rely
+on having seen the entire transaction before that. That's not harmful beside
+incurring some price in memory usage and runtime.
+
+After having reached consistency we recognize catalog modifying transactions
+via HEAP2_NEW_CID and HEAP_INPLACE that are logged by catalog modifying
+actions.
+
+== mixed DDL/DML transaction handling  ==
+
+When a transactions uses DDL and DML in the same transaction things get a bit
+more complicated because we need to handle CommandIds and ComboCids as we need
+to use the correct version of the catalog when decoding the individual tuples.
+
+For that we emit the new HEAP2_NEW_CID records which contain the physical tuple
+location, cmin and cmax when the catalog is modified. If we need to detect
+visibility of a catalog tuple that has been modified in our own transaction -
+which we can detect via xmin/xmax - we look in a hash table using the location
+as key to get correct cmin/cmax values.
+From those values we can also extract the commandid that generated the record.
+
+All this only needs to happen in the transaction performing the DDL.
+
+== Cache Handling ==
+
+As we allow usage of the normal {sys,cat,rel,..}cache we also need to integrate
+cache invalidation. For transactions that only do DDL thats easy as everything
+is already provided by HS. Everytime we read a commit record we apply the
+sinval messages contained therein.
+
+For transactions that contain DDL and DML cache invalidation needs to happen
+more frequently because we need to all tore down all caches that just got
+modified. To do that we simply apply all invalidation messages that got
+collected at the end of transaction and apply them everytime we've decoded
+single change. At some point this can get optimized by generating new local
+invalidation messages, but that seems too complicated for now.
+
+XXX: talk about syscache handling of relmapped relation.
+
+== xmin Horizon Handling ==
+
+Reusing MVCC for timetravel access has one obvious major problem: VACUUM. Rows
+we still need for decoding cannot be removed but at the same time we cannot
+keep data in the catalog indefinitely.
+
+For that we peg the xmin horizon that's used to decide which rows can be
+removed. We only need to prevent removal of those rows for catalog like
+relations, not for all user tables. For that reason a separate xmin horizon
+RecentGlobalDataXmin got introduced.
+
+Since we need to persist that knowledge across restarts we keep the xmin for a
+in the logical slots which are safed in a crashsafe manner. They are restored
+from disk into memory at server startup.
+
+== Restartable Decoding ==
+
+As we want to generate a consistent stream of changes we need to have the
+ability to start from a previously decoded location without waiting possibly
+very long to reach consistency. For that reason we dump the current visibility
+information to disk everytime we read an xl_running_xacts record.
+
-- 
1.8.4.21.g992c386.dirty

#2Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#1)
Re: logical changeset generation v6

What's with 0001-Improve-regression-test-for-8410.patch? Did you mean
to include that?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#2)
Re: logical changeset generation v6

On 2013-09-15 10:03:54 -0400, Peter Eisentraut wrote:

What's with 0001-Improve-regression-test-for-8410.patch? Did you mean
to include that?

Gah, no. That's already committed and unrelated. Stupid wildcard.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#1)
Re: logical changeset generation v6

On Sat, 2013-09-14 at 22:49 +0200, Andres Freund wrote:

Attached you can find the newest version of the logical changeset
generation patchset.

You probably have bigger things to worry about, but please check the
results of cpluspluscheck, because some of the header files don't
include header files they depend on.

(I guess that's really pgcompinclude's job to find out, but
cpluspluscheck seems to be easier to use.)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#4)
Re: logical changeset generation v6

On 2013-09-15 11:20:20 -0400, Peter Eisentraut wrote:

On Sat, 2013-09-14 at 22:49 +0200, Andres Freund wrote:

Attached you can find the newest version of the logical changeset
generation patchset.

You probably have bigger things to worry about, but please check the
results of cpluspluscheck, because some of the header files don't
include header files they depend on.

Hm. I tried to get that right, but it's been a while since I last
checked. I don't regularly use cpluspluscheck because it doesn't work in
VPATH builds... We really need to fix that.

I'll push a fix for that to the git tree, don't think that's worth a
resend in itself.

Thanks,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#5)
Re: logical changeset generation v6

On 9/15/13 11:30 AM, Andres Freund wrote:

On 2013-09-15 11:20:20 -0400, Peter Eisentraut wrote:

On Sat, 2013-09-14 at 22:49 +0200, Andres Freund wrote:

Attached you can find the newest version of the logical changeset
generation patchset.

You probably have bigger things to worry about, but please check the
results of cpluspluscheck, because some of the header files don't
include header files they depend on.

Hm. I tried to get that right, but it's been a while since I last
checked. I don't regularly use cpluspluscheck because it doesn't work in
VPATH builds... We really need to fix that.

I'll push a fix for that to the git tree, don't think that's worth a
resend in itself.

This patch set now fails to apply because of the commit "Rename various
"freeze multixact" variables".

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#6)
8 attachment(s)
Re: logical changeset generation v6

On 2013-09-17 09:45:28 -0400, Peter Eisentraut wrote:

On 9/15/13 11:30 AM, Andres Freund wrote:

On 2013-09-15 11:20:20 -0400, Peter Eisentraut wrote:

On Sat, 2013-09-14 at 22:49 +0200, Andres Freund wrote:

Attached you can find the newest version of the logical changeset
generation patchset.

You probably have bigger things to worry about, but please check the
results of cpluspluscheck, because some of the header files don't
include header files they depend on.

Hm. I tried to get that right, but it's been a while since I last
checked. I don't regularly use cpluspluscheck because it doesn't work in
VPATH builds... We really need to fix that.

I'll push a fix for that to the git tree, don't think that's worth a
resend in itself.

This patch set now fails to apply because of the commit "Rename various
"freeze multixact" variables".

And I am even partially guilty for that patch...

Rebased patches attached.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patchtext/x-patch; charset=us-asciiDownload
>From cdcac9ccbc3103285be4984f648ebe86551c0841 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 1/8] wal_decoding: Allow walsender's to connect to a specific
 database

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                         | 24 +++++++++---
 src/backend/postmaster/postmaster.c                | 23 ++++++++++--
 .../libpqwalreceiver/libpqwalreceiver.c            |  4 +-
 src/backend/replication/walsender.c                | 43 +++++++++++++++++++---
 src/backend/utils/init/postinit.c                  |  5 +++
 src/bin/pg_basebackup/pg_basebackup.c              |  4 +-
 src/bin/pg_basebackup/pg_receivexlog.c             |  4 +-
 src/bin/pg_basebackup/receivelog.c                 |  4 +-
 src/include/replication/walsender.h                |  1 +
 9 files changed, 89 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 0b2e60e..2ea14e5 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1301,10 +1301,13 @@
 
 <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>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 01d2618..a31b01d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,10 +1894,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';
 
 	/*
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 6bc0aa1..ee0f1fe 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -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) != 4 || 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 4 fields, got %d tuples with %d fields.",
 						   ntuples, nfields)));
 	}
 	primary_sysid = PQgetvalue(res, 0, 0);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index afd559d..b00a91a 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -46,7 +46,10 @@
 #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 */
@@ -295,17 +315,28 @@ IdentifySystem(void)
 	pq_sendint(&buf, -1, 2);
 	pq_sendint(&buf, 0, 4);
 	pq_sendint(&buf, 0, 2);
+
+	/* fourth field */
+	pq_sendstring(&buf, "dbname");
+	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_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));
+	pq_sendint(&buf, strlen(dbname), 4);	/* col4 len */
+	pq_sendbytes(&buf, (char *) dbname, strlen(dbname));
 
 	pq_endmessage(&buf);
 }
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 2c7f0f1..56c352c 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -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);
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index a1e12a8..89e2376 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -1361,11 +1361,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) != 4)
 	{
 		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);
+				progname, PQntuples(res), PQnfields(res), 1, 4);
 		disconnect_and_exit(1);
 	}
 	sysidentifier = pg_strdup(PQgetvalue(res, 0, 0));
diff --git a/src/bin/pg_basebackup/pg_receivexlog.c b/src/bin/pg_basebackup/pg_receivexlog.c
index 787a395..fe8aef6 100644
--- a/src/bin/pg_basebackup/pg_receivexlog.c
+++ b/src/bin/pg_basebackup/pg_receivexlog.c
@@ -252,11 +252,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) != 4)
 	{
 		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);
+				progname, PQntuples(res), PQnfields(res), 1, 4);
 		disconnect_and_exit(1);
 	}
 	servertli = atoi(PQgetvalue(res, 0, 1));
diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c
index d56a4d7..22a5340 100644
--- a/src/bin/pg_basebackup/receivelog.c
+++ b/src/bin/pg_basebackup/receivelog.c
@@ -534,11 +534,11 @@ ReceiveXlogStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 			PQclear(res);
 			return false;
 		}
-		if (PQnfields(res) != 3 || PQntuples(res) != 1)
+		if (PQnfields(res) != 4 || PQntuples(res) != 1)
 		{
 			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);
+					progname, PQntuples(res), PQnfields(res), 1, 4);
 			PQclear(res);
 			return false;
 		}
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 2cc7ddf..5097235 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -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 */
-- 
1.8.4.21.g992c386.dirty

0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patchtext/x-patch; charset=us-asciiDownload
>From 463cdb627c47b2e3945ae87fb6f594252be3c570 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 2/8] wal_decoding: Log xl_running_xact's at a higher frequency
 than checkpoints are done

Logging information about running xacts more frequently is beneficial for both,
hot standby which can reach consistency faster and release some resources
earlier using this information, and future logical replication which can
initialize quicker using this.

Do so in the background writer which seems to be the best choice as its
regularly running and shouldn't be busy for too long without getting back into
its main loop.

Also mark xl_running_xact records as being relevant for async commit so the wal
writer writes them out soonish instead of possibly waiting a long time.
---
 src/backend/postmaster/bgwriter.c | 62 +++++++++++++++++++++++++++++++++++++++
 src/backend/storage/ipc/standby.c | 27 ++++++++++++++---
 src/include/storage/standby.h     |  2 +-
 3 files changed, 86 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 286ae86..13d57c5 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -54,9 +54,11 @@
 #include "storage/shmem.h"
 #include "storage/smgr.h"
 #include "storage/spin.h"
+#include "storage/standby.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
+#include "utils/timestamp.h"
 
 
 /*
@@ -71,6 +73,20 @@ int			BgWriterDelay = 200;
 #define HIBERNATE_FACTOR			50
 
 /*
+ * Interval in which standby snapshots are logged into the WAL stream, in
+ * milliseconds.
+ */
+#define LOG_SNAPSHOT_INTERVAL_MS 15000
+
+/*
+ * LSN and timestamp at which we last issued a LogStandbySnapshot(), to avoid
+ * doing so too often or repeatedly if there has been no other write activity
+ * in the system.
+ */
+static TimestampTz last_snapshot_ts;
+static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
+
+/*
  * Flags set by interrupt handlers for later service in the main loop.
  */
 static volatile sig_atomic_t got_SIGHUP = false;
@@ -142,6 +158,12 @@ BackgroundWriterMain(void)
 	CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer");
 
 	/*
+	 * We just started, assume there has been either a shutdown or
+	 * end-of-recovery snapshot.
+	 */
+	last_snapshot_ts = GetCurrentTimestamp();
+
+	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
 	 * possible memory leaks.  Formerly this code just ran in
@@ -276,6 +298,46 @@ BackgroundWriterMain(void)
 		}
 
 		/*
+		 * Log a new xl_running_xacts every now and then so replication can get
+		 * into a consistent state faster (think of suboverflowed snapshots)
+		 * and clean up resources (locks, KnownXids*) more frequently. The
+		 * costs of this are relatively low, so doing it 4 times
+		 * (LOG_SNAPSHOT_INTERVAL_MS) a minute seems fine.
+		 *
+		 * We assume the interval for writing xl_running_xacts is
+		 * significantly bigger than BgWriterDelay, so we don't complicate the
+		 * overall timeout handling but just assume we're going to get called
+		 * often enough even if hibernation mode is active. It's not that
+		 * important that log_snap_interval_ms is met strictly. To make sure
+		 * we're not waking the disk up unneccesarily on an idle system we
+		 * check whether there has been any WAL inserted since the last time
+		 * we've logged a running xacts.
+		 *
+		 * We do this logging in the bgwriter as its the only process thats
+		 * run regularly and returns to its mainloop all the
+		 * time. E.g. Checkpointer, when active, is barely ever in its
+		 * mainloop and thus makes it hard to log regularly.
+		 */
+		if (XLogStandbyInfoActive() && !RecoveryInProgress())
+		{
+			TimestampTz timeout = 0;
+			TimestampTz now = GetCurrentTimestamp();
+			timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
+												  LOG_SNAPSHOT_INTERVAL_MS);
+
+			/*
+			 * only log if enough time has passed and some xlog record has been
+			 * inserted.
+			 */
+			if (now >= timeout &&
+				last_snapshot_lsn != GetXLogInsertRecPtr())
+			{
+				last_snapshot_lsn = LogStandbySnapshot();
+				last_snapshot_ts = now;
+			}
+		}
+
+		/*
 		 * Sleep until we are signaled or BgWriterDelay has elapsed.
 		 *
 		 * Note: the feedback control loop in BgBufferSync() expects that we
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index c704412..97da1a0 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -42,7 +42,7 @@ static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlis
 									   ProcSignalReason reason);
 static void ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid);
 static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
-static void LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
+static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
 static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
 
 
@@ -853,10 +853,13 @@ standby_redo(XLogRecPtr lsn, XLogRecord *record)
  * currently running xids, performed by StandbyReleaseOldLocks().
  * Zero xids should no longer be possible, but we may be replaying WAL
  * from a time when they were possible.
+ *
+ * Returns the RecPtr of the last inserted record.
  */
-void
+XLogRecPtr
 LogStandbySnapshot(void)
 {
+	XLogRecPtr recptr;
 	RunningTransactions running;
 	xl_standby_lock *locks;
 	int			nlocks;
@@ -876,9 +879,12 @@ LogStandbySnapshot(void)
 	 * record we write, because standby will open up when it sees this.
 	 */
 	running = GetRunningTransactionData();
-	LogCurrentRunningXacts(running);
+	recptr = LogCurrentRunningXacts(running);
+
 	/* GetRunningTransactionData() acquired XidGenLock, we must release it */
 	LWLockRelease(XidGenLock);
+
+	return recptr;
 }
 
 /*
@@ -889,7 +895,7 @@ LogStandbySnapshot(void)
  * is a contiguous chunk of memory and never exists fully until it is
  * assembled in WAL.
  */
-static void
+static XLogRecPtr
 LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 {
 	xl_running_xacts xlrec;
@@ -939,6 +945,19 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 			 CurrRunningXacts->oldestRunningXid,
 			 CurrRunningXacts->latestCompletedXid,
 			 CurrRunningXacts->nextXid);
+
+	/*
+	 * Ensure running_xacts information is synced to disk not too far in the
+	 * future. We don't want to stall anything though (i.e. use XLogFlush()),
+	 * so we let the wal writer do it during normal
+	 * operation. XLogSetAsyncXactLSN() conveniently will mark the LSN as
+	 * to-be-synced and nudge the WALWriter into action if sleeping. Check
+	 * XLogBackgroundFlush() for details why a record might not be flushed
+	 * without it.
+	 */
+	XLogSetAsyncXactLSN(recptr);
+
+	return recptr;
 }
 
 /*
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 7f3f051..d4a8fe4 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -113,6 +113,6 @@ typedef RunningTransactionsData *RunningTransactions;
 extern void LogAccessExclusiveLock(Oid dbOid, Oid relOid);
 extern void LogAccessExclusiveLockPrepare(void);
 
-extern void LogStandbySnapshot(void);
+extern XLogRecPtr LogStandbySnapshot(void);
 
 #endif   /* STANDBY_H */
-- 
1.8.4.21.g992c386.dirty

0003-wal_decoding-Add-information-about-a-tables-primary-.patchtext/x-patch; charset=us-asciiDownload
>From be59001586a9baa731876744fa84cf7987b59fe3 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 3/8] wal_decoding: Add information about a tables primary key
 to struct RelationData

'rd_primary' now contains the Oid of an index over uniquely identifying
columns. Several types of indexes are interesting and are collected in that
order:
* Primary Key
* oid index
* the first (OID order) unique, immediate, non-partial and
  non-expression index over one or more NOT NULL'ed columns

To gather rd_primary value RelationGetIndexList() needs to have been called.

This is helpful because for logical replication we frequently - on the sending
and receiving side - need to lookup that index and RelationGetIndexList already
gathers all the necessary information.

This could be used to replace tablecmd.c's transformFkeyGetPrimaryKey, but
would change the meaning of that, so it seems to require additional discussion.
---
 src/backend/utils/cache/relcache.c | 52 +++++++++++++++++++++++++++++++++++---
 src/include/utils/rel.h            | 12 +++++++++
 2 files changed, 61 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b4cc6ad..44dd0d2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3462,7 +3462,9 @@ RelationGetIndexList(Relation relation)
 	ScanKeyData skey;
 	HeapTuple	htup;
 	List	   *result;
-	Oid			oidIndex;
+	Oid			oidIndex = InvalidOid;
+	Oid			pkeyIndex = InvalidOid;
+	Oid			candidateIndex = InvalidOid;
 	MemoryContext oldcxt;
 
 	/* Quick exit if we already computed the list. */
@@ -3519,17 +3521,61 @@ RelationGetIndexList(Relation relation)
 		Assert(!isnull);
 		indclass = (oidvector *) DatumGetPointer(indclassDatum);
 
+		if (!IndexIsValid(index))
+			continue;
+
 		/* Check to see if it is a unique, non-partial btree index on OID */
-		if (IndexIsValid(index) &&
-			index->indnatts == 1 &&
+		if (index->indnatts == 1 &&
 			index->indisunique && index->indimmediate &&
 			index->indkey.values[0] == ObjectIdAttributeNumber &&
 			indclass->values[0] == OID_BTREE_OPS_OID &&
 			heap_attisnull(htup, Anum_pg_index_indpred))
 			oidIndex = index->indexrelid;
+
+		if (index->indisunique &&
+			index->indimmediate &&
+			heap_attisnull(htup, Anum_pg_index_indpred))
+		{
+			/* always prefer primary keys */
+			if (index->indisprimary)
+				pkeyIndex = index->indexrelid;
+			else if (!OidIsValid(pkeyIndex)
+					&& !OidIsValid(oidIndex)
+					&& !OidIsValid(candidateIndex))
+			{
+				int key;
+				bool found = true;
+				for (key = 0; key < index->indnatts; key++)
+				{
+					int16 attno = index->indkey.values[key];
+					Form_pg_attribute attr;
+					/* internal column, like oid */
+					if (attno <= 0)
+						continue;
+
+					attr = relation->rd_att->attrs[attno - 1];
+					if (!attr->attnotnull)
+					{
+						found = false;
+						break;
+					}
+				}
+				if (found)
+					candidateIndex = index->indexrelid;
+			}
+		}
 	}
 
 	systable_endscan(indscan);
+
+	if (OidIsValid(pkeyIndex))
+		relation->rd_primary = pkeyIndex;
+	/* prefer oid indexes over normal candidate ones */
+	else if (OidIsValid(oidIndex))
+		relation->rd_primary = oidIndex;
+	else if (OidIsValid(candidateIndex))
+		relation->rd_primary = candidateIndex;
+
 	heap_close(indrel, AccessShareLock);
 
 	/* Now save a copy of the completed list in the relcache entry. */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 589c9a8..0281b4b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -111,6 +111,18 @@ typedef struct RelationData
 	TriggerDesc *trigdesc;		/* Trigger info, or NULL if rel has none */
 
 	/*
+	 * The 'best' primary or candidate key that has been found, only set
+	 * correctly if RelationGetIndexList has been called/rd_indexvalid > 0.
+	 *
+	 * Indexes are chosen in the following order:
+	 * * Primary Key
+	 * * oid index
+	 * * the first (OID order) unique, immediate, non-partial and
+	 *   non-expression index over one or more NOT NULL'ed columns
+	 */
+	Oid rd_primary;
+
+	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
 	 * Note that you can NOT look into rd_rel for this data.  NULL means "use
 	 * defaults".
-- 
1.8.4.21.g992c386.dirty

0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patchtext/x-patch; charset=us-asciiDownload
>From 0a15a118d9b88a3e327cf76dfe297c17bf17fb01 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 4/8] wal_decoding: Introduce wal decoding via catalog
 timetravel

This introduces several things:
* 'reorderbuffer' module which reassembles transactions from a stream of interspersed changes
* 'snapbuilder' which builds catalog snapshots so that tuples from wal can be understood
* logging more data into wal to facilitate logical decoding
* wal decoding into an reorderbuffer
* shared library output plugins with 5 callbacks
 * init
 * begin
 * change
 * commit
* walsender infrastructur to stream out changes and to keep the global xmin low enough
 * INIT_LOGICAL_REPLICATION $plugin; waits till a consistent snapshot is built and returns
   * initial LSN
   * replication slot identifier
   * id of a pg_export() style snapshot
 * START_LOGICAL_REPLICATION $id $lsn; streams out changes
 * uses named output plugins for output specification

Todo:
* better integrated testing infrastructure
* more docs about the internals

Lowlevel:
* resource owner handling is suboptimal
* invalidations from uninteresting transactions (e.g. from other databases, old ones)
  need to be processed anyway
* error handling in walsender is suboptimal
* pg_receivellog needs to send a reply immediately when postgres is shutting down

Input, Testing and Review by:
Heikki Linnakangas
Kevin Grittner
Michael Paquier
Abhijit Menon-Sen
Peter Gheogegan
Robert Haas
Simon Riggs
Steve Singer

Code By:
Andres Freund

With code contributions by:
Abhijit Menon-Sen
Craig Ringer
Alvaro Herrera

Conflicts:
	src/backend/replication/repl_gram.y
---
 src/backend/access/common/reloptions.c          |   10 +
 src/backend/access/heap/heapam.c                |  465 ++++-
 src/backend/access/heap/pruneheap.c             |    2 +
 src/backend/access/index/indexam.c              |   14 +-
 src/backend/access/rmgrdesc/heapdesc.c          |    9 +
 src/backend/access/rmgrdesc/xlogdesc.c          |    1 +
 src/backend/access/transam/twophase.c           |    4 +-
 src/backend/access/transam/xact.c               |   48 +-
 src/backend/access/transam/xlog.c               |   14 +-
 src/backend/catalog/catalog.c                   |   14 +-
 src/backend/catalog/index.c                     |   15 +-
 src/backend/catalog/system_views.sql            |   10 +
 src/backend/commands/analyze.c                  |    2 +-
 src/backend/commands/cluster.c                  |    2 +
 src/backend/commands/trigger.c                  |    3 +-
 src/backend/commands/vacuum.c                   |    5 +-
 src/backend/commands/vacuumlazy.c               |    3 +
 src/backend/postmaster/postmaster.c             |    2 +-
 src/backend/replication/Makefile                |    2 +
 src/backend/replication/logical/Makefile        |   19 +
 src/backend/replication/logical/decode.c        |  687 ++++++
 src/backend/replication/logical/logical.c       | 1046 ++++++++++
 src/backend/replication/logical/logicalfuncs.c  |  361 ++++
 src/backend/replication/logical/reorderbuffer.c | 2548 +++++++++++++++++++++++
 src/backend/replication/logical/snapbuild.c     | 1581 ++++++++++++++
 src/backend/replication/repl_gram.y             |   75 +-
 src/backend/replication/repl_scanner.l          |   55 +-
 src/backend/replication/walreceiver.c           |    2 +-
 src/backend/replication/walsender.c             |  733 ++++++-
 src/backend/storage/ipc/ipci.c                  |    3 +
 src/backend/storage/ipc/procarray.c             |   72 +-
 src/backend/storage/ipc/standby.c               |   15 +
 src/backend/utils/cache/inval.c                 |    4 +-
 src/backend/utils/cache/relcache.c              |  113 +-
 src/backend/utils/misc/guc.c                    |   12 +
 src/backend/utils/misc/postgresql.conf.sample   |   11 +-
 src/backend/utils/time/snapmgr.c                |    7 +-
 src/backend/utils/time/tqual.c                  |  270 ++-
 src/bin/initdb/initdb.c                         |    4 +-
 src/bin/pg_controldata/pg_controldata.c         |    2 +
 src/include/access/heapam_xlog.h                |   59 +-
 src/include/access/transam.h                    |    5 +
 src/include/access/xact.h                       |    1 +
 src/include/access/xlog.h                       |    8 +-
 src/include/access/xlogreader.h                 |   13 +-
 src/include/catalog/catalog.h                   |    1 +
 src/include/catalog/pg_proc.h                   |    6 +
 src/include/commands/vacuum.h                   |    2 +-
 src/include/nodes/nodes.h                       |    3 +
 src/include/nodes/replnodes.h                   |   35 +
 src/include/replication/decode.h                |   20 +
 src/include/replication/logical.h               |  198 ++
 src/include/replication/logicalfuncs.h          |   21 +
 src/include/replication/output_plugin.h         |   70 +
 src/include/replication/reorderbuffer.h         |  342 +++
 src/include/replication/snapbuild.h             |   81 +
 src/include/replication/walsender_private.h     |    6 +-
 src/include/storage/itemptr.h                   |    3 +
 src/include/storage/lwlock.h                    |    1 +
 src/include/storage/procarray.h                 |    2 +-
 src/include/storage/sinval.h                    |    2 +
 src/include/utils/inval.h                       |    1 +
 src/include/utils/rel.h                         |   30 +-
 src/include/utils/relcache.h                    |   11 +-
 src/include/utils/snapmgr.h                     |    3 +
 src/include/utils/tqual.h                       |   21 +-
 src/test/regress/expected/rules.out             |    9 +-
 src/tools/pgindent/typedefs.list                |   40 +
 68 files changed, 9033 insertions(+), 206 deletions(-)
 create mode 100644 src/backend/replication/logical/Makefile
 create mode 100644 src/backend/replication/logical/decode.c
 create mode 100644 src/backend/replication/logical/logical.c
 create mode 100644 src/backend/replication/logical/logicalfuncs.c
 create mode 100644 src/backend/replication/logical/reorderbuffer.c
 create mode 100644 src/backend/replication/logical/snapbuild.c
 create mode 100644 src/include/replication/decode.h
 create mode 100644 src/include/replication/logical.h
 create mode 100644 src/include/replication/logicalfuncs.h
 create mode 100644 src/include/replication/output_plugin.h
 create mode 100644 src/include/replication/reorderbuffer.h
 create mode 100644 src/include/replication/snapbuild.h

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5fd30a..e1e5040 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -63,6 +63,14 @@ static relopt_bool boolRelOpts[] =
 	},
 	{
 		{
+			"treat_as_catalog_table",
+			"Treat table as a catalog table for the purpose of logical replication",
+			RELOPT_KIND_HEAP
+		},
+		false
+	},
+	{
+		{
 			"fastupdate",
 			"Enables \"fast update\" feature for this GIN index",
 			RELOPT_KIND_GIN
@@ -1166,6 +1174,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, security_barrier)},
 		{"check_option", RELOPT_TYPE_STRING,
 		offsetof(StdRdOptions, check_option_offset)},
+		{"treat_as_catalog_table", RELOPT_TYPE_BOOL,
+		 offsetof(StdRdOptions, treat_as_catalog_table)}
 	};
 
 	options = parseRelOptions(reloptions, validate, kind, &numoptions);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ead3d69..1a7281f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -85,12 +85,14 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
-				HeapTuple newtup, bool all_visible_cleared,
-				bool new_all_visible_cleared);
+				HeapTuple newtup, HeapTuple old_idx_tup,
+				bool all_visible_cleared, bool new_all_visible_cleared);
 static void HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
-							 bool *satisfies_hot, bool *satisfies_key,
-							 HeapTuple oldtup, HeapTuple newtup);
+						  Bitmapset *hot_attrs,
+						  Bitmapset *key_attrs, Bitmapset *ckey_attrs,
+						  bool *satisfies_hot, bool *satisfies_key,
+						  bool *satisfies_ckey,
+						  HeapTuple oldtup, HeapTuple newtup);
 static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -108,6 +110,8 @@ static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static bool ConditionalMultiXactIdWait(MultiXactId multi,
 						   MultiXactStatus status, int *remaining,
 						   uint16 infomask);
+static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static HeapTuple ExtractKeyTuple(Relation rel, HeapTuple tup);
 
 
 /*
@@ -342,8 +346,10 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	/*
 	 * Prune and repair fragmentation for the whole page, if possible.
 	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
+	if (IsSystemRelation(scan->rs_rd) || RelationIsDoingTimetravel(scan->rs_rd))
+		heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
+	else
+		heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalDataXmin);
 
 	/*
 	 * We must hold share lock on the buffer content while examining tuple
@@ -1743,10 +1749,16 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		 */
 		if (!skip)
 		{
+			/* setup the redirected t_self for the benefit of timetravel access */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
 			/* If it's visible per the snapshot, we must return it */
 			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
+			/* reset original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
 			if (valid)
 			{
 				ItemPointerSetOffsetNumber(tid, offnum);
@@ -2101,11 +2113,24 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		xl_heap_insert xlrec;
 		xl_heap_header xlhdr;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[3];
+		XLogRecData rdata[4];
 		Page		page = BufferGetPage(buffer);
 		uint8		info = XLOG_HEAP_INSERT;
+		bool		need_tuple_data;
+
+		/*
+		 * For logical replication, we need the tuple even if we're doing a
+		 * full page write, so make sure to log it separately. (XXX We could
+		 * alternatively store a pointer into the FPW).
+		 *
+		 * Also, if this is a catalog, we need to transmit combocids to
+		 * properly decode, so log that as well.
+		 */
+		need_tuple_data = RelationIsLogicallyLogged(relation);
+		if (RelationIsDoingTimetravel(relation))
+			log_heap_new_cid(relation, heaptup);
 
-		xlrec.all_visible_cleared = all_visible_cleared;
+		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.target.node = relation->rd_node;
 		xlrec.target.tid = heaptup->t_self;
 		rdata[0].data = (char *) &xlrec;
@@ -2124,18 +2149,35 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 */
 		rdata[1].data = (char *) &xlhdr;
 		rdata[1].len = SizeOfHeapHeader;
-		rdata[1].buffer = buffer;
+		rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;
 		rdata[1].buffer_std = true;
 		rdata[1].next = &(rdata[2]);
 
 		/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
 		rdata[2].data = (char *) heaptup->t_data + offsetof(HeapTupleHeaderData, t_bits);
 		rdata[2].len = heaptup->t_len - offsetof(HeapTupleHeaderData, t_bits);
-		rdata[2].buffer = buffer;
+		rdata[2].buffer = need_tuple_data ? InvalidBuffer : buffer;
 		rdata[2].buffer_std = true;
 		rdata[2].next = NULL;
 
 		/*
+		 * add record for the buffer without actual content thats removed if
+		 * fpw is done for that buffer
+		 */
+		if (need_tuple_data)
+		{
+			rdata[2].next = &(rdata[3]);
+
+			rdata[3].data = NULL;
+			rdata[3].len = 0;
+			rdata[3].buffer = buffer;
+			rdata[3].buffer_std = true;
+			rdata[3].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+		}
+
+		/*
 		 * If this is the single and first tuple on page, we can reinit the
 		 * page instead of restoring the whole thing.  Set flag, and hide
 		 * buffer references from XLogInsert.
@@ -2144,7 +2186,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 		{
 			info |= XLOG_HEAP_INIT_PAGE;
-			rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
+			rdata[1].buffer = rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
 		}
 
 		recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -2270,6 +2312,8 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 	Page		page;
 	bool		needwal;
 	Size		saveFreeSpace;
+	bool        need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool        need_cids = RelationIsDoingTimetravel(relation);
 
 	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
 	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
@@ -2356,7 +2400,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 		{
 			XLogRecPtr	recptr;
 			xl_heap_multi_insert *xlrec;
-			XLogRecData rdata[2];
+			XLogRecData rdata[3];
 			uint8		info = XLOG_HEAP2_MULTI_INSERT;
 			char	   *tupledata;
 			int			totaldatalen;
@@ -2386,7 +2430,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			/* the rest of the scratch space is used for tuple data */
 			tupledata = scratchptr;
 
-			xlrec->all_visible_cleared = all_visible_cleared;
+			xlrec->flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 			xlrec->node = relation->rd_node;
 			xlrec->blkno = BufferGetBlockNumber(buffer);
 			xlrec->ntuples = nthispage;
@@ -2418,6 +2462,13 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 					   datalen);
 				tuphdr->datalen = datalen;
 				scratchptr += datalen;
+
+				/*
+				 * We don't use heap_multi_insert for catalog tuples yet, but
+				 * better be prepared...
+				 */
+				if (need_cids)
+					log_heap_new_cid(relation, heaptup);
 			}
 			totaldatalen = scratchptr - tupledata;
 			Assert((scratchptr - scratch) < BLCKSZ);
@@ -2429,17 +2480,33 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 
 			rdata[1].data = tupledata;
 			rdata[1].len = totaldatalen;
-			rdata[1].buffer = buffer;
+			rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;
 			rdata[1].buffer_std = true;
 			rdata[1].next = NULL;
 
 			/*
+			 * add record for the buffer without actual content thats removed if
+			 * fpw is done for that buffer
+			 */
+			if (need_tuple_data)
+			{
+				rdata[1].next = &(rdata[2]);
+
+				rdata[2].data = NULL;
+				rdata[2].len = 0;
+				rdata[2].buffer = buffer;
+				rdata[2].buffer_std = true;
+				rdata[2].next = NULL;
+				xlrec->flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+			}
+
+			/*
 			 * If we're going to reinitialize the whole page using the WAL
 			 * record, hide buffer reference from XLogInsert.
 			 */
 			if (init)
 			{
-				rdata[1].buffer = InvalidBuffer;
+				rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
 				info |= XLOG_HEAP_INIT_PAGE;
 			}
 
@@ -2559,6 +2626,9 @@ heap_delete(Relation relation, ItemPointer tid,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
+	bool		need_tuple_data = RelationNeedsWAL(relation) &&
+		RelationIsLogicallyLogged(relation);
+	HeapTuple idx_tuple = NULL; /* primary key of the tuple */
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -2732,6 +2802,15 @@ l1:
 	/* replace cid with a combo cid if necessary */
 	HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
 
+	/*
+	 * Compute primary key tuple before entering the critical section so we
+	 * don't PANIC uppon a memory allocation failure.
+	 */
+	if (need_tuple_data)
+	{
+		idx_tuple = ExtractKeyTuple(relation, &tp);
+	}
+
 	START_CRIT_SECTION();
 
 	/*
@@ -2784,9 +2863,13 @@ l1:
 	{
 		xl_heap_delete xlrec;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[2];
+		XLogRecData rdata[4];
+
+		/* For logical decode we need combocids to properly decode the catalog */
+		if (RelationIsDoingTimetravel(relation))
+			log_heap_new_cid(relation, &tp);
 
-		xlrec.all_visible_cleared = all_visible_cleared;
+		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
 											  tp.t_data->t_infomask2);
 		xlrec.target.node = relation->rd_node;
@@ -2803,6 +2886,34 @@ l1:
 		rdata[1].buffer_std = true;
 		rdata[1].next = NULL;
 
+		/*
+		 * Log primary key of the deleted tuple
+		 */
+		if (need_tuple_data && idx_tuple != NULL)
+		{
+			xl_heap_header xlhdr;
+
+			xlhdr.t_infomask2 = idx_tuple->t_data->t_infomask2;
+			xlhdr.t_infomask = idx_tuple->t_data->t_infomask;
+			xlhdr.t_hoff = idx_tuple->t_data->t_hoff;
+
+			rdata[1].next = &(rdata[2]);
+			rdata[2].data = (char*)&xlhdr;
+			rdata[2].len = SizeOfHeapHeader;
+			rdata[2].buffer = InvalidBuffer;
+			rdata[2].next = NULL;
+
+			rdata[2].next = &(rdata[3]);
+			rdata[3].data = (char *) idx_tuple->t_data
+				+ offsetof(HeapTupleHeaderData, t_bits);
+			rdata[3].len = idx_tuple->t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+			rdata[3].buffer = InvalidBuffer;
+			rdata[3].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+		}
+
 		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE, rdata);
 
 		PageSetLSN(page, recptr);
@@ -2932,9 +3043,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
+	Bitmapset  *ckey_attrs;
 	ItemId		lp;
 	HeapTupleData oldtup;
 	HeapTuple	heaptup;
+	HeapTuple	old_idx_tuple = NULL;
 	Page		page;
 	BlockNumber block;
 	MultiXactStatus mxact_status;
@@ -2950,6 +3063,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		iscombo;
 	bool		satisfies_hot;
 	bool		satisfies_key;
+	bool		satisfies_ckey;
 	bool		use_hot_update = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
@@ -2977,8 +3091,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * Note that we get a copy here, so we need not worry about relcache flush
 	 * happening midway through.
 	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation, false);
-	key_attrs = RelationGetIndexAttrBitmap(relation, true);
+	hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
+	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	ckey_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_CANDIDATE_KEY);
 
 	block = ItemPointerGetBlockNumber(otid);
 	buffer = ReadBuffer(relation, block);
@@ -3036,9 +3152,9 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitiously arrive at the same key values.
 	 */
-	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs,
+	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, ckey_attrs,
 								 &satisfies_hot, &satisfies_key,
-								 &oldtup, newtup);
+								 &satisfies_ckey, &oldtup, newtup);
 	if (satisfies_key)
 	{
 		*lockmode = LockTupleNoKeyExclusive;
@@ -3508,6 +3624,12 @@ l2:
 		PageSetFull(page);
 	}
 
+	/* compute tuple for loggical logging */
+	if (!satisfies_ckey && RelationIsLogicallyLogged(relation))
+	{
+		old_idx_tuple = ExtractKeyTuple(relation, &oldtup);
+	}
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -3583,11 +3705,20 @@ l2:
 	/* XLOG stuff */
 	if (RelationNeedsWAL(relation))
 	{
-		XLogRecPtr	recptr = log_heap_update(relation, buffer,
-											 newbuf, &oldtup, heaptup,
-											 all_visible_cleared,
-											 all_visible_cleared_new);
+		XLogRecPtr	recptr;
+
+		/* For logical decode we need combocids to properly decode the catalog */
+		if (RelationIsDoingTimetravel(relation))
+		{
+			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, heaptup);
+		}
 
+		recptr = log_heap_update(relation, buffer,
+								 newbuf, &oldtup, heaptup,
+								 old_idx_tuple,
+								 all_visible_cleared,
+								 all_visible_cleared_new);
 		if (newbuf != buffer)
 		{
 			PageSetLSN(BufferGetPage(newbuf), recptr);
@@ -3739,18 +3870,23 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
  * modify columns used in the key.
  */
 static void
-HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
+HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs,
+							 Bitmapset *key_attrs, Bitmapset *ckey_attrs,
 							 bool *satisfies_hot, bool *satisfies_key,
+							 bool *satisfies_ckey,
 							 HeapTuple oldtup, HeapTuple newtup)
 {
 	int			next_hot_attnum;
 	int			next_key_attnum;
+	int			next_ckey_attnum;
 	bool		hot_result = true;
 	bool		key_result = true;
-	bool		key_done = false;
+	bool		ckey_result = true;
 	bool		hot_done = false;
 
+	Assert(bms_is_subset(ckey_attrs, key_attrs));
+	Assert(bms_is_subset(key_attrs, hot_attrs));
+
 	next_hot_attnum = bms_first_member(hot_attrs);
 	if (next_hot_attnum == -1)
 		hot_done = true;
@@ -3759,28 +3895,25 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 		next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
 
 	next_key_attnum = bms_first_member(key_attrs);
-	if (next_key_attnum == -1)
-		key_done = true;
-	else
+	if (next_key_attnum != -1)
 		/* Adjust for system attributes */
 		next_key_attnum += FirstLowInvalidHeapAttributeNumber;
 
+	next_ckey_attnum = bms_first_member(ckey_attrs);
+	if (next_ckey_attnum != -1)
+		/* Adjust for system attributes */
+		next_ckey_attnum += FirstLowInvalidHeapAttributeNumber;
+
 	for (;;)
 	{
 		int			check_now;
 		bool		changed;
 
-		/* both bitmapsets are now empty */
-		if (key_done && hot_done)
+		/* bitmapsets are now empty, hot includes others */
+		if (hot_done)
 			break;
 
-		/* XXX there's probably an easier way ... */
-		if (hot_done)
-			check_now = next_key_attnum;
-		if (key_done)
-			check_now = next_hot_attnum;
-		else
-			check_now = Min(next_hot_attnum, next_key_attnum);
+		check_now = next_hot_attnum;
 
 		changed = !heap_tuple_attr_equals(RelationGetDescr(relation),
 										  check_now, oldtup, newtup);
@@ -3790,11 +3923,15 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 				hot_result = false;
 			if (check_now == next_key_attnum)
 				key_result = false;
+			if (check_now == next_ckey_attnum)
+				ckey_result = false;
 		}
 
 		/* if both are false now, we can stop checking */
-		if (!hot_result && !key_result)
+		if (!hot_result && !key_result && !ckey_result)
+		{
 			break;
+		}
 
 		if (check_now == next_hot_attnum)
 		{
@@ -3808,16 +3945,22 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 		if (check_now == next_key_attnum)
 		{
 			next_key_attnum = bms_first_member(key_attrs);
-			if (next_key_attnum == -1)
-				key_done = true;
-			else
+			if (next_key_attnum != -1)
 				/* Adjust for system attributes */
 				next_key_attnum += FirstLowInvalidHeapAttributeNumber;
 		}
+		if (check_now == next_ckey_attnum)
+		{
+			next_ckey_attnum = bms_first_member(ckey_attrs);
+			if (next_ckey_attnum != -1)
+				/* Adjust for system attributes */
+				next_ckey_attnum += FirstLowInvalidHeapAttributeNumber;
+		}
 	}
 
 	*satisfies_hot = hot_result;
 	*satisfies_key = key_result;
+	*satisfies_ckey = ckey_result;
 }
 
 /*
@@ -5839,15 +5982,22 @@ log_heap_visible(RelFileNode rnode, Buffer heap_buffer, Buffer vm_buffer,
 static XLogRecPtr
 log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
+				HeapTuple idx_tuple,
 				bool all_visible_cleared, bool new_all_visible_cleared)
 {
 	xl_heap_update xlrec;
-	xl_heap_header xlhdr;
+	xl_heap_header_len xlhdr;
+	xl_heap_header_len xlhdr_idx;
 	uint8		info;
 	XLogRecPtr	recptr;
-	XLogRecData rdata[4];
+	XLogRecData rdata[7];
 	Page		page = BufferGetPage(newbuf);
 
+	/*
+	 * Just as for XLOG_HEAP_INSERT we need to make sure the tuple
+	 */
+	bool        need_tuple_data = RelationIsLogicallyLogged(reln);
+
 	/* Caller should not call me on a non-WAL-logged relation */
 	Assert(RelationNeedsWAL(reln));
 
@@ -5862,9 +6012,12 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	xlrec.old_infobits_set = compute_infobits(oldtup->t_data->t_infomask,
 											  oldtup->t_data->t_infomask2);
 	xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
-	xlrec.all_visible_cleared = all_visible_cleared;
+	xlrec.flags = 0;
+	if (all_visible_cleared)
+		xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED;
 	xlrec.newtid = newtup->t_self;
-	xlrec.new_all_visible_cleared = new_all_visible_cleared;
+	if (new_all_visible_cleared)
+		xlrec.flags |= XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED;
 
 	rdata[0].data = (char *) &xlrec;
 	rdata[0].len = SizeOfHeapUpdate;
@@ -5877,33 +6030,78 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	rdata[1].buffer_std = true;
 	rdata[1].next = &(rdata[2]);
 
-	xlhdr.t_infomask2 = newtup->t_data->t_infomask2;
-	xlhdr.t_infomask = newtup->t_data->t_infomask;
-	xlhdr.t_hoff = newtup->t_data->t_hoff;
+	xlhdr.header.t_infomask2 = newtup->t_data->t_infomask2;
+	xlhdr.header.t_infomask = newtup->t_data->t_infomask;
+	xlhdr.header.t_hoff = newtup->t_data->t_hoff;
+	xlhdr.t_len = newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
 
-	/*
-	 * As with insert records, we need not store the rdata[2] segment if we
-	 * decide to store the whole buffer instead.
-	 */
 	rdata[2].data = (char *) &xlhdr;
-	rdata[2].len = SizeOfHeapHeader;
-	rdata[2].buffer = newbuf;
+	rdata[2].len = SizeOfHeapHeaderLen;
+	rdata[2].buffer = need_tuple_data ? InvalidBuffer : newbuf;
 	rdata[2].buffer_std = true;
 	rdata[2].next = &(rdata[3]);
 
 	/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
-	rdata[3].data = (char *) newtup->t_data + offsetof(HeapTupleHeaderData, t_bits);
+	rdata[3].data = (char *) newtup->t_data
+		+ offsetof(HeapTupleHeaderData, t_bits);
 	rdata[3].len = newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
-	rdata[3].buffer = newbuf;
+	rdata[3].buffer = need_tuple_data ? InvalidBuffer : newbuf;
 	rdata[3].buffer_std = true;
 	rdata[3].next = NULL;
 
+	/*
+	 * separate storage for the buffer reference of the new page in the
+	 * wal_level >= logical case
+	*/
+	if(need_tuple_data)
+	{
+		rdata[3].next = &(rdata[4]);
+
+		rdata[4].data = NULL,
+		rdata[4].len = 0;
+		rdata[4].buffer = newbuf;
+		rdata[4].buffer_std = true;
+		rdata[4].next = NULL;
+		xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+
+		/* candidate key changed and we have a candidate key */
+		if (idx_tuple)
+		{
+			/* don't really need this, but its more comfy */
+			xlhdr_idx.header.t_infomask2 = idx_tuple->t_data->t_infomask2;
+			xlhdr_idx.header.t_infomask = idx_tuple->t_data->t_infomask;
+			xlhdr_idx.header.t_hoff = idx_tuple->t_data->t_hoff;
+			xlhdr_idx.t_len = idx_tuple->t_len;
+
+			rdata[4].next = &(rdata[5]);
+			rdata[5].data = (char *) &xlhdr_idx;
+			rdata[5].len = SizeOfHeapHeaderLen;
+			rdata[5].buffer = InvalidBuffer;
+			rdata[5].next = &(rdata[6]);
+
+			/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
+			rdata[6].data = (char *) idx_tuple->t_data
+				+ offsetof(HeapTupleHeaderData, t_bits);
+			rdata[6].len = idx_tuple->t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+			rdata[6].buffer = InvalidBuffer;
+			rdata[6].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+		}
+	}
+
 	/* If new tuple is the single and first tuple on page... */
 	if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
 		PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 	{
+		XLogRecData *rcur = &rdata[0];
 		info |= XLOG_HEAP_INIT_PAGE;
-		rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
+		while (rcur != NULL)
+		{
+			rcur->buffer = InvalidBuffer;
+			rcur = rcur->next;
+		}
 	}
 
 	recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -6010,6 +6208,112 @@ log_newpage_buffer(Buffer buffer)
 }
 
 /*
+ * Perform XLogInsert of a XLOG_HEAP2_NEW_CID record
+ *
+ * This is only used in wal_level >= WAL_LEVEL_LOGICAL
+ */
+static XLogRecPtr
+log_heap_new_cid(Relation relation, HeapTuple tup)
+{
+	xl_heap_new_cid xlrec;
+
+	XLogRecPtr	recptr;
+	XLogRecData rdata[1];
+	HeapTupleHeader hdr = tup->t_data;
+
+	Assert(ItemPointerIsValid(&tup->t_self));
+	Assert(tup->t_tableOid != InvalidOid);
+
+	xlrec.top_xid = GetTopTransactionId();
+	xlrec.target.node = relation->rd_node;
+	xlrec.target.tid = tup->t_self;
+
+	/*
+	 * if the tuple got inserted & deleted in the same TX we definitely have a
+	 * combocid, set cmin and cmax.
+	 */
+	if (hdr->t_infomask & HEAP_COMBOCID)
+	{
+		xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
+		xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
+		xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
+	}
+	/* No combocid, so only cmin or cmax can be set by this TX */
+	else
+	{
+		/* tuple inserted */
+		if (hdr->t_infomask & HEAP_XMAX_INVALID)
+		{
+			xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr);
+			xlrec.cmax = InvalidCommandId;
+		}
+		/* tuple from a different tx updated or deleted */
+		else
+		{
+			xlrec.cmin = InvalidCommandId;
+			xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr);
+
+		}
+		xlrec.combocid = InvalidCommandId;
+	}
+
+	rdata[0].data = (char *) &xlrec;
+	rdata[0].len = SizeOfHeapNewCid;
+	rdata[0].buffer = InvalidBuffer;
+	rdata[0].next = NULL;
+
+	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID, rdata);
+
+	return recptr;
+}
+
+static HeapTuple
+ExtractKeyTuple(Relation relation, HeapTuple tp)
+{
+	HeapTuple idx_tuple = NULL;
+	TupleDesc desc = RelationGetDescr(relation);
+	Relation idx_rel;
+	TupleDesc idx_desc;
+	Datum idx_vals[INDEX_MAX_KEYS];
+	bool idx_isnull[INDEX_MAX_KEYS];
+	int natt;
+
+	/* needs to already have been fetched? */
+	if (relation->rd_indexvalid == 0)
+		RelationGetIndexList(relation);
+
+	if (!OidIsValid(relation->rd_primary))
+	{
+		elog(DEBUG1, "Could not find primary key for table with oid %u",
+			 RelationGetRelid(relation));
+	}
+	else
+	{
+		idx_rel = RelationIdGetRelation(relation->rd_primary);
+		idx_desc = RelationGetDescr(idx_rel);
+
+		for (natt = 0; natt < idx_desc->natts; natt++)
+		{
+			int attno = idx_rel->rd_index->indkey.values[natt];
+			if (attno == ObjectIdAttributeNumber)
+			{
+				idx_vals[natt] = HeapTupleGetOid(tp);
+				idx_isnull[natt] = false;
+			}
+			else
+			{
+				idx_vals[natt] =
+					fastgetattr(tp, attno, desc, &idx_isnull[natt]);
+			}
+			Assert(!idx_isnull[natt]);
+		}
+		idx_tuple = heap_form_tuple(idx_desc, idx_vals, idx_isnull);
+		RelationClose(idx_rel);
+	}
+	return idx_tuple;
+}
+
+/*
  * Handles CLEANUP_INFO
  */
 static void
@@ -6370,7 +6674,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6419,7 +6723,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	/* Make sure there is no forward chain link in t_ctid */
@@ -6453,7 +6757,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6524,7 +6828,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 
 	PageSetLSN(page, lsn);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	MarkBufferDirty(buffer);
@@ -6587,7 +6891,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6670,7 +6974,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 
 	PageSetLSN(page, lsn);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	MarkBufferDirty(buffer);
@@ -6709,7 +7013,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 		HeapTupleHeaderData hdr;
 		char		data[MaxHeapTupleSize];
 	}			tbuf;
-	xl_heap_header xlhdr;
+	xl_heap_header_len xlhdr;
 	int			hsize;
 	uint32		newlen;
 	Size		freespace;
@@ -6718,7 +7022,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		BlockNumber block = ItemPointerGetBlockNumber(&xlrec->target.tid);
@@ -6796,7 +7100,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	/*
@@ -6820,7 +7124,7 @@ newt:;
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->new_all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		BlockNumber block = ItemPointerGetBlockNumber(&xlrec->newtid);
@@ -6878,13 +7182,13 @@ newsame:;
 	if (PageGetMaxOffsetNumber(page) + 1 < offnum)
 		elog(PANIC, "heap_update_redo: invalid max offset number");
 
-	hsize = SizeOfHeapUpdate + SizeOfHeapHeader;
+	hsize = SizeOfHeapUpdate + SizeOfHeapHeaderLen;
 
-	newlen = record->xl_len - hsize;
-	Assert(newlen <= MaxHeapTupleSize);
 	memcpy((char *) &xlhdr,
 		   (char *) xlrec + SizeOfHeapUpdate,
-		   SizeOfHeapHeader);
+		   SizeOfHeapHeaderLen);
+	newlen = xlhdr.t_len;
+	Assert(newlen <= MaxHeapTupleSize);
 	htup = &tbuf.hdr;
 	MemSet((char *) htup, 0, sizeof(HeapTupleHeaderData));
 	/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
@@ -6892,9 +7196,9 @@ newsame:;
 		   (char *) xlrec + hsize,
 		   newlen);
 	newlen += offsetof(HeapTupleHeaderData, t_bits);
-	htup->t_infomask2 = xlhdr.t_infomask2;
-	htup->t_infomask = xlhdr.t_infomask;
-	htup->t_hoff = xlhdr.t_hoff;
+	htup->t_infomask2 = xlhdr.header.t_infomask2;
+	htup->t_infomask = xlhdr.header.t_infomask;
+	htup->t_hoff = xlhdr.header.t_hoff;
 
 	HeapTupleHeaderSetXmin(htup, record->xl_xid);
 	HeapTupleHeaderSetCmin(htup, FirstCommandId);
@@ -6906,7 +7210,7 @@ newsame:;
 	if (offnum == InvalidOffsetNumber)
 		elog(PANIC, "heap_update_redo: failed to add tuple");
 
-	if (xlrec->new_all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	freespace = PageGetHeapFreeSpace(page);		/* needed to update FSM below */
@@ -7157,6 +7461,9 @@ heap2_redo(XLogRecPtr lsn, XLogRecord *record)
 		case XLOG_HEAP2_LOCK_UPDATED:
 			heap_xlog_lock_updated(lsn, record);
 			break;
+		case XLOG_HEAP2_NEW_CID:
+			/* nothing to do on a real replay, only during logical decoding */
+			break;
 		default:
 			elog(PANIC, "heap2_redo: unknown op code %u", info);
 	}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 3ec10a0..7fe9f32 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -75,6 +75,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
 	Page		page = BufferGetPage(buffer);
 	Size		minfree;
 
+	Assert(TransactionIdIsValid(OldestXmin));
+
 	/*
 	 * Let's see if we really need pruning.
 	 *
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index b878155..3bac4a5 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -67,7 +67,10 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "access/xlog.h"
+
 #include "catalog/index.h"
+#include "catalog/catalog.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -520,8 +523,15 @@ index_fetch_heap(IndexScanDesc scan)
 		 * Prune page, but only if we weren't already on this page
 		 */
 		if (prev_buf != scan->xs_cbuf)
-			heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
-								RecentGlobalXmin);
+		{
+			if (IsSystemRelation(scan->heapRelation)
+				|| RelationIsDoingTimetravel(scan->heapRelation))
+				heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
+									RecentGlobalXmin);
+			else
+				heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
+									RecentGlobalDataXmin);
+		}
 	}
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index bc8b985..c750fef 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -184,6 +184,15 @@ heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
 						 xlrec->infobits_set);
 		out_target(buf, &(xlrec->target));
 	}
+	else if (info == XLOG_HEAP2_NEW_CID)
+	{
+		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
+
+		appendStringInfo(buf, "new_cid: ");
+		out_target(buf, &(xlrec->target));
+		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
+						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
+	}
 	else
 		appendStringInfo(buf, "UNKNOWN");
 }
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 1b36f9a..e0900e2 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -28,6 +28,7 @@ const struct config_enum_entry wal_level_options[] = {
 	{"minimal", WAL_LEVEL_MINIMAL, false},
 	{"archive", WAL_LEVEL_ARCHIVE, false},
 	{"hot_standby", WAL_LEVEL_HOT_STANDBY, false},
+	{"logical", WAL_LEVEL_LOGICAL, false},
 	{NULL, 0, false}
 };
 
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index e975f8d..d46a50e 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -47,6 +47,7 @@
 #include "access/twophase.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
+#include "access/xlog.h"
 #include "access/xlogutils.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
@@ -1920,7 +1921,8 @@ RecoverPreparedTransactions(void)
 			 * the prepared transaction generated xid assignment records. Test
 			 * here must match one used in AssignTransactionId().
 			 */
-			if (InHotStandby && hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS)
+			if (InHotStandby && (hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS ||
+			                     XLogLogicalInfoActive()))
 				overwriteOK = true;
 
 			/*
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 0591f3f..b937ffe 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -146,6 +146,7 @@ typedef struct TransactionStateData
 	int			prevSecContext; /* previous SecurityRestrictionContext */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
+	bool		guaranteedlyLogged;		/* has xid been logged? */
 	struct TransactionStateData *parent;		/* back link to parent */
 } TransactionStateData;
 
@@ -175,6 +176,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* previous SecurityRestrictionContext */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
+	false,						/* guaranteedlyLogged */
 	NULL						/* link to parent state block */
 };
 
@@ -391,6 +393,21 @@ GetCurrentTransactionIdIfAny(void)
 }
 
 /*
+ *	MarkCurrentTransactionIdLoggedIfAny
+ *
+ * Remember that the current xid - if it is assigned - now has been wal logged.
+ */
+void
+MarkCurrentTransactionIdLoggedIfAny(void)
+{
+	if (TransactionIdIsValid(CurrentTransactionState->transactionId))
+	{
+		CurrentTransactionState->guaranteedlyLogged = true;
+	}
+}
+
+
+/*
  *	GetStableLatestTransactionId
  *
  * Get the transaction's XID if it has one, else read the next-to-be-assigned
@@ -431,6 +448,7 @@ AssignTransactionId(TransactionState s)
 {
 	bool		isSubXact = (s->parent != NULL);
 	ResourceOwner currentOwner;
+	bool log_unknown_top = false;
 
 	/* Assert that caller didn't screw up */
 	Assert(!TransactionIdIsValid(s->transactionId));
@@ -438,7 +456,7 @@ AssignTransactionId(TransactionState s)
 
 	/*
 	 * Ensure parent(s) have XIDs, so that a child always has an XID later
-	 * than its parent.  Musn't recurse here, or we might get a stack overflow
+	 * than its parent.  May not recurse here, or we might get a stack overflow
 	 * if we're at the bottom of a huge stack of subtransactions none of which
 	 * have XIDs yet.
 	 */
@@ -455,6 +473,8 @@ AssignTransactionId(TransactionState s)
 			p = p->parent;
 		}
 
+		Assert(parentOffset);
+
 		/*
 		 * This is technically a recursive call, but the recursion will never
 		 * be more than one layer deep.
@@ -466,6 +486,21 @@ AssignTransactionId(TransactionState s)
 	}
 
 	/*
+	 * When wal_level=logical, guarantee that a subtransaction's xid can only
+	 * be seen in the WAL stream if its toplevel xid has been logged before. If
+	 * necessary we log a xact_assignment record with fewer than
+	 * PGPROC_MAX_CACHED_SUBXIDS. Note that it is fine if guaranteedlyLogged
+	 * isn't set for a transaction even though it appears in a wal record,
+	 * we'll just superfluously log something.
+	 */
+	if (isSubXact && XLogLogicalInfoActive() &&
+		!TopTransactionStateData.guaranteedlyLogged)
+	{
+		log_unknown_top = true;
+	}
+
+
+	/*
 	 * Generate a new Xid and record it in PG_PROC and pg_subtrans.
 	 *
 	 * NB: we must make the subtrans entry BEFORE the Xid appears anywhere in
@@ -519,6 +554,9 @@ AssignTransactionId(TransactionState s)
 	 * top-level transaction that each subxact belongs to. This is correct in
 	 * recovery only because aborted subtransactions are separately WAL
 	 * logged.
+	 *
+	 * This is correct even for the case where several levels above us didn't
+	 * have an xid assigned as we recursed up to them beforehand.
 	 */
 	if (isSubXact && XLogStandbyInfoActive())
 	{
@@ -529,7 +567,8 @@ AssignTransactionId(TransactionState s)
 		 * ensure this test matches similar one in
 		 * RecoverPreparedTransactions()
 		 */
-		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS)
+		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS ||
+		    log_unknown_top)
 		{
 			XLogRecData rdata[2];
 			xl_xact_assignment xlrec;
@@ -548,13 +587,15 @@ AssignTransactionId(TransactionState s)
 			rdata[0].next = &rdata[1];
 
 			rdata[1].data = (char *) unreportedXids;
-			rdata[1].len = PGPROC_MAX_CACHED_SUBXIDS * sizeof(TransactionId);
+			rdata[1].len = nUnreportedXids * sizeof(TransactionId);
 			rdata[1].buffer = InvalidBuffer;
 			rdata[1].next = NULL;
 
 			(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT, rdata);
 
 			nUnreportedXids = 0;
+			/* mark top, not current xact as having been logged */
+			TopTransactionStateData.guaranteedlyLogged = true;
 		}
 	}
 }
@@ -1733,6 +1774,7 @@ StartTransaction(void)
 	 * initialize reported xid accounting
 	 */
 	nUnreportedXids = 0;
+	s->guaranteedlyLogged = false;
 
 	/*
 	 * must initialize resource-management stuff first
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fc495d6..fbb505d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -41,6 +41,7 @@
 #include "postmaster/startup.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/logical.h"
 #include "storage/barrier.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1191,6 +1192,8 @@ begin:;
 	 */
 	WALInsertSlotRelease();
 
+	MarkCurrentTransactionIdLoggedIfAny();
+
 	END_CRIT_SECTION();
 
 	/*
@@ -6332,6 +6335,13 @@ StartupXLOG(void)
 	XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
 	XLogCtl->ckptXid = checkPoint.nextXid;
 
+
+	/*
+	 * Startup logical state, needs to be setup now so we have proper data
+	 * during restore. XXX
+	 */
+	StartupLogicalReplication(checkPoint.redo);
+
 	/*
 	 * Initialize unlogged LSN. On a clean shutdown, it's restored from the
 	 * control file. On recovery, all unlogged relations are blown away, so
@@ -8312,7 +8322,7 @@ CreateCheckPoint(int flags)
 	 * StartupSUBTRANS hasn't been called yet.
 	 */
 	if (!RecoveryInProgress())
-		TruncateSUBTRANS(GetOldestXmin(true, false));
+		TruncateSUBTRANS(GetOldestXmin(true, true, false, false));
 
 	/* Real work is done, but log and update stats before releasing lock. */
 	LogCheckpointEnd(false);
@@ -8672,7 +8682,7 @@ CreateRestartPoint(int flags)
 	 * this because StartupSUBTRANS hasn't been called yet.
 	 */
 	if (EnableHotStandby)
-		TruncateSUBTRANS(GetOldestXmin(true, false));
+		TruncateSUBTRANS(GetOldestXmin(true, true, false, false));
 
 	/* Real work is done, but log and update before releasing lock. */
 	LogCheckpointEnd(true);
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index c1287a7..0d4cfcb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -106,7 +106,6 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
 	return path;
 }
 
-
 /*
  * IsSystemRelation
  *		True iff the relation is a system catalog relation.
@@ -123,8 +122,17 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
 bool
 IsSystemRelation(Relation relation)
 {
-	return IsSystemNamespace(RelationGetNamespace(relation)) ||
-		IsToastNamespace(RelationGetNamespace(relation));
+	return IsSystemRelationId(RelationGetRelid(relation));
+}
+
+/*
+ * IsSystemRelationId
+ *		True iff the relation is a system catalog relation.
+ */
+bool
+IsSystemRelationId(Oid relid)
+{
+	return relid < FirstNormalObjectId;
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b73ee4f..49ea38b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2174,9 +2174,20 @@ IndexBuildHeapScan(Relation heapRelation,
 	}
 	else
 	{
+		/*
+		 * We can ignore a) pegged xmins b) shared relations if we don't scan
+		 * something acting as a catalog.
+		 */
+		bool include_systables =
+			IsSystemRelation(heapRelation) ||
+			RelationIsDoingTimetravel(heapRelation);
+
 		snapshot = SnapshotAny;
 		/* okay to ignore lazy VACUUMs here */
-		OldestXmin = GetOldestXmin(heapRelation->rd_rel->relisshared, true);
+		OldestXmin = GetOldestXmin(heapRelation->rd_rel->relisshared,
+								   include_systables,
+								   true,
+								   false);
 	}
 
 	scan = heap_beginscan_strat(heapRelation,	/* relation */
@@ -3340,7 +3351,7 @@ reindex_relation(Oid relid, int flags)
 
 	/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
 	if (is_pg_class)
-		(void) RelationGetIndexAttrBitmap(rel, false);
+		(void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL);
 
 	PG_TRY();
 	{
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 575a40f..2acaf54 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -613,6 +613,16 @@ CREATE VIEW pg_stat_replication AS
     WHERE S.usesysid = U.oid AND
             S.pid = W.pid;
 
+CREATE VIEW pg_stat_logical_decoding AS
+    SELECT
+            L.slot_name,
+            L.plugin,
+            L.database,
+            L.active,
+            L.xmin,
+            L.restart_decoding_lsn
+    FROM pg_stat_get_logical_decoding_slots() AS L;
+
 CREATE VIEW pg_stat_database AS
     SELECT
             D.oid AS datid,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9845b0b..7a05cea 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1081,7 +1081,7 @@ acquire_sample_rows(Relation onerel, int elevel,
 	totalblocks = RelationGetNumberOfBlocks(onerel);
 
 	/* Need a cutoff xmin for HeapTupleSatisfiesVacuum */
-	OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true);
+	OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true, true, false);
 
 	/* Prepare for sampling block numbers */
 	BlockSampler_Init(&bs, totalblocks, targrows);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index f6a5bfe..76b2904 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -859,6 +859,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 	 */
 	vacuum_set_xid_limits(freeze_min_age, freeze_table_age,
 						  OldHeap->rd_rel->relisshared,
+						  IsSystemRelation(OldHeap)
+						  || RelationIsDoingTimetravel(OldHeap),
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff);
 
 	/*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d86e9ad..912f7a8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2355,7 +2355,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	 * concurrency.
 	 */
 	modifiedCols = GetModifiedColumns(relinfo, estate);
-	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, true);
+	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+										 INDEX_ATTR_BITMAP_KEY);
 	if (bms_overlap(keyCols, modifiedCols))
 		lockmode = LockTupleExclusive;
 	else
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 27aea73..3528c27 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -381,6 +381,7 @@ void
 vacuum_set_xid_limits(int freeze_min_age,
 					  int freeze_table_age,
 					  bool sharedRel,
+					  bool catalogRel,
 					  TransactionId *oldestXmin,
 					  TransactionId *freezeLimit,
 					  TransactionId *freezeTableLimit,
@@ -399,7 +400,7 @@ vacuum_set_xid_limits(int freeze_min_age,
 	 * working on a particular table at any time, and that each vacuum is
 	 * always an independent transaction.
 	 */
-	*oldestXmin = GetOldestXmin(sharedRel, true);
+	*oldestXmin = GetOldestXmin(sharedRel, catalogRel, true, false);
 
 	Assert(TransactionIdIsNormal(*oldestXmin));
 
@@ -720,7 +721,7 @@ vac_update_datfrozenxid(void)
 	 * committed pg_class entries for new tables; see AddNewRelationTuple().
 	 * So we cannot produce a wrong minimum by starting with this.
 	 */
-	newFrozenXid = GetOldestXmin(true, true);
+	newFrozenXid = GetOldestXmin(true, true, true, false);
 
 	/*
 	 * Similarly, initialize the MultiXact "min" with the value that would be
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index bb4e03e..3e90a1a 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -44,6 +44,7 @@
 #include "access/multixact.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
+#include "catalog/catalog.h"
 #include "catalog/storage.h"
 #include "commands/dbcommands.h"
 #include "commands/vacuum.h"
@@ -202,6 +203,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
 
 	vacuum_set_xid_limits(vacstmt->freeze_min_age, vacstmt->freeze_table_age,
 						  onerel->rd_rel->relisshared,
+						  IsSystemRelation(onerel)
+						  || RelationIsDoingTimetravel(onerel),
 						  &OldestXmin, &FreezeLimit, &freezeTableLimit,
 						  &MultiXactCutoff);
 	scan_all = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index a31b01d..8a52cdc 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -818,7 +818,7 @@ PostmasterMain(int argc, char *argv[])
 				(errmsg("WAL archival (archive_mode=on) requires wal_level \"archive\" or \"hot_standby\"")));
 	if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
 		ereport(ERROR,
-				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\" or \"hot_standby\"")));
+				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\", \"logical\" or \"hot_standby\"")));
 
 	/*
 	 * Other one-time internal sanity checks can go here, if they are fast.
diff --git a/src/backend/replication/Makefile b/src/backend/replication/Makefile
index 2dde011..2e13e27 100644
--- a/src/backend/replication/Makefile
+++ b/src/backend/replication/Makefile
@@ -17,6 +17,8 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 OBJS = walsender.o walreceiverfuncs.o walreceiver.o basebackup.o \
 	repl_gram.o syncrep.o
 
+SUBDIRS = logical
+
 include $(top_srcdir)/src/backend/common.mk
 
 # repl_scanner is compiled as part of repl_gram
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
new file mode 100644
index 0000000..310a45c
--- /dev/null
+++ b/src/backend/replication/logical/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for src/backend/replication/logical
+#
+# IDENTIFICATION
+#    src/backend/replication/logical/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/replication/logical
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
+
+OBJS = decode.o logical.o logicalfuncs.o reorderbuffer.o snapbuild.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
new file mode 100644
index 0000000..53043b9
--- /dev/null
+++ b/src/backend/replication/logical/decode.c
@@ -0,0 +1,687 @@
+/*-------------------------------------------------------------------------
+ *
+ * decode.c
+ *		Decodes WAL records fed from xlogreader.h read into an reorderbuffer
+ *		while simultaneously letting snapbuild.c build an appropriate snapshots
+ *		to decode those.
+ *
+ * NOTE:
+ * 		This basically tries to handle all low level xlog stuff for
+ *      reorderbuffer.c and snapbuild.c. There's some minor leakage where a
+ *      specific record's struct is used to pass data along, but that's just
+ *      because those are convenient and uncomplicated to read.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/decode.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+
+#include "access/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog_internal.h"
+#include "access/xlogreader.h"
+
+#include "catalog/pg_control.h"
+
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h"
+
+#include "storage/standby.h"
+
+/* RMGR Handlers */
+static void DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+
+/* individual record(group)'s handlers */
+static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
+						 TransactionId xid, int nsubxacts, TransactionId *sub_xids,
+						 int ninval_msgs, SharedInvalidationMessage *msg);
+static void DecodeAbort(LogicalDecodingContext *ctx, XLogRecPtr lsn,
+			TransactionId xid, TransactionId *sub_xids, int nsubxacts,
+			bool was_commit);
+
+/* common function to decode tuples */
+static void DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tup);
+
+void
+DecodeRecordIntoReorderBuffer(LogicalDecodingContext *ctx,
+							  XLogRecordBuffer *buf)
+{
+	/* cast so we get a warning when new rmgrs are added */
+	switch ((RmgrIds) buf->record.xl_rmid)
+	{
+		case RM_XLOG_ID:
+			DecodeXLogOp(ctx, buf);
+			break;
+
+		case RM_XACT_ID:
+			DecodeXactOp(ctx, buf);
+			break;
+
+		case RM_STANDBY_ID:
+			DecodeStandbyOp(ctx, buf);
+			break;
+
+		case RM_HEAP_ID:
+			DecodeHeapOp(ctx, buf);
+			break;
+
+		case RM_HEAP2_ID:
+			DecodeHeap2Op(ctx, buf);
+			break;
+
+		/* irrelevant for changeset extraction */
+		case RM_SMGR_ID:
+		case RM_CLOG_ID:
+		case RM_DBASE_ID:
+		case RM_TBLSPC_ID:
+		case RM_MULTIXACT_ID:
+		case RM_RELMAP_ID:
+		case RM_BTREE_ID:
+		case RM_HASH_ID:
+		case RM_GIN_ID:
+		case RM_GIST_ID:
+		case RM_SEQ_ID:
+		case RM_SPGIST_ID:
+			break;
+		case RM_NEXT_ID:
+			elog(ERROR, "unexpected NEXT_ID record");
+	}
+}
+
+static void
+DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  	   *builder = ctx->snapshot_builder;
+	ReorderBuffer  *reorder = ctx->reorder;
+	XLogRecord	   *r = &buf->record;
+
+	/* no point in doing anything yet */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT)
+		return;
+
+	switch (r->xl_info & ~XLR_INFO_MASK)
+	{
+		case XLOG_XACT_COMMIT:
+			{
+				xl_xact_commit *xlrec;
+				TransactionId *subxacts = NULL;
+				SharedInvalidationMessage *invals = NULL;
+
+				xlrec = (xl_xact_commit *) buf->record_data;
+
+				subxacts = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+				invals = (SharedInvalidationMessage *) &(subxacts[xlrec->nsubxacts]);
+
+				/* FIXME: skip if wrong db? */
+
+				DecodeCommit(ctx, buf, r->xl_xid, xlrec->nsubxacts, subxacts,
+							 xlrec->nmsgs, invals);
+
+				break;
+			}
+		case XLOG_XACT_COMMIT_PREPARED:
+			{
+				xl_xact_commit_prepared *prec;
+				xl_xact_commit *xlrec;
+				TransactionId *subxacts;
+				SharedInvalidationMessage *invals = NULL;
+
+
+				prec = (xl_xact_commit_prepared *) buf->record_data;
+				xlrec = &prec->crec;
+
+				subxacts = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+				invals = (SharedInvalidationMessage *) &(subxacts[xlrec->nsubxacts]);
+
+				/* FIXME: skip if wrong db? */
+
+				DecodeCommit(ctx, buf, r->xl_xid, xlrec->nsubxacts, subxacts,
+							 xlrec->nmsgs, invals);
+
+				break;
+			}
+		case XLOG_XACT_COMMIT_COMPACT:
+			{
+				xl_xact_commit_compact *xlrec;
+
+#if 0
+				/* FIXME: should we error out? */
+				elog(WARNING, "unexpectedly got compact commit");
+#endif
+				xlrec = (xl_xact_commit_compact *) buf->record_data;
+
+				DecodeCommit(ctx, buf, r->xl_xid,
+							 xlrec->nsubxacts, xlrec->subxacts,
+							 0, NULL);
+				break;
+			}
+		case XLOG_XACT_ABORT:
+			{
+				xl_xact_abort *xlrec;
+				TransactionId *sub_xids;
+
+				xlrec = (xl_xact_abort *) buf->record_data;
+
+				sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+
+				DecodeAbort(ctx, buf->origptr, r->xl_xid,
+							sub_xids, xlrec->nsubxacts, false);
+				break;
+			}
+		case XLOG_XACT_ABORT_PREPARED:
+			{
+				xl_xact_abort_prepared *prec;
+				xl_xact_abort *xlrec;
+				TransactionId *sub_xids;
+
+				prec = (xl_xact_abort_prepared *) buf->record_data;
+				xlrec = &prec->arec;
+
+				sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
+
+				/* r->xl_xid is committed in a separate record */
+				DecodeAbort(ctx, buf->origptr, prec->xid,
+							sub_xids, xlrec->nsubxacts, false);
+				break;
+			}
+
+		case XLOG_XACT_ASSIGNMENT:
+			{
+				xl_xact_assignment *xlrec;
+				int			i;
+				TransactionId *sub_xid;
+
+				xlrec =	(xl_xact_assignment *) buf->record_data;
+
+				sub_xid = &xlrec->xsub[0];
+
+				for (i = 0; i < xlrec->nsubxacts; i++)
+				{
+					ReorderBufferAssignChild(reorder, xlrec->xtop,
+											 *(sub_xid++), buf->origptr);
+				}
+				break;
+			}
+		case XLOG_XACT_PREPARE:
+
+			/*
+			 * XXX: we could replay the transaction and prepare it
+			 * as well.
+			 */
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  *builder = ctx->snapshot_builder;
+	XLogRecord	   *r = &buf->record;
+
+	switch (r->xl_info & ~XLR_INFO_MASK)
+	{
+		case XLOG_RUNNING_XACTS:
+			SnapBuildProcessRunningXacts(builder, buf->origptr,
+										 (xl_running_xacts *) buf->record_data);
+			break;
+		case XLOG_STANDBY_LOCK:
+			break;
+		default:
+			elog(ERROR, "unexpected standby record type");
+	}
+}
+static void
+DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  *builder = ctx->snapshot_builder;
+
+	switch (buf->record.xl_info & ~XLR_INFO_MASK)
+	{
+		/* this is also used in END_OF_RECOVERY checkpoints */
+		case XLOG_CHECKPOINT_SHUTDOWN:
+		case XLOG_END_OF_RECOVERY:
+			SnapBuildSerializationPoint(builder, buf->origptr);
+
+			/*
+			 * abort all transactions that still deemed to be in progress, they
+			 * aren't actually in progress anymore. Do not abort prepared
+			 * transactions that have been prepared for commit.
+			 *
+			 * FIXME: implement.
+			 */
+			break;
+		case XLOG_CHECKPOINT_ONLINE:
+			/*
+			 * a RUNNING_XACTS record will have been logged near to this, we
+			 * can restart from there.
+			 */
+			break;
+		case XLOG_NOOP:
+		case XLOG_NEXTOID:
+		case XLOG_SWITCH:
+		case XLOG_BACKUP_END:
+		case XLOG_PARAMETER_CHANGE:
+		case XLOG_RESTORE_POINT:
+		case XLOG_FPW_CHANGE:
+		case XLOG_FPI:
+			break;
+	}
+}
+
+static void
+DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	uint8 info = buf->record.xl_info & XLOG_HEAP_OPMASK;
+	TransactionId xid = buf->record.xl_xid;
+	SnapBuild *builder = ctx->snapshot_builder;
+
+	/* no point in doing anything yet */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT)
+		return;
+
+	switch (info)
+	{
+		case XLOG_HEAP_INSERT:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeInsert(ctx, buf);
+			break;
+
+			/*
+			 * Treat HOT update as normal updates, there is no useful
+			 * information in the fact that we could make it a HOT update
+			 * locally and the WAL layout is compatible.
+			 */
+		case XLOG_HEAP_HOT_UPDATE:
+		case XLOG_HEAP_UPDATE:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeUpdate(ctx, buf);
+			break;
+
+		case XLOG_HEAP_DELETE:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeDelete(ctx, buf);
+			break;
+
+		case XLOG_HEAP_NEWPAGE:
+			/*
+			 * XXX: There doesn't seem to be a usecase for decoding
+			 * HEAP_NEWPAGE's. Its only used in various indexam's and CLUSTER,
+			 * neither of which should be relevant for the logical
+			 * changestream.
+			 */
+			break;
+		case XLOG_HEAP_INPLACE:
+			/* cannot be important for our purposes, not part of transaction */
+			if (!TransactionIdIsValid(xid))
+				break;
+
+			SnapBuildProcessChange(builder, xid, buf->origptr);
+			/* heap_inplace is only done in catalog modifying txns */
+			ReorderBufferXidSetTimetravel(ctx->reorder, xid, buf->origptr);
+			break;
+		case XLOG_HEAP_LOCK:
+			break;
+		default:
+			elog(ERROR, "unexpected info value %u", info);
+			break;
+	}
+}
+
+static void
+DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	uint8 info = buf->record.xl_info & XLOG_HEAP_OPMASK;
+	TransactionId xid = buf->record.xl_xid;
+	SnapBuild *builder = ctx->snapshot_builder;
+
+	/* no point in doing anything yet */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT)
+		return;
+
+	switch (info)
+	{
+		case XLOG_HEAP2_MULTI_INSERT:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeMultiInsert(ctx, buf);
+			break;
+		case XLOG_HEAP2_NEW_CID:
+			{
+				xl_heap_new_cid *xlrec;
+				xlrec = (xl_heap_new_cid *) buf->record_data;
+				SnapBuildProcessNewCid(builder, xid, buf->origptr, xlrec);
+
+				break;
+			}
+			/*
+			 * everything else here is just low level stuff we're not
+			 * interested in
+			 */
+		case XLOG_HEAP2_FREEZE:
+		case XLOG_HEAP2_CLEAN:
+		case XLOG_HEAP2_CLEANUP_INFO:
+		case XLOG_HEAP2_VISIBLE:
+		case XLOG_HEAP2_LOCK_UPDATED:
+			break;
+		default:
+			elog(ERROR, "unexpected info value %u", info);
+	}
+}
+
+static void
+DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, TransactionId xid,
+			 int nsubxacts, TransactionId *sub_xids,
+			 int ninval_msgs, SharedInvalidationMessage *msgs)
+{
+	int			i;
+
+	/* always need the invalidation messages */
+	if (ninval_msgs > 0)
+	{
+		ReorderBufferAddInvalidations(ctx->reorder, xid, buf->origptr,
+									  ninval_msgs, msgs);
+		ReorderBufferXidSetTimetravel(ctx->reorder, xid, buf->origptr);
+	}
+
+	SnapBuildCommitTxn(ctx->snapshot_builder, buf->origptr, xid,
+					   nsubxacts, sub_xids);
+
+	/*
+	 * If we are not interested in anything up to this LSN convert the commit
+	 * into an ABORT to cleanup.
+	 *
+	 * FIXME: this needs to replay invalidations anyway!
+	 */
+	if (SnapBuildXactNeedsSkip(ctx->snapshot_builder, buf->origptr))
+	{
+		DecodeAbort(ctx, buf->origptr, xid,	sub_xids, nsubxacts, true);
+		return;
+	}
+
+	for (i = 0; i < nsubxacts; i++)
+	{
+		ReorderBufferCommitChild(ctx->reorder, xid, *sub_xids,
+								 buf->origptr, buf->endptr);
+		sub_xids++;
+	}
+
+	/* replay actions of all transaction + subtransactions in order */
+	ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr);
+}
+
+static void
+DecodeAbort(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
+			TransactionId *sub_xids, int nsubxacts, bool was_commit)
+{
+	int			i;
+
+	/*
+	 * this is a bit grotty, but if we're "faking" an abort we've already gone
+	 * through
+	 */
+	if (!was_commit)
+		SnapBuildAbortTxn(ctx->snapshot_builder, xid,
+						  nsubxacts, sub_xids);
+
+
+	/* FIXME: process invalidations anyway if was_commit */
+
+	for (i = 0; i < nsubxacts; i++)
+	{
+		ReorderBufferAbort(ctx->reorder, *sub_xids, lsn);
+		sub_xids++;
+	}
+
+	ReorderBufferAbort(ctx->reorder, xid, lsn);
+}
+
+static void
+DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_insert *xlrec;
+	ReorderBufferChange *change;
+
+	xlrec = (xl_heap_insert *) buf->record_data;
+
+	/* XXX: nicer */
+	if (xlrec->target.node.dbNode != ctx->slot->database)
+		return;
+
+	change = ReorderBufferGetChange(ctx->reorder);
+	change->action = REORDER_BUFFER_CHANGE_INSERT;
+	memcpy(&change->relnode, &xlrec->target.node, sizeof(RelFileNode));
+
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
+	{
+		Assert(r->xl_len > (SizeOfHeapInsert + SizeOfHeapHeader));
+
+		change->newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+		DecodeXLogTuple((char *) xlrec + SizeOfHeapInsert,
+						r->xl_len - SizeOfHeapInsert,
+						change->newtuple);
+	}
+
+	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+}
+
+static void
+DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_update *xlrec;
+	xl_heap_header_len *xlhdr;
+	ReorderBufferChange *change;
+	char	   *data;
+
+	xlrec = (xl_heap_update *) buf->record_data;
+	xlhdr = (xl_heap_header_len *) (buf->record_data + SizeOfHeapUpdate);
+
+	/* XXX: nicer */
+	if (xlrec->target.node.dbNode != ctx->slot->database)
+		return;
+
+	change = ReorderBufferGetChange(ctx->reorder);
+	change->action = REORDER_BUFFER_CHANGE_UPDATE;
+	memcpy(&change->relnode, &xlrec->target.node, sizeof(RelFileNode));
+
+	data = (char *) &xlhdr->header;
+
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
+	{
+		Assert(r->xl_len > (SizeOfHeapUpdate + SizeOfHeapHeaderLen));
+
+		change->newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+		DecodeXLogTuple(data,
+						xlhdr->t_len + SizeOfHeapHeader,
+						change->newtuple);
+		/* skip over the rest of the tuple header */
+		data += SizeOfHeapHeader;
+		/* skip over the tuple data */
+		data += xlhdr->t_len;
+	}
+
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY)
+	{
+		xlhdr = (xl_heap_header_len *) data;
+		change->oldtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+		DecodeXLogTuple((char *) &xlhdr->header,
+						xlhdr->t_len + SizeOfHeapHeader,
+						change->oldtuple);
+		data = (char *) &xlhdr->header;
+		data += SizeOfHeapHeader;
+		data += xlhdr->t_len;
+	}
+
+	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+}
+
+static void
+DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_delete *xlrec;
+	ReorderBufferChange *change;
+
+	xlrec = (xl_heap_delete *) buf->record_data;
+
+	/* XXX: nicer */
+	if (xlrec->target.node.dbNode != ctx->slot->database)
+		return;
+
+	change = ReorderBufferGetChange(ctx->reorder);
+	change->action = REORDER_BUFFER_CHANGE_DELETE;
+
+	memcpy(&change->relnode, &xlrec->target.node, sizeof(RelFileNode));
+
+	/* old primary key stored */
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY)
+	{
+		Assert(r->xl_len > (SizeOfHeapDelete + SizeOfHeapHeader));
+
+		change->oldtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+		DecodeXLogTuple((char *) xlrec + SizeOfHeapDelete,
+						r->xl_len - SizeOfHeapDelete,
+						change->oldtuple);
+	}
+	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+}
+
+/*
+ * Decode xl_heap_multi_insert record into multiple changes.
+ */
+static void
+DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	XLogRecord *r = &buf->record;
+	xl_heap_multi_insert *xlrec;
+	int			i;
+	char	   *data;
+	bool		isinit = (r->xl_info & XLOG_HEAP_INIT_PAGE) != 0;
+
+	xlrec = (xl_heap_multi_insert *) buf->record_data;
+
+	/* XXX: nicer */
+	if (xlrec->node.dbNode != ctx->slot->database)
+		return;
+
+	data = buf->record_data + SizeOfHeapMultiInsert;
+
+	/*
+	 * OffsetNumbers (which are not of interest to us) are stored when
+	 * XLOG_HEAP_INIT_PAGE is not set -- skip over them.
+	 */
+	if (!isinit)
+		data += sizeof(OffsetNumber) * xlrec->ntuples;
+
+	for (i = 0; i < xlrec->ntuples; i++)
+	{
+		ReorderBufferChange *change;
+		xl_multi_insert_tuple *xlhdr;
+		int			datalen;
+		ReorderBufferTupleBuf *tuple;
+
+		change = ReorderBufferGetChange(ctx->reorder);
+		change->action = REORDER_BUFFER_CHANGE_INSERT;
+		memcpy(&change->relnode, &xlrec->node, sizeof(RelFileNode));
+
+		/*
+		 * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
+		 * isn't used for catalogs, but better be future proof.
+		 *
+		 * We decode the tuple in pretty much the same way as DecodeXLogTuple,
+		 * but since the layout is slightly different, we can't use it here.
+		 */
+		if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
+		{
+			change->newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
+
+			tuple = change->newtuple;
+
+			/* not a disk based tuple */
+			ItemPointerSetInvalid(&tuple->tuple.t_self);
+
+			xlhdr = (xl_multi_insert_tuple *) SHORTALIGN(data);
+			data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
+			datalen = xlhdr->datalen;
+
+			/* we can only figure this out after reassembling the transactions */
+			tuple->tuple.t_tableOid = InvalidOid;
+			tuple->tuple.t_data = &tuple->header;
+			tuple->tuple.t_len = datalen + offsetof(HeapTupleHeaderData, t_bits);
+
+			memset(&tuple->header, 0, sizeof(HeapTupleHeaderData));
+
+			memcpy((char *) &tuple->header + offsetof(HeapTupleHeaderData, t_bits),
+				   (char *) data,
+				   datalen);
+			data += datalen;
+
+			tuple->header.t_infomask = xlhdr->t_infomask;
+			tuple->header.t_infomask2 = xlhdr->t_infomask2;
+			tuple->header.t_hoff = xlhdr->t_hoff;
+		}
+
+		ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
+	}
+}
+
+/*
+ * Read a tuple of size 'len' from 'data' into 'tuple'.
+ */
+static void
+DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tuple)
+{
+	xl_heap_header xlhdr;
+	int			datalen = len - SizeOfHeapHeader;
+
+	Assert(datalen >= 0);
+	Assert(datalen <= MaxHeapTupleSize);
+
+	tuple->tuple.t_len = datalen + offsetof(HeapTupleHeaderData, t_bits);
+
+	/* not a disk based tuple */
+	ItemPointerSetInvalid(&tuple->tuple.t_self);
+
+	/* we can only figure this out after reassembling the transactions */
+	tuple->tuple.t_tableOid = InvalidOid;
+	tuple->tuple.t_data = &tuple->header;
+
+	/* data is not stored aligned, copy to aligned storage */
+	memcpy((char *) &xlhdr,
+		   data,
+		   SizeOfHeapHeader);
+
+	memset(&tuple->header, 0, sizeof(HeapTupleHeaderData));
+
+	memcpy((char *) &tuple->header + offsetof(HeapTupleHeaderData, t_bits),
+		   data + SizeOfHeapHeader,
+		   datalen);
+
+	tuple->header.t_infomask = xlhdr.t_infomask;
+	tuple->header.t_infomask2 = xlhdr.t_infomask2;
+	tuple->header.t_hoff = xlhdr.t_hoff;
+}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
new file mode 100644
index 0000000..656e995
--- /dev/null
+++ b/src/backend/replication/logical/logical.c
@@ -0,0 +1,1046 @@
+/*-------------------------------------------------------------------------
+ *
+ * logical.c
+ *
+ *	   Logical decoding shared memory management
+ *
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/logical.c
+ *
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "access/transam.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+
+#include "replication/logical.h"
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "storage/fd.h"
+
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+/*
+ * logical replication on-disk data structures.
+ */
+typedef struct LogicalDecodingSlotOnDisk
+{
+	uint32		magic;
+	LogicalDecodingSlot slot;
+} LogicalDecodingSlotOnDisk;
+
+#define LOGICAL_MAGIC	0x1051CA1		/* format identifier */
+
+/* Control array for logical decoding */
+LogicalDecodingCtlData *LogicalDecodingCtl = NULL;
+
+/* My slot for logical rep in the shared memory array */
+LogicalDecodingSlot *MyLogicalDecodingSlot = NULL;
+
+/* user settable parameters */
+int			max_logical_slots = 0;		/* the maximum number of logical slots */
+
+static void LogicalSlotKill(int code, Datum arg);
+
+/* persistency functions */
+static void RestoreLogicalSlot(const char *name);
+static void CreateLogicalSlot(LogicalDecodingSlot *slot);
+static void SaveLogicalSlot(LogicalDecodingSlot *slot);
+static void SaveLogicalSlotInternal(LogicalDecodingSlot *slot, const char *path);
+static void DeleteLogicalSlot(LogicalDecodingSlot *slot);
+
+
+/* Report shared-memory space needed by LogicalDecodingShmemInit */
+Size
+LogicalDecodingShmemSize(void)
+{
+	Size		size = 0;
+
+	if (max_logical_slots == 0)
+		return size;
+
+	size = offsetof(LogicalDecodingCtlData, logical_slots);
+	size = add_size(size,
+					mul_size(max_logical_slots, sizeof(LogicalDecodingSlot)));
+
+	return size;
+}
+
+/* Allocate and initialize walsender-related shared memory */
+void
+LogicalDecodingShmemInit(void)
+{
+	bool		found;
+
+	if (max_logical_slots == 0)
+		return;
+
+	LogicalDecodingCtl = (LogicalDecodingCtlData *)
+		ShmemInitStruct("Logical Decoding Ctl", LogicalDecodingShmemSize(),
+						&found);
+
+	if (!found)
+	{
+		int			i;
+
+		/* First time through, so initialize */
+		MemSet(LogicalDecodingCtl, 0, LogicalDecodingShmemSize());
+
+		LogicalDecodingCtl->xmin = InvalidTransactionId;
+
+		for (i = 0; i < max_logical_slots; i++)
+		{
+			LogicalDecodingSlot *slot =
+			&LogicalDecodingCtl->logical_slots[i];
+
+			slot->xmin = InvalidTransactionId;
+			slot->effective_xmin = InvalidTransactionId;
+			SpinLockInit(&slot->mutex);
+		}
+	}
+}
+
+static void
+LogicalSlotKill(int code, Datum arg)
+{
+	/* LOCK? */
+	if (MyLogicalDecodingSlot && MyLogicalDecodingSlot->active)
+	{
+		MyLogicalDecodingSlot->active = false;
+	}
+	MyLogicalDecodingSlot = NULL;
+}
+
+/*
+ * Set the xmin required for catalog timetravel for the specific decoding slot.
+ */
+void
+IncreaseLogicalXminForSlot(XLogRecPtr lsn, TransactionId xmin)
+{
+	Assert(MyLogicalDecodingSlot != NULL);
+
+	SpinLockAcquire(&MyLogicalDecodingSlot->mutex);
+
+	/*
+	 * Only increase if the previous values have been applied, otherwise we
+	 * might never end up updating if the receiver acks too slowly.
+	 */
+	if (MyLogicalDecodingSlot->candidate_lsn == InvalidXLogRecPtr ||
+		(lsn == MyLogicalDecodingSlot->candidate_lsn &&
+		 !TransactionIdIsValid(MyLogicalDecodingSlot->candidate_xmin)))
+	{
+		MyLogicalDecodingSlot->candidate_lsn = lsn;
+		MyLogicalDecodingSlot->candidate_xmin = xmin;
+		elog(DEBUG1, "got new xmin %u at %X/%X", xmin,
+			 (uint32) (lsn >> 32), (uint32) lsn);
+	}
+	SpinLockRelease(&MyLogicalDecodingSlot->mutex);
+}
+
+void
+IncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn)
+{
+	Assert(MyLogicalDecodingSlot != NULL);
+	Assert(restart_lsn != InvalidXLogRecPtr);
+	Assert(current_lsn != InvalidXLogRecPtr);
+
+	SpinLockAcquire(&MyLogicalDecodingSlot->mutex);
+
+	/*
+	 * Only increase if the previous values have been applied, otherwise we
+	 * might never end up updating if the receiver acks too slowly. A missed
+	 * value here will just cause some extra effort after reconnecting.
+	 */
+	if (MyLogicalDecodingSlot->candidate_lsn == InvalidXLogRecPtr ||
+		(current_lsn == MyLogicalDecodingSlot->candidate_lsn &&
+	 MyLogicalDecodingSlot->candidate_restart_decoding == InvalidXLogRecPtr))
+	{
+		MyLogicalDecodingSlot->candidate_lsn = current_lsn;
+		MyLogicalDecodingSlot->candidate_restart_decoding = restart_lsn;
+
+		elog(DEBUG1, "got new restart lsn %X/%X at %X/%X",
+			 (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
+			 (uint32) (current_lsn >> 32), (uint32) current_lsn);
+
+	}
+	SpinLockRelease(&MyLogicalDecodingSlot->mutex);
+}
+
+void
+LogicalConfirmReceivedLocation(XLogRecPtr lsn)
+{
+	Assert(lsn != InvalidXLogRecPtr);
+
+	/* Do an unlocked check for candidate_lsn first. */
+	if (MyLogicalDecodingSlot->candidate_lsn != InvalidXLogRecPtr)
+	{
+		bool		updated_xmin = false;
+		bool		updated_restart = false;
+
+		/* use volatile pointer to prevent code rearrangement */
+		volatile LogicalDecodingSlot *slot = MyLogicalDecodingSlot;
+
+		SpinLockAcquire(&slot->mutex);
+
+		slot->confirmed_flush = lsn;
+
+		/* if were past the location required for bumping xmin, do so */
+		if (slot->candidate_lsn != InvalidXLogRecPtr &&
+			slot->candidate_lsn < lsn)
+		{
+			/*
+			 * We have to write the changed xmin to disk *before* we change
+			 * the in-memory value, otherwise after a crash we wouldn't know
+			 * that some catalog tuples might have been removed already.
+			 *
+			 * Ensure that by first writing to ->xmin and only update
+			 * ->effective_xmin once the new state is fsynced to disk. After a
+			 * crash ->effective_xmin is set to ->xmin.
+			 */
+			if (TransactionIdIsValid(slot->candidate_xmin) &&
+				slot->xmin != slot->candidate_xmin)
+			{
+				slot->xmin = slot->candidate_xmin;
+				updated_xmin = true;
+			}
+
+			if (slot->candidate_restart_decoding != InvalidXLogRecPtr &&
+				slot->restart_decoding != slot->candidate_restart_decoding)
+			{
+				slot->restart_decoding = slot->candidate_restart_decoding;
+				updated_restart = true;
+			}
+
+			slot->candidate_lsn = InvalidXLogRecPtr;
+			slot->candidate_xmin = InvalidTransactionId;
+			slot->candidate_restart_decoding = InvalidXLogRecPtr;
+		}
+
+		SpinLockRelease(&slot->mutex);
+
+		/* first write new xmin to disk, so we know whats up after a crash */
+		if (updated_xmin || updated_restart)
+			/* cast away volatile, thats ok. */
+			SaveLogicalSlot((LogicalDecodingSlot *) slot);
+
+		/*
+		 * now the new xmin is safely on disk, we can let the global value
+		 * advance
+		 */
+		if (updated_xmin)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->effective_xmin = slot->xmin;
+			SpinLockRelease(&slot->mutex);
+
+			ComputeLogicalXmin();
+		}
+	}
+	else
+	{
+		volatile LogicalDecodingSlot *slot = MyLogicalDecodingSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->confirmed_flush = lsn;
+		SpinLockRelease(&slot->mutex);
+	}
+}
+
+/*
+ * Compute the xmin between all of the decoding slots and store it in
+ * WalSndCtlData.
+ */
+void
+ComputeLogicalXmin(void)
+{
+	int			i;
+	TransactionId xmin = InvalidTransactionId;
+	LogicalDecodingSlot *slot;
+
+	Assert(LogicalDecodingCtl);
+
+	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&slot->mutex);
+		if (slot->in_use &&
+			TransactionIdIsValid(slot->effective_xmin) && (
+											   !TransactionIdIsValid(xmin) ||
+						   TransactionIdPrecedes(slot->effective_xmin, xmin))
+			)
+		{
+			xmin = slot->effective_xmin;
+		}
+		SpinLockRelease(&slot->mutex);
+	}
+	LogicalDecodingCtl->xmin = xmin;
+	LWLockRelease(ProcArrayLock);
+
+	elog(DEBUG1, "computed new global xmin for decoding: %u", xmin);
+}
+
+/*
+ * Make sure the current settings & environment are capable of doing logical
+ * replication.
+ */
+void
+CheckLogicalReplicationRequirements(void)
+{
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* XXX invent class 51 for code 51028? */
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("logical replication requires wal_level=logical")));
+
+	if (MyDatabaseId == InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("logical replication requires to be connected to a database")));
+
+	if (max_logical_slots == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 (errmsg("logical replication requires needs max_logical_slots > 0"))));
+}
+
+/*
+ * Search for a free slot, mark it as used and acquire a valid xmin horizon
+ * value.
+ */
+void
+LogicalDecodingAcquireFreeSlot(const char *name, const char *plugin)
+{
+	LogicalDecodingSlot *slot;
+	bool		name_in_use;
+	int			i;
+
+	Assert(!MyLogicalDecodingSlot);
+
+	CheckLogicalReplicationRequirements();
+
+	LWLockAcquire(LogicalReplicationCtlLock, LW_EXCLUSIVE);
+
+	/* First, make sure the requested name is not in use. */
+
+	name_in_use = false;
+	for (i = 0; i < max_logical_slots && !name_in_use; i++)
+	{
+		LogicalDecodingSlot *s = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&s->mutex);
+		if (s->in_use && strcmp(name, NameStr(s->name)) == 0)
+			name_in_use = true;
+		SpinLockRelease(&s->mutex);
+	}
+
+	if (name_in_use)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+			  errmsg("There already is a logical slot named \"%s\"", name)));
+
+	/* Find the first available (not in_use (=> not active)) slot. */
+
+	slot = NULL;
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		LogicalDecodingSlot *s = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&s->mutex);
+		if (!s->in_use)
+		{
+			Assert(!s->active);
+			/* NOT releasing the lock yet */
+			slot = s;
+			break;
+		}
+		SpinLockRelease(&s->mutex);
+	}
+
+	LWLockRelease(LogicalReplicationCtlLock);
+
+	if (!slot)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("couldn't find free logical slot. free one or increase max_logical_slots")));
+
+	MyLogicalDecodingSlot = slot;
+
+	/* Lets start with enough information if we can */
+	if (!RecoveryInProgress())
+		slot->restart_decoding = LogStandbySnapshot();
+	else
+		slot->restart_decoding = GetRedoRecPtr();
+
+	slot->in_use = true;
+	slot->active = true;
+	slot->database = MyDatabaseId;
+	/* XXX: do we want to use truncate identifier instead? */
+	strncpy(NameStr(slot->plugin), plugin, NAMEDATALEN);
+	NameStr(slot->plugin)[NAMEDATALEN - 1] = '\0';
+	strncpy(NameStr(slot->name), name, NAMEDATALEN);
+	NameStr(slot->name)[NAMEDATALEN - 1] = '\0';
+
+	/* Arrange to clean up at exit/error */
+	on_shmem_exit(LogicalSlotKill, 0);
+
+	/* release slot so it can be examined by others */
+	SpinLockRelease(&slot->mutex);
+
+	/* XXX: verify that the specified plugin is valid */
+
+	/*
+	 * Acquire the current global xmin value and directly set the logical xmin
+	 * before releasing the lock if necessary. We do this so wal decoding is
+	 * guaranteed to have all catalog rows produced by xacts with an xid >
+	 * walsnd->xmin available.
+	 *
+	 * We can't use ComputeLogicalXmin here as that acquires ProcArrayLock
+	 * separately which would open a short window for the global xmin to
+	 * advance above walsnd->xmin.
+	 */
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	slot->effective_xmin = GetOldestXmin(true, true, true, true);
+	slot->xmin = slot->effective_xmin;
+
+	if (!TransactionIdIsValid(LogicalDecodingCtl->xmin) ||
+		NormalTransactionIdPrecedes(slot->effective_xmin, LogicalDecodingCtl->xmin))
+		LogicalDecodingCtl->xmin = slot->effective_xmin;
+	LWLockRelease(ProcArrayLock);
+
+	Assert(slot->effective_xmin <= GetOldestXmin(true, true, true, false));
+
+	LWLockAcquire(LogicalReplicationCtlLock, LW_EXCLUSIVE);
+	CreateLogicalSlot(slot);
+	LWLockRelease(LogicalReplicationCtlLock);
+}
+
+/*
+ * Find an previously initiated slot and mark it as used again.
+ */
+void
+LogicalDecodingReAcquireSlot(const char *name)
+{
+	LogicalDecodingSlot *slot;
+	int			i;
+
+	CheckLogicalReplicationRequirements();
+
+	Assert(!MyLogicalDecodingSlot);
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&slot->mutex);
+		if (slot->in_use && strcmp(name, NameStr(slot->name)) == 0)
+		{
+			MyLogicalDecodingSlot = slot;
+			/* NOT releasing the lock yet */
+			break;
+		}
+		SpinLockRelease(&slot->mutex);
+	}
+
+	if (!MyLogicalDecodingSlot)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("couldn't find logical slot \"%s\"", name)));
+
+	slot = MyLogicalDecodingSlot;
+
+	if (slot->active)
+	{
+		SpinLockRelease(&slot->mutex);
+		MyLogicalDecodingSlot = NULL;
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("slot already active")));
+	}
+
+	slot->active = true;
+	/* now that we've marked it as active, we release our lock */
+	SpinLockRelease(&slot->mutex);
+
+	/* Don't let the user switch the database... */
+	if (slot->database != MyDatabaseId)
+	{
+		SpinLockAcquire(&slot->mutex);
+		slot->active = false;
+		SpinLockRelease(&slot->mutex);
+
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 (errmsg("START_LOGICAL_REPLICATION needs to be run in the same database as INIT_LOGICAL_REPLICATION"))));
+	}
+
+	/* Arrange to clean up at exit */
+	on_shmem_exit(LogicalSlotKill, 0);
+
+	SaveLogicalSlot(slot);
+}
+
+/*
+  * Temporarily remove a logical decoding slot, this or another backend can
+  * reacquire it later.
+ */
+void
+LogicalDecodingReleaseSlot(void)
+{
+	LogicalDecodingSlot *slot;
+
+	CheckLogicalReplicationRequirements();
+
+	slot = MyLogicalDecodingSlot;
+
+	Assert(slot != NULL && slot->active);
+
+	SpinLockAcquire(&slot->mutex);
+	slot->active = false;
+	SpinLockRelease(&slot->mutex);
+
+	MyLogicalDecodingSlot = NULL;
+
+	SaveLogicalSlot(slot);
+
+	cancel_shmem_exit(LogicalSlotKill, 0);
+}
+
+/*
+ * Permanently remove a logical decoding slot.
+ */
+void
+LogicalDecodingFreeSlot(const char *name)
+{
+	LogicalDecodingSlot *slot = NULL;
+	int			i;
+
+	CheckLogicalReplicationRequirements();
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		SpinLockAcquire(&slot->mutex);
+		if (slot->in_use && strcmp(name, NameStr(slot->name)) == 0)
+		{
+			/* NOT releasing the lock yet */
+			break;
+		}
+		SpinLockRelease(&slot->mutex);
+		slot = NULL;
+	}
+
+	if (!slot)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("couldn't find logical slot \"%s\"", name)));
+
+	if (slot->active)
+	{
+		SpinLockRelease(&slot->mutex);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("cannot free active logical slot \"%s\"", name)));
+	}
+
+	/*
+	 * Mark it as as active, so nobody can claim this slot while we are
+	 * working on it. We don't want to hold the spinlock while doing stuff
+	 * like fsyncing the state file to disk.
+	 */
+	slot->active = true;
+
+	SpinLockRelease(&slot->mutex);
+
+	/*
+	 * Start critical section, we can't to be interrupted while on-disk/memory
+	 * state aren't coherent.
+	 */
+	START_CRIT_SECTION();
+
+	DeleteLogicalSlot(slot);
+
+	/* ok, everything gone, after a crash we now wouldn't restore this slot */
+	SpinLockAcquire(&slot->mutex);
+	slot->active = false;
+	slot->in_use = false;
+	SpinLockRelease(&slot->mutex);
+
+	END_CRIT_SECTION();
+
+	/* slot is dead and doesn't nail the xmin anymore */
+	ComputeLogicalXmin();
+}
+
+/*
+ * Load replication state from disk into memory at server startup.
+ */
+void
+StartupLogicalReplication(XLogRecPtr checkPointRedo)
+{
+	DIR		   *logical_dir;
+	struct dirent *logical_de;
+
+	ereport(DEBUG1,
+			(errmsg("starting up logical decoding from %X/%X",
+					(uint32) (checkPointRedo >> 32), (uint32) checkPointRedo)));
+
+	/* restore all slots */
+	logical_dir = AllocateDir("pg_llog");
+	while ((logical_de = ReadDir(logical_dir, "pg_llog")) != NULL)
+	{
+		if (strcmp(logical_de->d_name, ".") == 0 ||
+			strcmp(logical_de->d_name, "..") == 0)
+			continue;
+
+		/* one of our own directories */
+		if (strcmp(logical_de->d_name, "snapshots") == 0)
+			continue;
+
+		/* we crashed while a slot was being setup or deleted, clean up */
+		if (strcmp(logical_de->d_name, "new") == 0 ||
+			strcmp(logical_de->d_name, "old") == 0)
+		{
+			char		path[MAXPGPATH];
+
+			sprintf(path, "pg_llog/%s", logical_de->d_name);
+
+			if (!rmtree(path, true))
+			{
+				FreeDir(logical_dir);
+				ereport(PANIC,
+						(errcode_for_file_access(),
+						 errmsg("could not remove directory \"%s\": %m",
+								path)));
+			}
+			continue;
+		}
+
+		RestoreLogicalSlot(logical_de->d_name);
+	}
+	FreeDir(logical_dir);
+
+	if (max_logical_slots <= 0)
+		return;
+
+	/* Now that we have recovered all the data, compute logical xmin */
+	ComputeLogicalXmin();
+
+	ReorderBufferStartup();
+}
+
+/* ----
+ * Manipulation of ondisk state of logical slots
+ * ----
+ */
+static void
+CreateLogicalSlot(LogicalDecodingSlot *slot)
+{
+	char		tmppath[MAXPGPATH];
+	char		path[MAXPGPATH];
+
+	START_CRIT_SECTION();
+
+	sprintf(tmppath, "pg_llog/new");
+	sprintf(path, "pg_llog/%s", NameStr(slot->name));
+
+	if (mkdir(tmppath, S_IRWXU) < 0)
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not create directory \"%s\": %m",
+						tmppath)));
+
+	fsync_fname(tmppath, true);
+
+	SaveLogicalSlotInternal(slot, tmppath);
+
+	if (rename(tmppath, path) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename logical checkpoint from \"%s\" to \"%s\": %m",
+						tmppath, path)));
+	}
+
+	fsync_fname(path, true);
+
+	END_CRIT_SECTION();
+}
+
+static void
+SaveLogicalSlot(LogicalDecodingSlot *slot)
+{
+	char		path[MAXPGPATH];
+
+	sprintf(path, "pg_llog/%s", NameStr(slot->name));
+	SaveLogicalSlotInternal(slot, path);
+}
+
+/*
+ * Shared functionality between saving and creating a logical slot.
+ */
+static void
+SaveLogicalSlotInternal(LogicalDecodingSlot *slot, const char *dir)
+{
+	char		tmppath[MAXPGPATH];
+	char		path[MAXPGPATH];
+	int			fd;
+	LogicalDecodingSlotOnDisk cp;
+
+	/* silence valgrind :( */
+	memset(&cp, 0, sizeof(LogicalDecodingSlotOnDisk));
+
+	sprintf(tmppath, "%s/state.tmp", dir);
+	sprintf(path, "%s/state", dir);
+
+	START_CRIT_SECTION();
+
+	fd = OpenTransientFile(tmppath,
+						   O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
+						   S_IRUSR | S_IWUSR);
+	if (fd < 0)
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not create logical checkpoint file \"%s\": %m",
+						tmppath)));
+
+	cp.magic = LOGICAL_MAGIC;
+
+	SpinLockAcquire(&slot->mutex);
+
+	cp.slot.xmin = slot->xmin;
+	cp.slot.effective_xmin = slot->effective_xmin;
+
+	strcpy(NameStr(cp.slot.name), NameStr(slot->name));
+	strcpy(NameStr(cp.slot.plugin), NameStr(slot->plugin));
+
+	cp.slot.database = slot->database;
+	cp.slot.confirmed_flush = slot->confirmed_flush;
+	cp.slot.restart_decoding = slot->restart_decoding;
+	cp.slot.candidate_lsn = InvalidXLogRecPtr;
+	cp.slot.candidate_xmin = InvalidTransactionId;
+	cp.slot.candidate_restart_decoding = InvalidXLogRecPtr;
+	cp.slot.in_use = slot->in_use;
+	cp.slot.active = false;
+
+	SpinLockRelease(&slot->mutex);
+
+	if ((write(fd, &cp, sizeof(cp))) != sizeof(cp))
+	{
+		CloseTransientFile(fd);
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not write logical checkpoint file \"%s\": %m",
+						tmppath)));
+	}
+
+	/* fsync the file */
+	if (pg_fsync(fd) != 0)
+	{
+		CloseTransientFile(fd);
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not fsync logical checkpoint \"%s\": %m",
+						tmppath)));
+	}
+
+	CloseTransientFile(fd);
+
+	/* rename to permanent file, fsync file and directory */
+	if (rename(tmppath, path) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename logical checkpoint from \"%s\" to \"%s\": %m",
+						tmppath, path)));
+	}
+
+	fsync_fname((char *) dir, true);
+	fsync_fname(path, false);
+
+	END_CRIT_SECTION();
+}
+
+
+static void
+DeleteLogicalSlot(LogicalDecodingSlot *slot)
+{
+	char		path[MAXPGPATH];
+	char		tmppath[] = "pg_llog/old";
+
+	START_CRIT_SECTION();
+
+	sprintf(path, "pg_llog/%s", NameStr(slot->name));
+
+	if (rename(path, tmppath) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename logical checkpoint from \"%s\" to \"%s\": %m",
+						path, tmppath)));
+	}
+
+	/* make sure no partial state is visible after a crash */
+	fsync_fname(tmppath, true);
+	fsync_fname("pg_llog", true);
+
+	if (!rmtree(tmppath, true))
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not remove directory \"%s\": %m",
+						tmppath)));
+	}
+
+	END_CRIT_SECTION();
+}
+
+/*
+ * Load a single ondisk slot into memory.
+ */
+static void
+RestoreLogicalSlot(const char *name)
+{
+	LogicalDecodingSlotOnDisk cp;
+	int			i;
+	char		path[MAXPGPATH];
+	int			fd;
+	bool		restored = false;
+	int			readBytes;
+
+	START_CRIT_SECTION();
+
+	/* delete temp file if it exists */
+	sprintf(path, "pg_llog/%s/state.tmp", name);
+	if (unlink(path) < 0 && errno != ENOENT)
+		ereport(PANIC, (errmsg("failed while unlinking %s", path)));
+
+	sprintf(path, "pg_llog/%s/state", name);
+
+	elog(DEBUG1, "restoring logical slot from %s", path);
+
+	fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+
+	/*
+	 * We do not need to handle this as we are rename()ing the directory into
+	 * place only after we fsync()ed the state file.
+	 */
+	if (fd < 0)
+		ereport(PANIC, (errmsg("could not open state file %s", path)));
+
+	readBytes = read(fd, &cp, sizeof(cp));
+	if (readBytes != sizeof(cp))
+	{
+		int			saved_errno = errno;
+
+		CloseTransientFile(fd);
+		errno = saved_errno;
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not read logical checkpoint file \"%s\": %m, read %d of %zu",
+						path, readBytes, sizeof(cp))));
+	}
+
+	CloseTransientFile(fd);
+
+	if (cp.magic != LOGICAL_MAGIC)
+		ereport(PANIC, (errmsg("Logical checkpoint has wrong magic %u instead of %u",
+							   cp.magic, LOGICAL_MAGIC)));
+
+	/* nothing can be active yet, don't lock anything */
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		LogicalDecodingSlot *slot;
+
+		slot = &LogicalDecodingCtl->logical_slots[i];
+
+		if (slot->in_use)
+			continue;
+
+		slot->xmin = cp.slot.xmin;
+		/* XXX: after a crash, always use xmin, not effective_xmin */
+		slot->effective_xmin = cp.slot.xmin;
+		strcpy(NameStr(slot->name), NameStr(cp.slot.name));
+		strcpy(NameStr(slot->plugin), NameStr(cp.slot.plugin));
+		slot->database = cp.slot.database;
+		slot->restart_decoding = cp.slot.restart_decoding;
+		slot->confirmed_flush = cp.slot.confirmed_flush;
+		slot->candidate_lsn = InvalidXLogRecPtr;
+		slot->candidate_xmin = InvalidTransactionId;
+		slot->candidate_restart_decoding = InvalidXLogRecPtr;
+		slot->in_use = true;
+		slot->active = false;
+		restored = true;
+
+		/*
+		 * FIXME: Do some validation here.
+		 */
+		break;
+	}
+
+	if (!restored)
+		ereport(PANIC,
+				(errmsg("too many logical slots active before shutdown, increase max_logical_slots and try again")));
+
+	END_CRIT_SECTION();
+}
+
+
+static void
+LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin)
+{
+	/* lookup symbols in the shared libarary */
+
+	/* optional */
+	callbacks->init_cb = (LogicalDecodeInitCB)
+		load_external_function(plugin, "pg_decode_init", false, NULL);
+
+	/* required */
+	callbacks->begin_cb = (LogicalDecodeBeginCB)
+		load_external_function(plugin, "pg_decode_begin_txn", true, NULL);
+
+	/* required */
+	callbacks->change_cb = (LogicalDecodeChangeCB)
+		load_external_function(plugin, "pg_decode_change", true, NULL);
+
+	/* required */
+	callbacks->commit_cb = (LogicalDecodeCommitCB)
+		load_external_function(plugin, "pg_decode_commit_txn", true, NULL);
+
+	/* optional */
+	callbacks->cleanup_cb = (LogicalDecodeCleanupCB)
+		load_external_function(plugin, "pg_decode_clean", false, NULL);
+}
+
+/*
+ * Context management functions to make coordination between the different
+ * logical decoding pieces.
+ */
+
+/*
+ * Callbacks for ReorderBuffer which add in some more information and then call
+ * output_plugin.h plugins.
+ */
+static void
+begin_txn_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+
+	ctx->callbacks.begin_cb(ctx, txn);
+}
+
+static void
+commit_txn_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn, XLogRecPtr commit_lsn)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+
+	ctx->callbacks.commit_cb(ctx, txn, commit_lsn);
+}
+
+static void
+change_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+			   Relation relation, ReorderBufferChange *change)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+
+	ctx->callbacks.change_cb(ctx, txn, relation, change);
+}
+
+LogicalDecodingContext *
+CreateLogicalDecodingContext(LogicalDecodingSlot *slot,
+							 bool is_init,
+							 XLogRecPtr	start_lsn,
+							 List *output_plugin_options,
+							 XLogPageReadCB read_page,
+						 LogicalOutputPluginWriterPrepareWrite prepare_write,
+							 LogicalOutputPluginWriterWrite do_write)
+{
+	MemoryContext context;
+	MemoryContext old_context;
+	TransactionId xmin_horizon;
+	LogicalDecodingContext *ctx;
+
+	context = AllocSetContextCreate(TopMemoryContext,
+									"ReorderBuffer",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	old_context = MemoryContextSwitchTo(context);
+	ctx = palloc0(sizeof(LogicalDecodingContext));
+
+
+	/* load output plugins first, so we detect a wrong output plugin early */
+	LoadOutputPlugin(&ctx->callbacks, NameStr(slot->plugin));
+
+	if (is_init && start_lsn != InvalidXLogRecPtr)
+		elog(ERROR, "cannot initially start at a specified lsn");
+
+	if (is_init)
+		xmin_horizon = slot->xmin;
+	else
+		xmin_horizon = InvalidTransactionId;
+
+	ctx->slot = slot;
+
+	ctx->reader = XLogReaderAllocate(read_page, ctx);
+	ctx->reader->private_data = ctx;
+
+	ctx->reorder = ReorderBufferAllocate();
+	ctx->snapshot_builder =
+		AllocateSnapshotBuilder(ctx->reorder, xmin_horizon, start_lsn);
+
+	ctx->reorder->private_data = ctx;
+
+	ctx->reorder->begin = begin_txn_wrapper;
+	ctx->reorder->apply_change = change_wrapper;
+	ctx->reorder->commit = commit_txn_wrapper;
+
+	ctx->out = makeStringInfo();
+	ctx->prepare_write = prepare_write;
+	ctx->write = do_write;
+
+	ctx->output_plugin_options = output_plugin_options;
+
+	if (is_init)
+		ctx->stop_after_consistent = true;
+	else
+		ctx->stop_after_consistent = false;
+
+	/* call output plugin initialization callback */
+	if (ctx->callbacks.init_cb != NULL)
+		ctx->callbacks.init_cb(ctx, is_init);
+
+	MemoryContextSwitchTo(old_context);
+
+	return ctx;
+}
+
+void
+FreeLogicalDecodingContext(LogicalDecodingContext *ctx)
+{
+	if (ctx->callbacks.cleanup_cb != NULL)
+		ctx->callbacks.cleanup_cb(ctx);
+}
+
+
+/* has the initial snapshot found a consistent state? */
+bool
+LogicalDecodingContextReady(LogicalDecodingContext *ctx)
+{
+	return SnapBuildCurrentState(ctx->snapshot_builder) == SNAPBUILD_CONSISTENT;
+}
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
new file mode 100644
index 0000000..9837a95
--- /dev/null
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -0,0 +1,361 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalfuncs.c
+ *
+ *	   Support functions for using xlog decoding
+ *
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logicalfuncs.c
+ *
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "fmgr.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "storage/fd.h"
+
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalfuncs.h"
+
+Datum		init_logical_replication(PG_FUNCTION_ARGS);
+Datum		stop_logical_replication(PG_FUNCTION_ARGS);
+Datum		pg_stat_get_logical_decoding_slots(PG_FUNCTION_ARGS);
+
+/* FIXME: duplicate code with pg_xlogdump, similar to walsender.c */
+static void
+XLogRead(char *buf, XLogRecPtr startptr, Size count)
+{
+	char	   *p;
+	XLogRecPtr	recptr;
+	Size		nbytes;
+
+	static int	sendFile = -1;
+	static XLogSegNo sendSegNo = 0;
+	static uint32 sendOff = 0;
+
+	p = buf;
+	recptr = startptr;
+	nbytes = count;
+
+	while (nbytes > 0)
+	{
+		uint32		startoff;
+		int			segbytes;
+		int			readbytes;
+
+		startoff = recptr % XLogSegSize;
+
+		if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo))
+		{
+			char		path[MAXPGPATH];
+
+			/* Switch to another logfile segment */
+			if (sendFile >= 0)
+				close(sendFile);
+
+			XLByteToSeg(recptr, sendSegNo);
+
+			XLogFilePath(path, ThisTimeLineID, sendSegNo);
+
+			sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0);
+
+			if (sendFile < 0)
+			{
+				if (errno == ENOENT)
+					ereport(ERROR,
+							(errcode_for_file_access(),
+							 errmsg("requested WAL segment %s has already been removed",
+									path)));
+				else
+					ereport(ERROR,
+							(errcode_for_file_access(),
+							 errmsg("could not open file \"%s\": %m",
+									path)));
+			}
+			sendOff = 0;
+		}
+
+		/* Need to seek in the file? */
+		if (sendOff != startoff)
+		{
+			if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
+			{
+				char		path[MAXPGPATH];
+
+				XLogFilePath(path, ThisTimeLineID, sendSegNo);
+
+				ereport(ERROR,
+						(errcode_for_file_access(),
+				  errmsg("could not seek in log segment %s to offset %u: %m",
+						 path, startoff)));
+			}
+			sendOff = startoff;
+		}
+
+		/* How many bytes are within this segment? */
+		if (nbytes > (XLogSegSize - startoff))
+			segbytes = XLogSegSize - startoff;
+		else
+			segbytes = nbytes;
+
+		readbytes = read(sendFile, p, segbytes);
+		if (readbytes <= 0)
+		{
+			char		path[MAXPGPATH];
+
+			XLogFilePath(path, ThisTimeLineID, sendSegNo);
+
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not read from log segment %s, offset %u, length %lu: %m",
+							path, sendOff, (unsigned long) segbytes)));
+		}
+
+		/* Update state for read */
+		recptr += readbytes;
+
+		sendOff += readbytes;
+		nbytes -= readbytes;
+		p += readbytes;
+	}
+}
+
+int
+logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
+	int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI)
+{
+	XLogRecPtr	flushptr,
+				loc;
+	int			count;
+
+	loc = targetPagePtr + reqLen;
+	while (1)
+	{
+		flushptr = GetFlushRecPtr();
+		if (loc <= flushptr)
+			break;
+		pg_usleep(1000L);
+	}
+
+	/* more than one block available */
+	if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
+		count = XLOG_BLCKSZ;
+	/* not enough data there */
+	else if (targetPagePtr + reqLen > flushptr)
+		return -1;
+	/* part of the page available */
+	else
+		count = flushptr - targetPagePtr;
+
+	/* FIXME: more sensible/efficient implementation */
+	XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ);
+
+	return count;
+}
+
+static void
+DummyWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	elog(ERROR, "init_logical_replication shouldn't be writing anything");
+}
+
+Datum
+init_logical_replication(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	Name		plugin = PG_GETARG_NAME(1);
+
+	char		xpos[MAXFNAMELEN];
+
+	TupleDesc	tupdesc;
+	HeapTuple	tuple;
+	Datum		result;
+	Datum		values[2];
+	bool		nulls[2];
+	LogicalDecodingContext *ctx = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Acquire a logical replication slot */
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingAcquireFreeSlot(NameStr(*name), NameStr(*plugin));
+
+	/* make sure we don't end up with an unreleased slot */
+	PG_TRY();
+	{
+		XLogRecPtr	startptr;
+
+		/*
+		 * Use the same initial_snapshot_reader, but with our own read_page
+		 * callback that does not depend on walsender.
+		 */
+		ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, true,
+										   InvalidXLogRecPtr, NIL,
+										   logical_read_local_xlog_page,
+										   DummyWrite, DummyWrite);
+
+		/* setup from where to read xlog */
+		startptr = ctx->slot->restart_decoding;
+
+		/* Wait for a consistent starting point */
+		for (;;)
+		{
+			XLogRecord *record;
+			XLogRecordBuffer buf;
+			char	   *err = NULL;
+
+			/* the read_page callback waits for new WAL */
+			record = XLogReadRecord(ctx->reader, startptr, &err);
+			if (err)
+				elog(ERROR, "%s", err);
+
+			Assert(record);
+
+			startptr = InvalidXLogRecPtr;
+
+			buf.origptr = ctx->reader->ReadRecPtr;
+			buf.record = *record;
+			buf.record_data = XLogRecGetData(record);
+			DecodeRecordIntoReorderBuffer(ctx, &buf);
+
+			/* only continue till we found a consistent spot */
+			if (LogicalDecodingContextReady(ctx))
+				break;
+		}
+
+		/* Extract the values we want */
+		MyLogicalDecodingSlot->confirmed_flush = ctx->reader->EndRecPtr;
+		snprintf(xpos, sizeof(xpos), "%X/%X",
+				 (uint32) (MyLogicalDecodingSlot->confirmed_flush >> 32),
+				 (uint32) MyLogicalDecodingSlot->confirmed_flush);
+	}
+	PG_CATCH();
+	{
+		LogicalDecodingReleaseSlot();
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	values[0] = CStringGetTextDatum(NameStr(MyLogicalDecodingSlot->name));
+	values[1] = CStringGetTextDatum(xpos);
+
+	memset(nulls, 0, sizeof(nulls));
+
+	tuple = heap_form_tuple(tupdesc, values, nulls);
+	result = HeapTupleGetDatum(tuple);
+
+	LogicalDecodingReleaseSlot();
+
+	PG_RETURN_DATUM(result);
+}
+
+Datum
+stop_logical_replication(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingFreeSlot(NameStr(*name));
+
+	PG_RETURN_INT32(0);
+}
+
+/*
+ * Return one row for each logical replication slot currently in use.
+ */
+
+Datum
+pg_stat_get_logical_decoding_slots(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_LOGICAL_DECODING_SLOTS_COLS 6
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	int			i;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not " \
+						"allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	for (i = 0; i < max_logical_slots; i++)
+	{
+		LogicalDecodingSlot *slot = &LogicalDecodingCtl->logical_slots[i];
+		Datum		values[PG_STAT_GET_LOGICAL_DECODING_SLOTS_COLS];
+		bool		nulls[PG_STAT_GET_LOGICAL_DECODING_SLOTS_COLS];
+		char		location[MAXFNAMELEN];
+		const char *slot_name;
+		const char *plugin;
+		TransactionId xmin;
+		XLogRecPtr	last_req;
+		bool		active;
+		Oid			database;
+
+		SpinLockAcquire(&slot->mutex);
+		if (!slot->in_use)
+		{
+			SpinLockRelease(&slot->mutex);
+			continue;
+		}
+		else
+		{
+			xmin = slot->xmin;
+			active = slot->active;
+			database = slot->database;
+			last_req = slot->restart_decoding;
+			slot_name = pstrdup(NameStr(slot->name));
+			plugin = pstrdup(NameStr(slot->plugin));
+		}
+		SpinLockRelease(&slot->mutex);
+
+		memset(nulls, 0, sizeof(nulls));
+
+		snprintf(location, sizeof(location), "%X/%X",
+				 (uint32) (last_req >> 32), (uint32) last_req);
+
+		values[0] = CStringGetTextDatum(slot_name);
+		values[1] = CStringGetTextDatum(plugin);
+		values[2] = database;
+		values[3] = BoolGetDatum(active);
+		values[4] = TransactionIdGetDatum(xmin);
+		values[5] = CStringGetTextDatum(location);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
new file mode 100644
index 0000000..b6df411
--- /dev/null
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -0,0 +1,2548 @@
+/*-------------------------------------------------------------------------
+ *
+ * reorderbuffer.c
+ *
+ * PostgreSQL logical replay buffer management
+ *
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/reorderbuffer.c
+ *
+ * NOTES
+ *	  This module gets handed individual pieces of transactions in the order
+ *	  they are written to the WAL and is responsible to reassemble them into
+ *	  toplevel transaction sized pieces. When a transaction is completely
+ *	  reassembled - signalled by reading the transaction commit record - it
+ *	  will then call the output plugin (c.f. ReorderBufferCommit()) with the
+ *	  individual changes. The output plugins rely on snapshots built by
+ *	  snapbuild.c which hands them to us.
+ *
+ *	  Transactions and subtransactions/savepoints in postgres are not
+ *	  immediately linked to each other from outside the performing
+ *	  backend. Only at commit/abort (or special xact_assignment records) they
+ *	  are linked together. Which means that we will have to splice together a
+ *	  toplevel transaction from its subtransactions. To do that efficiently we
+ *	  build a binary heap indexed by the smallest current lsn of the individual
+ *	  subtransactions' changestreams. As the individual streams are inherently
+ *	  ordered by LSN - since that is where we build them from - the transaction
+ *	  can easily be reassembled by always using the subtransaction with the
+ *	  smallest current LSN from the heap.
+ *
+ *	  In order to cope with large transactions - which can be several times as
+ *	  big as the available memory - this module supports spooling the contents
+ *	  of a large transactions to disk. When the transaction is replayed the
+ *	  contents of individual (sub-)transactions will be read from disk in
+ *	  chunks.
+ *
+ *	  This module also has to deal with reassembling toast records from the
+ *	  individual chunks stored in WAL. When a new (or initial) version of a
+ *	  tuple is stored in WAL it will always be preceded by the toast chunks
+ *	  emitted for the columns stored out of line. Within a single toplevel
+ *	  transaction there will be no other data carrying records between a row's
+ *	  toast chunks and the row data itself. See ReorderBufferToast* for
+ *	  details.
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "access/transam.h"
+#include "access/xact.h"
+
+#include "catalog/catalog.h"
+
+#include "common/relpath.h"
+
+#include "lib/binaryheap.h"
+
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h" /* just for SnapBuildSnapDecRefcount */
+#include "replication/logical.h"
+
+#include "storage/bufmgr.h"
+#include "storage/fd.h"
+#include "storage/sinval.h"
+
+#include "utils/builtins.h"
+#include "utils/combocid.h"
+#include "utils/memdebug.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+#include "utils/relfilenodemap.h"
+#include "utils/resowner.h"
+#include "utils/tqual.h"
+
+/*
+ * For efficiency and simplicity reasons we want to keep Snapshots, CommandIds
+ * and ComboCids in the same list with the user visible INSERT/UPDATE/DELETE
+ * changes. We don't want to leak those internal values to external users
+ * though (they would just use switch()...default:) because that would make it
+ * harder to add to new user visible values.
+ *
+ * This needs to be synchronized with ReorderBufferChangeType! Adjust the
+ * StaticAssertExpr's in ReorderBufferAllocate if you add anything!
+ */
+typedef enum
+{
+	REORDER_BUFFER_CHANGE_INTERNAL_INSERT,
+	REORDER_BUFFER_CHANGE_INTERNAL_UPDATE,
+	REORDER_BUFFER_CHANGE_INTERNAL_DELETE,
+	REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
+	REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
+	REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID
+} ReorderBufferChangeTypeInternal;
+
+/* entry for a hash table we use to map from xid to our transaction state */
+typedef struct ReorderBufferTXNByIdEnt
+{
+	TransactionId xid;
+	ReorderBufferTXN *txn;
+} ReorderBufferTXNByIdEnt;
+
+/* data structures for (relfilenode, ctid) => (cmin, cmax) mapping */
+typedef struct ReorderBufferTupleCidKey
+{
+	RelFileNode relnode;
+	ItemPointerData tid;
+} ReorderBufferTupleCidKey;
+
+typedef struct ReorderBufferTupleCidEnt
+{
+	ReorderBufferTupleCidKey key;
+	CommandId	cmin;
+	CommandId	cmax;
+	CommandId	combocid;		/* just for debugging */
+} ReorderBufferTupleCidEnt;
+
+/* k-way in-order change iteration support structures */
+typedef struct ReorderBufferIterTXNEntry
+{
+	XLogRecPtr	lsn;
+	ReorderBufferChange *change;
+	ReorderBufferTXN *txn;
+	int			fd;
+	XLogSegNo	segno;
+} ReorderBufferIterTXNEntry;
+
+typedef struct ReorderBufferIterTXNState
+{
+	binaryheap *heap;
+	Size		nr_txns;
+	dlist_head	old_change;
+	ReorderBufferIterTXNEntry entries[FLEXIBLE_ARRAY_MEMBER];
+} ReorderBufferIterTXNState;
+
+/* toast datastructures */
+typedef struct ReorderBufferToastEnt
+{
+	Oid			chunk_id;		/* toast_table.chunk_id */
+	int32		last_chunk_seq; /* toast_table.chunk_seq of the last chunk we
+								 * have seen */
+	Size		num_chunks;		/* number of chunks we've already seen */
+	Size		size;			/* combined size of chunks seen */
+	dlist_head	chunks;			/* linked list of chunks */
+	struct varlena *reconstructed;		/* reconstructed varlena now pointed
+										 * to in main tup */
+} ReorderBufferToastEnt;
+
+
+/* number of changes kept in memory, per transaction */
+const Size	max_memtries = 4096;
+
+/* Size of the slab caches used for frequently allocated objects */
+const Size	max_cached_changes = 4096 * 2;
+const Size	max_cached_tuplebufs = 1024;		/* ~8MB */
+const Size	max_cached_transactions = 512;
+
+
+/* ---------------------------------------
+ * primary reorderbuffer support routines
+ * ---------------------------------------
+ */
+static ReorderBufferTXN *ReorderBufferGetTXN(ReorderBuffer *rb);
+static void ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static ReorderBufferTXN *ReorderBufferTXNByXid(ReorderBuffer *rb,
+					  TransactionId xid, bool create, bool *is_new,
+					  XLogRecPtr lsn, bool create_as_top);
+
+static void AssertTXNLsnOrder(ReorderBuffer *rb);
+
+/* ---------------------------------------
+ * support functions for lsn-order iterating over the ->changes of a
+ * transaction and its subtransactions
+ *
+ * used for iteration over the k-way heap merge of a transaction and its
+ * subtransactions
+ * ---------------------------------------
+ */
+static ReorderBufferIterTXNState *ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static ReorderBufferChange *
+			ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state);
+static void ReorderBufferIterTXNFinish(ReorderBuffer *rb,
+						   ReorderBufferIterTXNState *state);
+static void ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn);
+
+/*
+ * ---------------------------------------
+ * Disk serialization support functions
+ * ---------------------------------------
+ */
+static void ReorderBufferCheckSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							 int fd, ReorderBufferChange *change);
+static Size ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							int *fd, XLogSegNo *segno);
+static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						   char *change);
+static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn);
+
+static void ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap);
+static Snapshot ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
+					  ReorderBufferTXN *txn, CommandId cid);
+
+/* ---------------------------------------
+ * toast reassembly support
+ * ---------------------------------------
+ */
+/* Size of an EXTERNAL datum that contains a standard TOAST pointer */
+#define TOAST_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(struct varatt_external))
+
+/* Size of an indirect datum that contains a standard TOAST pointer */
+#define INDIRECT_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(struct varatt_indirect))
+
+static void ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn);
+static void ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						  Relation relation, ReorderBufferChange *change);
+static void ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							  Relation relation, ReorderBufferChange *change);
+
+
+/*
+ * Allocate a new ReorderBuffer
+ */
+ReorderBuffer *
+ReorderBufferAllocate(void)
+{
+	ReorderBuffer *buffer;
+	HASHCTL		hash_ctl;
+	MemoryContext new_ctx;
+
+	StaticAssertExpr((int) REORDER_BUFFER_CHANGE_INTERNAL_INSERT == (int) REORDER_BUFFER_CHANGE_INSERT, "out of sync enums");
+	StaticAssertExpr((int) REORDER_BUFFER_CHANGE_INTERNAL_UPDATE == (int) REORDER_BUFFER_CHANGE_UPDATE, "out of sync enums");
+	StaticAssertExpr((int) REORDER_BUFFER_CHANGE_INTERNAL_DELETE == (int) REORDER_BUFFER_CHANGE_DELETE, "out of sync enums");
+
+	new_ctx = AllocSetContextCreate(TopMemoryContext,
+									"ReorderBuffer",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+
+	buffer = (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+
+	buffer->context = new_ctx;
+
+	hash_ctl.keysize = sizeof(TransactionId);
+	hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
+	hash_ctl.hash = tag_hash;
+	hash_ctl.hcxt = buffer->context;
+
+	buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
+								 HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+	buffer->by_txn_last_xid = InvalidTransactionId;
+	buffer->by_txn_last_txn = NULL;
+
+	buffer->nr_cached_transactions = 0;
+	buffer->nr_cached_changes = 0;
+	buffer->nr_cached_tuplebufs = 0;
+
+	buffer->outbuf = NULL;
+	buffer->outbufsize = 0;
+
+	buffer->current_restart_decoding_lsn = InvalidXLogRecPtr;
+
+	dlist_init(&buffer->toplevel_by_lsn);
+	dlist_init(&buffer->cached_transactions);
+	dlist_init(&buffer->cached_changes);
+	slist_init(&buffer->cached_tuplebufs);
+
+	return buffer;
+}
+
+/*
+ * Free a ReorderBuffer
+ */
+void
+ReorderBufferFree(ReorderBuffer *rb)
+{
+	MemoryContext context = rb->context;
+
+	/*
+	 * We free separately allocated data by entirely scrapping oure personal
+	 * memory context.
+	 */
+	MemoryContextDelete(context);
+}
+
+/*
+ * Get a unused, possibly preallocated, ReorderBufferTXN.
+ */
+static ReorderBufferTXN *
+ReorderBufferGetTXN(ReorderBuffer *rb)
+{
+	ReorderBufferTXN *txn;
+
+	if (rb->nr_cached_transactions > 0)
+	{
+		rb->nr_cached_transactions--;
+		txn = (ReorderBufferTXN *)
+			dlist_container(ReorderBufferTXN, node,
+							dlist_pop_head_node(&rb->cached_transactions));
+	}
+	else
+	{
+		txn = (ReorderBufferTXN *)
+			MemoryContextAlloc(rb->context, sizeof(ReorderBufferTXN));
+	}
+
+	memset(txn, 0, sizeof(ReorderBufferTXN));
+
+	dlist_init(&txn->changes);
+	dlist_init(&txn->tuplecids);
+	dlist_init(&txn->subtxns);
+
+	return txn;
+}
+
+/*
+ * Free an ReorderBufferTXN. Deallocation might be delayed for efficiency
+ * purposes.
+ */
+void
+ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	/* clean the lookup cache if we were cached (quite likely) */
+	if (rb->by_txn_last_xid == txn->xid)
+	{
+		rb->by_txn_last_xid = InvalidTransactionId;
+		rb->by_txn_last_txn = NULL;
+	}
+
+	if (txn->tuplecid_hash != NULL)
+	{
+		hash_destroy(txn->tuplecid_hash);
+		txn->tuplecid_hash = NULL;
+	}
+
+	if (txn->invalidations)
+	{
+		pfree(txn->invalidations);
+		txn->invalidations = NULL;
+	}
+
+	if (rb->nr_cached_transactions < max_cached_transactions)
+	{
+		rb->nr_cached_transactions++;
+		dlist_push_head(&rb->cached_transactions, &txn->node);
+		VALGRIND_MAKE_MEM_UNDEFINED(txn, sizeof(ReorderBufferTXN));
+		VALGRIND_MAKE_MEM_DEFINED(&txn->node, sizeof(txn->node));
+	}
+	else
+	{
+		pfree(txn);
+	}
+}
+
+/*
+ * Get a unused, possibly preallocated, ReorderBufferChange.
+ */
+ReorderBufferChange *
+ReorderBufferGetChange(ReorderBuffer *rb)
+{
+	ReorderBufferChange *change;
+
+	if (rb->nr_cached_changes)
+	{
+		rb->nr_cached_changes--;
+		change = (ReorderBufferChange *)
+			dlist_container(ReorderBufferChange, node,
+							dlist_pop_head_node(&rb->cached_changes));
+	}
+	else
+	{
+		change = (ReorderBufferChange *)
+			MemoryContextAlloc(rb->context, sizeof(ReorderBufferChange));
+	}
+
+	memset(change, 0, sizeof(ReorderBufferChange));
+	return change;
+}
+
+/*
+ * Free an ReorderBufferChange. Deallocation might be delayed for efficiency
+ * purposes.
+ */
+void
+ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
+{
+	switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+	{
+		case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+		case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+		case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+			if (change->newtuple)
+			{
+				ReorderBufferReturnTupleBuf(rb, change->newtuple);
+				change->newtuple = NULL;
+			}
+
+			if (change->oldtuple)
+			{
+				ReorderBufferReturnTupleBuf(rb, change->oldtuple);
+				change->oldtuple = NULL;
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+			if (change->snapshot)
+			{
+				ReorderBufferFreeSnap(rb, change->snapshot);
+				change->snapshot = NULL;
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+			break;
+	}
+
+	if (rb->nr_cached_changes < max_cached_changes)
+	{
+		rb->nr_cached_changes++;
+		dlist_push_head(&rb->cached_changes, &change->node);
+		VALGRIND_MAKE_MEM_UNDEFINED(change, sizeof(ReorderBufferChange));
+		VALGRIND_MAKE_MEM_DEFINED(&change->node, sizeof(change->node));
+	}
+	else
+	{
+		pfree(change);
+	}
+}
+
+
+/*
+ * Get a unused, possibly preallocated, ReorderBufferTupleBuf
+ */
+ReorderBufferTupleBuf *
+ReorderBufferGetTupleBuf(ReorderBuffer *rb)
+{
+	ReorderBufferTupleBuf *tuple;
+
+	if (rb->nr_cached_tuplebufs)
+	{
+		rb->nr_cached_tuplebufs--;
+		tuple = slist_container(ReorderBufferTupleBuf, node,
+								slist_pop_head_node(&rb->cached_tuplebufs));
+#ifdef USE_ASSERT_CHECKING
+		memset(tuple, 0xdeadbeef, sizeof(ReorderBufferTupleBuf));
+#endif
+	}
+	else
+	{
+		tuple = (ReorderBufferTupleBuf *)
+			MemoryContextAlloc(rb->context, sizeof(ReorderBufferTupleBuf));
+	}
+
+	return tuple;
+}
+
+/*
+ * Free an ReorderBufferTupleBuf. Deallocation might be delayed for efficiency
+ * purposes.
+ */
+void
+ReorderBufferReturnTupleBuf(ReorderBuffer *rb, ReorderBufferTupleBuf *tuple)
+{
+	if (rb->nr_cached_tuplebufs < max_cached_tuplebufs)
+	{
+		rb->nr_cached_tuplebufs++;
+		slist_push_head(&rb->cached_tuplebufs, &tuple->node);
+		VALGRIND_MAKE_MEM_UNDEFINED(tuple, sizeof(ReorderBufferTupleBuf));
+		VALGRIND_MAKE_MEM_DEFINED(&tuple->node, sizeof(tuple->node));
+	}
+	else
+	{
+		pfree(tuple);
+	}
+}
+
+/*
+ * Return the ReorderBufferTXN from the given buffer, specified by Xid.
+ * If create is true, and a transaction doesn't already exist, create it
+ * (with the given LSN, and as top transaction if that's specified);
+ * when this happens, is_new is set to true.
+ */
+static ReorderBufferTXN *
+ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create,
+					  bool *is_new, XLogRecPtr lsn, bool create_as_top)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferTXNByIdEnt *ent;
+	bool		found;
+
+	Assert(TransactionIdIsValid(xid));
+	Assert(!create || lsn != InvalidXLogRecPtr);
+
+	/*
+	 * Check the one-entry lookup cache first
+	 */
+	if (TransactionIdIsValid(rb->by_txn_last_xid) &&
+		rb->by_txn_last_xid == xid)
+	{
+		txn = rb->by_txn_last_txn;
+
+		if (txn != NULL)
+		{
+			/* found it, and it's valid */
+			if (is_new)
+				*is_new = false;
+			return txn;
+		}
+
+		/*
+		 * cached as non-existant, and asked not to create? Then nothing else
+		 * to do.
+		 */
+		if (!create)
+			return NULL;
+		/* otherwise fall through to create it */
+	}
+
+	/*
+	 * If the cache wasn't hit or it yielded an "does-not-exist" and we want
+	 * to create an entry.
+	 */
+
+	/* search the lookup table */
+	ent = (ReorderBufferTXNByIdEnt *)
+		hash_search(rb->by_txn,
+					(void *) &xid,
+					create ? HASH_ENTER : HASH_FIND,
+					&found);
+	if (found)
+		txn = ent->txn;
+	else if (create)
+	{
+		/* initialize the new entry, if creation was requested */
+		Assert(ent != NULL);
+
+		ent->txn = ReorderBufferGetTXN(rb);
+		ent->txn->xid = xid;
+		txn = ent->txn;
+		txn->first_lsn = lsn;
+		txn->restart_decoding_lsn = rb->current_restart_decoding_lsn;
+
+		if (create_as_top)
+		{
+			dlist_push_tail(&rb->toplevel_by_lsn, &txn->node);
+			AssertTXNLsnOrder(rb);
+		}
+	}
+	else
+		txn = NULL;				/* not found and not asked to create */
+
+	/* update cache */
+	rb->by_txn_last_xid = xid;
+	rb->by_txn_last_txn = txn;
+
+	if (is_new)
+		*is_new = !found;
+
+	Assert(!create || !!txn);
+	return txn;
+}
+
+/*
+ * Queue a change into a transaction so it can be replayed upon commit.
+ */
+void
+ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
+					   ReorderBufferChange *change)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	change->lsn = lsn;
+	Assert(InvalidXLogRecPtr != lsn);
+	dlist_push_tail(&txn->changes, &change->node);
+	txn->nentries++;
+	txn->nentries_mem++;
+
+	ReorderBufferCheckSerializeTXN(rb, txn);
+}
+
+static void
+AssertTXNLsnOrder(ReorderBuffer *rb)
+{
+#ifdef USE_ASSERT_CHECKING
+	dlist_iter	iter;
+	XLogRecPtr	prev_first_lsn = InvalidXLogRecPtr;
+
+	dlist_foreach(iter, &rb->toplevel_by_lsn)
+	{
+		ReorderBufferTXN *cur_txn;
+
+		cur_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
+		Assert(cur_txn->first_lsn != InvalidXLogRecPtr);
+
+		if (cur_txn->end_lsn != InvalidXLogRecPtr)
+			Assert(cur_txn->first_lsn <= cur_txn->end_lsn);
+
+		if (prev_first_lsn != InvalidXLogRecPtr)
+			Assert(prev_first_lsn < cur_txn->first_lsn);
+
+		Assert(!cur_txn->is_known_as_subxact);
+		prev_first_lsn = cur_txn->first_lsn;
+	}
+#endif
+}
+
+ReorderBufferTXN *
+ReorderBufferGetOldestTXN(ReorderBuffer *rb)
+{
+	ReorderBufferTXN *txn;
+
+	if (dlist_is_empty(&rb->toplevel_by_lsn))
+		return NULL;
+
+	AssertTXNLsnOrder(rb);
+
+	txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn);
+
+	Assert(!txn->is_known_as_subxact);
+	Assert(txn->first_lsn != InvalidXLogRecPtr);
+	return txn;
+}
+
+void
+ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr)
+{
+	rb->current_restart_decoding_lsn = ptr;
+}
+
+void
+ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid,
+						 TransactionId subxid, XLogRecPtr lsn)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferTXN *subtxn;
+	bool		new_top;
+	bool		new_sub;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true);
+	subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false);
+
+	if (new_sub)
+	{
+		/*
+		 * we assign subtransactions to top level transaction even if we don't
+		 * have data for it yet, assignment records frequently reference xids
+		 * that have not yet produced any records. Knowing those aren't top
+		 * level xids allows us to make processing cheaper in some places.
+		 */
+		dlist_push_tail(&txn->subtxns, &subtxn->node);
+		txn->nsubtxns++;
+	}
+	else if (!subtxn->is_known_as_subxact)
+	{
+		subtxn->is_known_as_subxact = true;
+		Assert(subtxn->nsubtxns == 0);
+
+		/* remove from lsn order list of top-level transactions */
+		dlist_delete(&subtxn->node);
+
+		/* add to toplevel transaction */
+		dlist_push_tail(&txn->subtxns, &subtxn->node);
+		txn->nsubtxns++;
+	}
+	else if (new_top)
+	{
+		elog(ERROR, "existing subxact assigned to unknown toplevel xact");
+	}
+}
+
+/*
+ * Associate a subtransaction with its toplevel transaction at commit
+ * time. There may be no further changes added after this.
+ */
+void
+ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid,
+						 TransactionId subxid, XLogRecPtr commit_lsn,
+						 XLogRecPtr end_lsn)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferTXN *subtxn;
+
+	subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL,
+								   InvalidXLogRecPtr, false);
+
+	/*
+	 * No need to do anything if that subtxn didn't contain any changes
+	 */
+	if (!subtxn)
+		return;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, commit_lsn, true);
+
+	if (txn == NULL)
+		elog(ERROR, "subxact logged without previous toplevel record");
+
+	subtxn->final_lsn = commit_lsn;
+	subtxn->end_lsn = end_lsn;
+
+	if (!subtxn->is_known_as_subxact)
+	{
+		subtxn->is_known_as_subxact = true;
+		Assert(subtxn->nsubtxns == 0);
+
+		/* remove from lsn order list of top-level transactions */
+		dlist_delete(&subtxn->node);
+
+		/* add to subtransaction list */
+		dlist_push_tail(&txn->subtxns, &subtxn->node);
+		txn->nsubtxns++;
+	}
+}
+
+
+/*
+ * Support for efficiently iterating over a transaction's and its
+ * subtransactions' changes.
+ *
+ * We do by doing a k-way merge between transactions/subtransactions. For that
+ * we model the current heads of the different transactions as a binary heap so
+ * we easily know which (sub-)transaction has the change with the smallest lsn
+ * next.
+ *
+ * We assume the changes in individual transactions are already sorted by LSN.
+ */
+
+/*
+ * Binary heap comparison function.
+ */
+static int
+ReorderBufferIterCompare(Datum a, Datum b, void *arg)
+{
+	ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg;
+	XLogRecPtr	pos_a = state->entries[DatumGetInt32(a)].lsn;
+	XLogRecPtr	pos_b = state->entries[DatumGetInt32(b)].lsn;
+
+	if (pos_a < pos_b)
+		return 1;
+	else if (pos_a == pos_b)
+		return 0;
+	return -1;
+}
+
+/*
+ * Allocate & initialize an iterator which iterates in lsn order over a
+ * transaction and all its subtransactions.
+ */
+static ReorderBufferIterTXNState *
+ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	Size		nr_txns = 0;
+	ReorderBufferIterTXNState *state;
+	dlist_iter	cur_txn_i;
+	int32		off;
+
+	/*
+	 * Calculate the size of our heap: one element for every transaction that
+	 * contains changes.  (Besides the transactions already in the reorder
+	 * buffer, we count the one we were directly passed.)
+	 */
+	if (txn->nentries > 0)
+		nr_txns++;
+
+	dlist_foreach(cur_txn_i, &txn->subtxns)
+	{
+		ReorderBufferTXN *cur_txn;
+
+		cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
+
+		if (cur_txn->nentries > 0)
+			nr_txns++;
+	}
+
+	/*
+	 * XXX: Add fastpath for the rather common nr_txns=1 case, no need to
+	 * allocate/build a heap in that case.
+	 */
+
+	/* allocate iteration state */
+	state = (ReorderBufferIterTXNState *)
+		MemoryContextAllocZero(rb->context,
+							   sizeof(ReorderBufferIterTXNState) +
+							   sizeof(ReorderBufferIterTXNEntry) * nr_txns);
+
+	state->nr_txns = nr_txns;
+	dlist_init(&state->old_change);
+
+	for (off = 0; off < state->nr_txns; off++)
+	{
+		state->entries[off].fd = -1;
+		state->entries[off].segno = 0;
+	}
+
+	/* allocate heap */
+	state->heap = binaryheap_allocate(state->nr_txns, ReorderBufferIterCompare,
+									  state);
+
+	/*
+	 * Now insert items into the binary heap, unordered.  (We will run a heap
+	 * assembly step at the end; this is more efficient.)
+	 */
+
+	off = 0;
+
+	/* add toplevel transaction if it contains changes */
+	if (txn->nentries > 0)
+	{
+		ReorderBufferChange *cur_change;
+
+		if (txn->nentries != txn->nentries_mem)
+			ReorderBufferRestoreChanges(rb, txn, &state->entries[off].fd,
+										&state->entries[off].segno);
+
+		cur_change = dlist_head_element(ReorderBufferChange, node,
+										&txn->changes);
+
+		state->entries[off].lsn = cur_change->lsn;
+		state->entries[off].change = cur_change;
+		state->entries[off].txn = txn;
+
+		binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
+	}
+
+	/* add subtransactions if they contain changes */
+	dlist_foreach(cur_txn_i, &txn->subtxns)
+	{
+		ReorderBufferTXN *cur_txn;
+
+		cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
+
+		if (cur_txn->nentries > 0)
+		{
+			ReorderBufferChange *cur_change;
+
+			if (txn->nentries != txn->nentries_mem)
+				ReorderBufferRestoreChanges(rb, cur_txn,
+											&state->entries[off].fd,
+											&state->entries[off].segno);
+
+			cur_change = dlist_head_element(ReorderBufferChange, node,
+											&cur_txn->changes);
+
+			state->entries[off].lsn = cur_change->lsn;
+			state->entries[off].change = cur_change;
+			state->entries[off].txn = cur_txn;
+
+			binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
+		}
+	}
+
+	/* assemble a valid binary heap */
+	binaryheap_build(state->heap);
+
+	return state;
+}
+
+/*
+ * FIXME: better comment and/or name
+ */
+static void
+ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	XLogSegNo	first;
+	XLogSegNo	cur;
+	XLogSegNo	last;
+
+	Assert(txn->first_lsn != InvalidXLogRecPtr);
+	Assert(txn->final_lsn != InvalidXLogRecPtr);
+
+	XLByteToSeg(txn->first_lsn, first);
+	XLByteToSeg(txn->final_lsn, last);
+
+	for (cur = first; cur <= last; cur++)
+	{
+		char		path[MAXPGPATH];
+		XLogRecPtr	recptr;
+
+		XLogSegNoOffsetToRecPtr(cur, 0, recptr);
+
+		sprintf(path, "pg_llog/%s/xid-%u-lsn-%X-%X.snap",
+				NameStr(MyLogicalDecodingSlot->name), txn->xid,
+				(uint32) (recptr >> 32), (uint32) recptr);
+		if (unlink(path) != 0 && errno != ENOENT)
+			elog(FATAL, "could not unlink file \"%s\": %m", path);
+	}
+}
+
+/*
+ * Return the next change when iterating over a transaction and its
+ * subtransaction.
+ *
+ * Returns NULL when no further changes exist.
+ */
+static ReorderBufferChange *
+ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state)
+{
+	ReorderBufferChange *change;
+	ReorderBufferIterTXNEntry *entry;
+	int32		off;
+
+	/* nothing there anymore */
+	if (state->heap->bh_size == 0)
+		return NULL;
+
+	off = DatumGetInt32(binaryheap_first(state->heap));
+	entry = &state->entries[off];
+
+	if (!dlist_is_empty(&entry->txn->subtxns))
+		elog(LOG, "tx with subtxn %u", entry->txn->xid);
+
+	/* free memory we might have "leaked" in the previous *Next call */
+	if (!dlist_is_empty(&state->old_change))
+	{
+		change = dlist_container(ReorderBufferChange, node,
+								 dlist_pop_head_node(&state->old_change));
+		ReorderBufferReturnChange(rb, change);
+		Assert(dlist_is_empty(&state->old_change));
+	}
+
+	change = entry->change;
+
+	/*
+	 * update heap with information about which transaction has the next
+	 * relevant change in LSN order
+	 */
+
+	/* there are in-memory changes */
+	if (dlist_has_next(&entry->txn->changes, &entry->change->node))
+	{
+		dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node);
+		ReorderBufferChange *next_change =
+		dlist_container(ReorderBufferChange, node, next);
+
+		/* txn stays the same */
+		state->entries[off].lsn = next_change->lsn;
+		state->entries[off].change = next_change;
+
+		binaryheap_replace_first(state->heap, Int32GetDatum(off));
+		return change;
+	}
+
+	/* try to load changes from disk */
+	if (entry->txn->nentries != entry->txn->nentries_mem)
+	{
+		/*
+		 * Ugly: restoring changes will reuse *Change records, thus delete the
+		 * current one from the per-tx list and only free in the next call.
+		 */
+		dlist_delete(&change->node);
+		dlist_push_tail(&state->old_change, &change->node);
+
+		if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->fd,
+										&state->entries[off].segno))
+		{
+			/* successfully restored changes from disk */
+			ReorderBufferChange *next_change =
+			dlist_head_element(ReorderBufferChange, node,
+							   &entry->txn->changes);
+
+			elog(DEBUG2, "restored %zu/%zu changes from disk",
+				 entry->txn->nentries_mem, entry->txn->nentries);
+			Assert(entry->txn->nentries_mem);
+			/* txn stays the same */
+			state->entries[off].lsn = next_change->lsn;
+			state->entries[off].change = next_change;
+			binaryheap_replace_first(state->heap, Int32GetDatum(off));
+
+			return change;
+		}
+	}
+
+	/* ok, no changes there anymore, remove */
+	binaryheap_remove_first(state->heap);
+
+	return change;
+}
+
+/*
+ * Deallocate the iterator
+ */
+static void
+ReorderBufferIterTXNFinish(ReorderBuffer *rb,
+						   ReorderBufferIterTXNState *state)
+{
+	int32		off;
+
+	for (off = 0; off < state->nr_txns; off++)
+	{
+		if (state->entries[off].fd != -1)
+			CloseTransientFile(state->entries[off].fd);
+	}
+
+	/* free memory we might have "leaked" in the last *Next call */
+	if (!dlist_is_empty(&state->old_change))
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node,
+								 dlist_pop_head_node(&state->old_change));
+		ReorderBufferReturnChange(rb, change);
+		Assert(dlist_is_empty(&state->old_change));
+	}
+
+	binaryheap_free(state->heap);
+	pfree(state);
+}
+
+/*
+ * Cleanup the contents of a transaction, usually after the transaction
+ * committed or aborted.
+ */
+static void
+ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	bool		found;
+	dlist_mutable_iter iter;
+
+	/* cleanup subtransactions & their changes */
+	dlist_foreach_modify(iter, &txn->subtxns)
+	{
+		ReorderBufferTXN *subtxn;
+
+		subtxn = dlist_container(ReorderBufferTXN, node, iter.cur);
+		Assert(subtxn->is_known_as_subxact);
+		Assert(subtxn->nsubtxns == 0);
+
+		/*
+		 * subtransactions are always associated to the toplevel TXN, even if
+		 * they originally were happening inside another subtxn, so we won't
+		 * ever recurse more than one level here.
+		 */
+		ReorderBufferCleanupTXN(rb, subtxn);
+	}
+
+	/* cleanup changes in the toplevel txn */
+	dlist_foreach_modify(iter, &txn->changes)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+
+		ReorderBufferReturnChange(rb, change);
+	}
+
+	/*
+	 * cleanup the tuplecids we stored timetravel access. They are always
+	 * stored in the toplevel transaction.
+	 */
+	dlist_foreach_modify(iter, &txn->tuplecids)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+		Assert(change->action_internal == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
+		ReorderBufferReturnChange(rb, change);
+	}
+
+	if (txn->base_snapshot != NULL)
+	{
+		SnapBuildSnapDecRefcount(txn->base_snapshot);
+		txn->base_snapshot = NULL;
+	}
+
+	/* delete from list of known subxacts */
+	if (txn->is_known_as_subxact)
+	{
+		dlist_delete(&txn->node);
+	}
+	/* delete from LSN ordered list of toplevel TXNs */
+	else
+	{
+		/* FIXME: adjust nsubxacts count of parent */
+		dlist_delete(&txn->node);
+	}
+
+	/* now remove reference from buffer */
+	hash_search(rb->by_txn,
+				(void *) &txn->xid,
+				HASH_REMOVE,
+				&found);
+	Assert(found);
+
+	/* remove entries spilled to disk */
+	if (txn->nentries != txn->nentries_mem)
+		ReorderBufferRestoreCleanup(rb, txn);
+
+	/* deallocate */
+	ReorderBufferReturnTXN(rb, txn);
+}
+
+/*
+ * Build a hash with a (relfilenode, ctid) -> (cmin, cmax) mapping for use by
+ * tqual.c's HeapTupleSatisfiesMVCCDuringDecoding.
+ */
+static void
+ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	dlist_iter	iter;
+	HASHCTL		hash_ctl;
+
+	if (!txn->does_timetravel || dlist_is_empty(&txn->tuplecids))
+		return;
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+
+	hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
+	hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
+	hash_ctl.hash = tag_hash;
+	hash_ctl.hcxt = rb->context;
+
+	/*
+	 * create the hash with the exact number of to-be-stored tuplecids from
+	 * the start
+	 */
+	txn->tuplecid_hash =
+		hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
+					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+	dlist_foreach(iter, &txn->tuplecids)
+	{
+		ReorderBufferTupleCidKey key;
+		ReorderBufferTupleCidEnt *ent;
+		bool		found;
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+
+		Assert(change->action_internal == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
+
+		/* be careful about padding */
+		memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
+
+		key.relnode = change->tuplecid.node;
+
+		ItemPointerCopy(&change->tuplecid.tid,
+						&key.tid);
+
+		ent = (ReorderBufferTupleCidEnt *)
+			hash_search(txn->tuplecid_hash,
+						(void *) &key,
+						HASH_ENTER | HASH_FIND,
+						&found);
+		if (!found)
+		{
+			ent->cmin = change->tuplecid.cmin;
+			ent->cmax = change->tuplecid.cmax;
+			ent->combocid = change->tuplecid.combocid;
+		}
+		else
+		{
+			Assert(ent->cmin == change->tuplecid.cmin);
+			Assert(ent->cmax == InvalidCommandId ||
+				   ent->cmax == change->tuplecid.cmax);
+
+			/*
+			 * if the tuple got valid in this transaction and now got deleted
+			 * we already have a valid cmin stored. The cmax will be
+			 * InvalidCommandId though.
+			 */
+			ent->cmax = change->tuplecid.cmax;
+		}
+	}
+}
+
+/*
+ * Copy a provided snapshot so we can modify it privately. This is needed so
+ * that catalog modifying transactions can look into intermediate catalog
+ * states.
+ */
+static Snapshot
+ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
+					  ReorderBufferTXN *txn, CommandId cid)
+{
+	Snapshot	snap;
+	dlist_iter	iter;
+	int			i = 0;
+	Size		size;
+
+	size = sizeof(SnapshotData) +
+		sizeof(TransactionId) * orig_snap->xcnt +
+		sizeof(TransactionId) * (txn->nsubtxns + 1);
+
+	elog(DEBUG1, "copying a non-transaction-specific snapshot into timetravel tx %u", txn->xid);
+
+	snap = MemoryContextAllocZero(rb->context, size);
+	memcpy(snap, orig_snap, sizeof(SnapshotData));
+
+	snap->copied = true;
+	snap->active_count = 0;
+	snap->regd_count = 0;
+	snap->xip = (TransactionId *) (snap + 1);
+
+	memcpy(snap->xip, orig_snap->xip, sizeof(TransactionId) * snap->xcnt);
+
+	/*
+	 * ->subxip contains all txids that belong to our transaction which we
+	 * need to check via cmin/cmax. Thats why we store the toplevel
+	 * transaction in there as well.
+	 */
+	snap->subxip = snap->xip + snap->xcnt;
+	snap->subxip[i++] = txn->xid;
+	snap->subxcnt = txn->nsubtxns + 1;
+
+	dlist_foreach(iter, &txn->subtxns)
+	{
+		ReorderBufferTXN *sub_txn;
+
+		sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
+		snap->subxip[i++] = sub_txn->xid;
+	}
+
+	/* sort so we can bsearch() later */
+	qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator);
+
+	/* store the specified current CommandId */
+	snap->curcid = cid;
+
+	return snap;
+}
+
+/*
+ * Free a previously ReorderBufferCopySnap'ed snapshot
+ */
+static void
+ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap)
+{
+	if (snap->copied)
+		pfree(snap);
+	else
+		SnapBuildSnapDecRefcount(snap);
+}
+
+/*
+ * Commit a transaction and replay all actions that previously have been
+ * ReorderBufferQueueChange'd in the toplevel TX or any of the subtransactions
+ * assigned via ReorderBufferCommitChild.
+ */
+void
+ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, XLogRecPtr commit_lsn,
+					XLogRecPtr end_lsn)
+{
+	ReorderBufferTXN *txn;
+	ReorderBufferIterTXNState *iterstate = NULL;
+	ReorderBufferChange *change;
+	CommandId	command_id = FirstCommandId;
+	volatile Snapshot snapshot_now;
+	Relation	relation = NULL;
+	Oid reloid;
+	bool is_transaction_state = IsTransactionOrTransactionBlock();
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+
+	/* empty transaction */
+	if (txn == NULL)
+		return;
+
+	txn->final_lsn = commit_lsn;
+	txn->end_lsn = end_lsn;
+
+	/* serialize the last bunch of changes if we need start earlier anyway */
+	if (txn->nentries_mem != txn->nentries)
+		ReorderBufferSerializeTXN(rb, txn);
+
+	/*
+	 * If this transaction didn't have any real changes in our database, it's
+	 * OK not to have a snapshot.
+	 */
+	if (txn->base_snapshot == NULL)
+		return;
+
+	snapshot_now = txn->base_snapshot;
+
+	ReorderBufferBuildTupleCidHash(rb, txn);
+
+	/* setup initial snapshot */
+	SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+
+	PG_TRY();
+	{
+		/*
+		 * Decoding needs access to syscaches et al., which in turn use
+		 * heavyweight locks and such. Thus we need to have enough state around
+		 * to keep track of those. The easiest way is to simply use a
+		 * transaction internally. That also allows us to easily enforce that
+		 * nothing writes to the database by checking for xid assignments.
+		 *
+		 * When we're called via the SQL SRF there's already a transaction
+		 * started, so start an explicit subtransaction there.
+		 */
+		if (is_transaction_state)
+			BeginInternalSubTransaction("replay");
+		else
+			StartTransactionCommand();
+
+		rb->begin(rb, txn);
+
+		iterstate = ReorderBufferIterTXNInit(rb, txn);
+		while ((change = ReorderBufferIterTXNNext(rb, iterstate)))
+		{
+			switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+			{
+				case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+				case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+				case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+					Assert(snapshot_now);
+
+					reloid = RelidByRelfilenode(change->relnode.spcNode,
+												change->relnode.relNode);
+
+					/*
+					 * catalog tuple without data, while catalog has been
+					 * rewritten
+					 */
+					if (reloid == InvalidOid &&
+						change->newtuple == NULL && change->oldtuple == NULL)
+						continue;
+					else if (reloid == InvalidOid)
+						elog(ERROR, "could not lookup relation %s",
+							 relpathperm(change->relnode, MAIN_FORKNUM));
+
+					relation = RelationIdGetRelation(reloid);
+
+					if (relation == NULL)
+						elog(ERROR, "could open relation descriptor %s",
+							 relpathperm(change->relnode, MAIN_FORKNUM));
+
+					if (RelationIsLogicallyLogged(relation))
+					{
+						/* user-triggered change */
+						if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
+						{
+						}
+						else if (!IsToastRelation(relation))
+						{
+							ReorderBufferToastReplace(rb, txn, relation, change);
+							rb->apply_change(rb, txn, relation, change);
+							ReorderBufferToastReset(rb, txn);
+						}
+						/* we're not interested in toast deletions */
+						else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
+						{
+							/*
+							 * need to reassemble change in memory, ensure it
+							 * doesn't get reused till we're done.
+							 */
+							dlist_delete(&change->node);
+							ReorderBufferToastAppendChunk(rb, txn, relation,
+														  change);
+						}
+
+					}
+					RelationClose(relation);
+					break;
+				case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+					/* XXX: we could skip snapshots in non toplevel txns */
+
+					/* get rid of the old */
+					RevertFromDecodingSnapshots();
+
+					if (snapshot_now->copied)
+					{
+						ReorderBufferFreeSnap(rb, snapshot_now);
+						snapshot_now =
+							ReorderBufferCopySnap(rb, change->snapshot,
+												  txn, command_id);
+					}
+
+					/*
+					 * restored from disk, we need to be careful not to double
+					 * free. We could introduce refcounting for that, but for
+					 * now this seems infrequent enough not to care.
+					 */
+					else if (change->snapshot->copied)
+					{
+						snapshot_now =
+							ReorderBufferCopySnap(rb, change->snapshot,
+												  txn, command_id);
+					}
+					else
+					{
+						snapshot_now = change->snapshot;
+					}
+
+
+					/* and start with the new one */
+					SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+					break;
+
+				case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+					if (!snapshot_now->copied)
+					{
+						/* we don't use the global one anymore */
+						snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
+															 txn, command_id);
+					}
+
+					command_id = Max(command_id, change->command_id);
+
+					if (command_id != InvalidCommandId)
+					{
+						snapshot_now->curcid = command_id;
+
+						RevertFromDecodingSnapshots();
+						SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+					}
+
+					/*
+					 * everytime the CommandId is incremented, we could see
+					 * new catalog contents
+					 */
+					ReorderBufferExecuteInvalidations(rb, txn);
+
+					break;
+
+				case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+					elog(ERROR, "tuplecid value in normal queue");
+					break;
+			}
+		}
+
+		ReorderBufferIterTXNFinish(rb, iterstate);
+
+		/* call commit callback */
+		rb->commit(rb, txn, commit_lsn);
+
+		/* make sure nothing has written anything */
+		if (GetTopTransactionIdIfAny() != InvalidTransactionId)
+			elog(ERROR, "cannot write during replay");
+
+		/*
+		 * Abort subtransaction or aborting transaction as a whole has the
+		 * right semantics. We want all locks acquired in here to be released,
+		 * not reassinged to the parent and we do not want any database access
+		 * have persistent effects.
+		 */
+		if (is_transaction_state)
+			RollbackAndReleaseCurrentSubTransaction();
+		else
+			AbortCurrentTransaction();
+
+		/* make sure there's no cache pollution */
+		ReorderBufferExecuteInvalidations(rb, txn);
+
+		/* cleanup */
+		RevertFromDecodingSnapshots();
+
+		if (snapshot_now->copied)
+			ReorderBufferFreeSnap(rb, snapshot_now);
+
+		ReorderBufferCleanupTXN(rb, txn);
+	}
+	PG_CATCH();
+	{
+		/* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */
+		if (iterstate)
+			ReorderBufferIterTXNFinish(rb, iterstate);
+
+		if (is_transaction_state)
+			RollbackAndReleaseCurrentSubTransaction();
+		else
+			AbortCurrentTransaction();
+
+		ReorderBufferExecuteInvalidations(rb, txn);
+
+		RevertFromDecodingSnapshots();
+
+		if (snapshot_now->copied)
+			ReorderBufferFreeSnap(rb, snapshot_now);
+
+		/*
+		 * don't do a ReorderBufferCleanupTXN here, with the vague idea of
+		 * allowing to retry decoding.
+		 */
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+}
+
+/*
+ * Abort a transaction that possibly has previous changes. Needs to be done
+ * independently for toplevel and subtransactions.
+ */
+void
+ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+
+	/* no changes in this commit */
+	if (txn == NULL)
+		return;
+
+	txn->final_lsn = lsn;
+
+	ReorderBufferCleanupTXN(rb, txn);
+}
+
+/*
+ * Check whether a transaction is already known in this module
+ */
+bool
+ReorderBufferIsXidKnown(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+	return txn != NULL;
+}
+
+/*
+ * Add a new snapshot to this transaction that is only used after lsn 'lsn'.
+ */
+void
+ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid,
+						 XLogRecPtr lsn, Snapshot snap)
+{
+	ReorderBufferChange *change = ReorderBufferGetChange(rb);
+
+	change->snapshot = snap;
+	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT;
+
+	ReorderBufferQueueChange(rb, xid, lsn, change);
+}
+
+/*
+ * Setup the base snapshot of a transaction. That is the snapshot that is used
+ * to decode all changes until either this transaction modifies the catalog or
+ * another catalog modifying transaction commits.
+ */
+void
+ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, Snapshot snap)
+{
+	ReorderBufferTXN *txn;
+	bool		is_new;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true);
+	Assert(txn->base_snapshot == NULL);
+
+	txn->base_snapshot = snap;
+}
+
+/*
+ * Access the catalog with this CommandId at this point in the changestream.
+ *
+ * May only be called for command ids > 1
+ */
+void
+ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, CommandId cid)
+{
+	ReorderBufferChange *change = ReorderBufferGetChange(rb);
+
+	change->command_id = cid;
+	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID;
+
+	ReorderBufferQueueChange(rb, xid, lsn, change);
+}
+
+
+/*
+ * Add new (relfilenode, tid) -> (cmin, cmax) mappings.
+ */
+void
+ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, RelFileNode node,
+							 ItemPointerData tid, CommandId cmin,
+							 CommandId cmax, CommandId combocid)
+{
+	ReorderBufferChange *change = ReorderBufferGetChange(rb);
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	change->tuplecid.node = node;
+	change->tuplecid.tid = tid;
+	change->tuplecid.cmin = cmin;
+	change->tuplecid.cmax = cmax;
+	change->tuplecid.combocid = combocid;
+	change->lsn = lsn;
+	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
+
+	dlist_push_tail(&txn->tuplecids, &change->node);
+	txn->ntuplecids++;
+}
+
+/*
+ * Setup the invalidation of the toplevel transaction.
+ *
+ * This needs to be done before ReorderBufferCommit is called!
+ */
+void
+ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid,
+							  XLogRecPtr lsn, Size nmsgs,
+							  SharedInvalidationMessage *msgs)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	if (txn->ninvalidations != 0)
+		elog(ERROR, "only ever add one set of invalidations");
+
+	Assert(nmsgs > 0);
+
+	txn->ninvalidations = nmsgs;
+	txn->invalidations = (SharedInvalidationMessage *)
+		MemoryContextAlloc(rb->context,
+						   sizeof(SharedInvalidationMessage) * nmsgs);
+	memcpy(txn->invalidations, msgs, sizeof(SharedInvalidationMessage) * nmsgs);
+}
+
+/*
+ * Apply all invalidations we know. Possibly we only need parts at this point
+ * in the changestream but we don't know which those are.
+ */
+static void
+ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	int			i;
+
+	for (i = 0; i < txn->ninvalidations; i++)
+		LocalExecuteInvalidationMessage(&txn->invalidations[i]);
+}
+
+/*
+ * Mark a transaction as doing timetravel.
+ */
+void
+ReorderBufferXidSetTimetravel(ReorderBuffer *rb, TransactionId xid,
+							  XLogRecPtr lsn)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
+
+	txn->does_timetravel = true;
+}
+
+/*
+ * Query whether a transaction is already *known* to be doing timetravel. This
+ * can be wrong until directly before the commit!
+ */
+bool
+ReorderBufferXidDoesTimetravel(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+	if (txn == NULL)
+		return false;
+
+	return txn->does_timetravel;
+}
+
+/*
+ * Have we already added the first snapshot?
+ */
+bool
+ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+
+	/* transaction isn't known yet, ergo no snapshot */
+	if (txn == NULL)
+		return false;
+
+	return txn->base_snapshot != NULL;
+}
+
+static void
+ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz)
+{
+	if (!rb->outbufsize)
+	{
+		rb->outbuf = MemoryContextAlloc(rb->context, sz);
+		rb->outbufsize = sz;
+	}
+	else if (rb->outbufsize < sz)
+	{
+		rb->outbuf = repalloc(rb->outbuf, sz);
+		rb->outbufsize = sz;
+	}
+}
+
+typedef struct ReorderBufferDiskChange
+{
+	Size		size;
+	ReorderBufferChange change;
+	/* data follows */
+} ReorderBufferDiskChange;
+
+/*
+ * Persistency support
+ */
+static void
+ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							 int fd, ReorderBufferChange *change)
+{
+	ReorderBufferDiskChange *ondisk;
+	Size		sz = sizeof(ReorderBufferDiskChange);
+
+	ReorderBufferSerializeReserve(rb, sz);
+
+	ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+	memcpy(&ondisk->change, change, sizeof(ReorderBufferChange));
+
+	switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+	{
+		case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+			{
+				char	   *data;
+				Size		oldlen = 0;
+				Size		newlen = 0;
+
+				if (change->oldtuple)
+					oldlen = offsetof(ReorderBufferTupleBuf, data)
+						+change->oldtuple->tuple.t_len
+						- offsetof(HeapTupleHeaderData, t_bits);
+
+				if (change->newtuple)
+					newlen = offsetof(ReorderBufferTupleBuf, data)
+						+change->newtuple->tuple.t_len
+						- offsetof(HeapTupleHeaderData, t_bits);
+
+				sz += oldlen;
+				sz += newlen;
+
+				/* make sure we have enough space */
+				ReorderBufferSerializeReserve(rb, sz);
+
+				data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+				/* might have been reallocated above */
+				ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+				if (oldlen)
+				{
+					memcpy(data, change->oldtuple, oldlen);
+					data += oldlen;
+					Assert(&change->oldtuple->header == change->oldtuple->tuple.t_data);
+				}
+
+				if (newlen)
+				{
+					memcpy(data, change->newtuple, newlen);
+					data += newlen;
+					Assert(&change->newtuple->header == change->newtuple->tuple.t_data);
+				}
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+			{
+				char	   *data;
+
+				sz += sizeof(SnapshotData) +
+					sizeof(TransactionId) * change->snapshot->xcnt +
+					sizeof(TransactionId) * change->snapshot->subxcnt
+					;
+
+				/* make sure we have enough space */
+				ReorderBufferSerializeReserve(rb, sz);
+				data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+				/* might have been reallocated above */
+				ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+				memcpy(data, change->snapshot, sizeof(SnapshotData));
+				data += sizeof(SnapshotData);
+
+				if (change->snapshot->xcnt)
+				{
+					memcpy(data, change->snapshot->xip,
+						   sizeof(TransactionId) + change->snapshot->xcnt);
+					data += sizeof(TransactionId) + change->snapshot->xcnt;
+				}
+
+				if (change->snapshot->subxcnt)
+				{
+					memcpy(data, change->snapshot->subxip,
+						   sizeof(TransactionId) + change->snapshot->subxcnt);
+					data += sizeof(TransactionId) + change->snapshot->subxcnt;
+				}
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+			/* ReorderBufferChange contains everything important */
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+			/* ReorderBufferChange contains everything important */
+			break;
+	}
+
+	ondisk->size = sz;
+
+	if (write(fd, rb->outbuf, ondisk->size) != ondisk->size)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not write to xid data file \"%u\": %m",
+						txn->xid)));
+	}
+
+	Assert(ondisk->change.action_internal == change->action_internal);
+}
+
+static void
+ReorderBufferCheckSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	/* FIXME subtxn handling? */
+	if (txn->nentries_mem >= max_memtries)
+	{
+		ReorderBufferSerializeTXN(rb, txn);
+		Assert(txn->nentries_mem == 0);
+	}
+}
+
+static void
+ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	dlist_iter	subtxn_i;
+	dlist_mutable_iter change_i;
+	int			fd = -1;
+	XLogSegNo	curOpenSegNo = 0;
+	Size		spilled = 0;
+	char		path[MAXPGPATH];
+
+	elog(DEBUG2, "spill %zu changes in tx %u to disk",
+		 txn->nentries_mem, txn->xid);
+
+	/* do the same to all child TXs */
+	dlist_foreach(subtxn_i, &txn->subtxns)
+	{
+		ReorderBufferTXN *subtxn;
+
+		subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur);
+		ReorderBufferSerializeTXN(rb, subtxn);
+	}
+
+	/* serialize changestream */
+	dlist_foreach_modify(change_i, &txn->changes)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, change_i.cur);
+
+		/*
+		 * store in segment in which it belongs by start lsn, don't split over
+		 * multiple segments tho
+		 */
+		if (fd == -1 || XLByteInSeg(change->lsn, curOpenSegNo))
+		{
+			XLogRecPtr	recptr;
+
+			if (fd != -1)
+				CloseTransientFile(fd);
+
+			XLByteToSeg(change->lsn, curOpenSegNo);
+			XLogSegNoOffsetToRecPtr(curOpenSegNo, 0, recptr);
+
+			sprintf(path, "pg_llog/%s/xid-%u-lsn-%X-%X.snap",
+					NameStr(MyLogicalDecodingSlot->name), txn->xid,
+					(uint32) (recptr >> 32), (uint32) recptr);
+
+			/* open segment, create it if necessary */
+			fd = OpenTransientFile(path,
+								   O_CREAT | O_WRONLY | O_APPEND | PG_BINARY,
+								   S_IRUSR | S_IWUSR);
+
+			if (fd < 0)
+				ereport(ERROR, (errmsg("could not open reorderbuffer file %s for writing: %m", path)));
+		}
+
+		ReorderBufferSerializeChange(rb, txn, fd, change);
+		dlist_delete(&change->node);
+		ReorderBufferReturnChange(rb, change);
+
+		spilled++;
+	}
+
+	Assert(spilled == txn->nentries_mem);
+	Assert(dlist_is_empty(&txn->changes));
+	txn->nentries_mem = 0;
+
+	if (fd != -1)
+		CloseTransientFile(fd);
+
+	/* issue write barrier */
+	/* serialize main transaction state */
+}
+
+static Size
+ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							int *fd, XLogSegNo *segno)
+{
+	Size		restored = 0;
+	XLogSegNo	last_segno;
+	dlist_mutable_iter cleanup_iter;
+
+	Assert(txn->first_lsn != InvalidXLogRecPtr);
+	Assert(txn->final_lsn != InvalidXLogRecPtr);
+
+	/* free current entries, so we have memory for more */
+	dlist_foreach_modify(cleanup_iter, &txn->changes)
+	{
+		ReorderBufferChange *cleanup =
+		dlist_container(ReorderBufferChange, node, cleanup_iter.cur);
+
+		dlist_delete(&cleanup->node);
+		ReorderBufferReturnChange(rb, cleanup);
+	}
+	txn->nentries_mem = 0;
+	Assert(dlist_is_empty(&txn->changes));
+
+	XLByteToSeg(txn->final_lsn, last_segno);
+
+	while (restored < max_memtries && *segno <= last_segno)
+	{
+		int			readBytes;
+		ReorderBufferDiskChange *ondisk;
+
+		if (*fd == -1)
+		{
+			XLogRecPtr	recptr;
+			char		path[MAXPGPATH];
+
+			/* first time in */
+			if (*segno == 0)
+			{
+				XLByteToSeg(txn->first_lsn, *segno);
+				elog(LOG, "initial restoring from %zu to %zu",
+					 *segno, last_segno);
+			}
+
+			Assert(*segno != 0 || dlist_is_empty(&txn->changes));
+			XLogSegNoOffsetToRecPtr(*segno, 0, recptr);
+
+			sprintf(path, "pg_llog/%s/xid-%u-lsn-%X-%X.snap",
+					NameStr(MyLogicalDecodingSlot->name), txn->xid,
+					(uint32) (recptr >> 32), (uint32) recptr);
+
+			elog(LOG, "opening file %s", path);
+
+			*fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+			if (*fd < 0 && errno == ENOENT)
+			{
+				*fd = -1;
+				(*segno)++;
+				continue;
+			}
+			else if (*fd < 0)
+				ereport(ERROR, (errmsg("could not open reorderbuffer file %s for reading: %m", path)));
+
+		}
+
+		ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange));
+
+
+		/*
+		 * read the statically sized part of a change which has information
+		 * about the total size. If we couldn't read a record, we're at the
+		 * end of this file.
+		 */
+
+		readBytes = read(*fd, rb->outbuf, sizeof(ReorderBufferDiskChange));
+
+		/* eof */
+		if (readBytes == 0)
+		{
+			CloseTransientFile(*fd);
+			*fd = -1;
+			(*segno)++;
+			continue;
+		}
+		else if (readBytes < 0)
+			elog(ERROR, "read failed: %m");
+		else if (readBytes != sizeof(ReorderBufferDiskChange))
+			elog(ERROR, "incomplete read, read %d instead of %zu",
+				 readBytes, sizeof(ReorderBufferDiskChange));
+
+		ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+		ReorderBufferSerializeReserve(rb,
+									  sizeof(ReorderBufferDiskChange) + ondisk->size);
+		ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+		readBytes = read(*fd, rb->outbuf + sizeof(ReorderBufferDiskChange),
+						 ondisk->size - sizeof(ReorderBufferDiskChange));
+
+		if (readBytes < 0)
+			elog(ERROR, "read2 failed: %m");
+		else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange))
+			elog(ERROR, "incomplete read2, read %d instead of %zu",
+				 readBytes, ondisk->size - sizeof(ReorderBufferDiskChange));
+
+		/*
+		 * ok, read a full change from disk, now restore it into proper
+		 * in-memory format
+		 */
+		ReorderBufferRestoreChange(rb, txn, rb->outbuf);
+		restored++;
+	}
+
+	return restored;
+}
+
+/*
+ * Convert change from its on-disk format to in-memory format and queue it onto
+ * the TXN's ->changes list.
+ */
+static void
+ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						   char *data)
+{
+	ReorderBufferDiskChange *ondisk;
+	ReorderBufferChange *change;
+
+	ondisk = (ReorderBufferDiskChange *) data;
+
+	change = ReorderBufferGetChange(rb);
+
+	/* copy static part */
+	memcpy(change, &ondisk->change, sizeof(ReorderBufferChange));
+
+	data += sizeof(ReorderBufferDiskChange);
+
+	/* restore individual stuff */
+	switch ((ReorderBufferChangeTypeInternal) change->action_internal)
+	{
+		case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_UPDATE:
+			/* fall through */
+		case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
+			if (change->newtuple)
+			{
+				Size		len = offsetof(ReorderBufferTupleBuf, data)
+				+((ReorderBufferTupleBuf *) data)->tuple.t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+
+				change->newtuple = ReorderBufferGetTupleBuf(rb);
+				memcpy(change->newtuple, data, len);
+				change->newtuple->tuple.t_data = &change->newtuple->header;
+
+				data += len;
+			}
+
+			if (change->oldtuple)
+			{
+				Size		len = offsetof(ReorderBufferTupleBuf, data)
+				+((ReorderBufferTupleBuf *) data)->tuple.t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+
+				change->oldtuple = ReorderBufferGetTupleBuf(rb);
+				memcpy(change->oldtuple, data, len);
+				change->oldtuple->tuple.t_data = &change->oldtuple->header;
+				data += len;
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
+			{
+				Snapshot	oldsnap = (Snapshot) data;
+				Size		size = sizeof(SnapshotData) +
+				sizeof(TransactionId) * oldsnap->xcnt +
+				sizeof(TransactionId) * (oldsnap->subxcnt + 0)
+						   ;
+
+				Assert(change->snapshot != NULL);
+
+				change->snapshot = MemoryContextAllocZero(rb->context, size);
+
+				memcpy(change->snapshot, data, size);
+				change->snapshot->xip = (TransactionId *)
+					(((char *) change->snapshot) + sizeof(SnapshotData));
+				change->snapshot->subxip =
+					change->snapshot->xip + change->snapshot->xcnt + 0;
+				change->snapshot->copied = true;
+				break;
+			}
+			/* nothing needs to be done */
+		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
+		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
+			break;
+	}
+
+	dlist_push_tail(&txn->changes, &change->node);
+	txn->nentries_mem++;
+}
+
+/*
+ * Delete all data spilled to disk after we've restarted/crashed. It will be
+ * recreated when the respective slots are reused.
+ */
+void
+ReorderBufferStartup(void)
+{
+	DIR		   *logical_dir;
+	struct dirent *logical_de;
+
+	DIR		   *spill_dir;
+	struct dirent *spill_de;
+
+	logical_dir = AllocateDir("pg_llog");
+	while ((logical_de = ReadDir(logical_dir, "pg_llog")) != NULL)
+	{
+		char		path[MAXPGPATH];
+
+		if (strcmp(logical_de->d_name, ".") == 0 ||
+			strcmp(logical_de->d_name, "..") == 0)
+			continue;
+
+		/* one of our own directories */
+		if (strcmp(logical_de->d_name, "snapshots") == 0)
+			continue;
+
+		/*
+		 * ok, has to be a surviving logical slot, iterate and delete
+		 * everythign starting with xid-*
+		 */
+		sprintf(path, "pg_llog/%s", logical_de->d_name);
+
+		spill_dir = AllocateDir(path);
+		while ((spill_de = ReadDir(spill_dir, "pg_llog")) != NULL)
+		{
+			if (strcmp(spill_de->d_name, ".") == 0 ||
+				strcmp(spill_de->d_name, "..") == 0)
+				continue;
+
+			if (strncmp(spill_de->d_name, "xid", 3) == 0)
+			{
+				sprintf(path, "pg_llog/%s/%s", logical_de->d_name,
+						spill_de->d_name);
+
+				if (unlink(path) != 0)
+					ereport(PANIC,
+							(errcode_for_file_access(),
+						  errmsg("could not remove xid data file \"%s\": %m",
+								 path)));
+			}
+			/* XXX: WARN? */
+		}
+		FreeDir(spill_dir);
+	}
+	FreeDir(logical_dir);
+}
+
+/*
+ * toast support
+ */
+
+/*
+ * copied stuff from tuptoaster.c. Perhaps there should be toast_internal.h?
+ */
+#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)	\
+do { \
+	varattrib_1b_e *attre = (varattrib_1b_e *) (attr); \
+	Assert(VARATT_IS_EXTERNAL(attre)); \
+	Assert(VARSIZE_EXTERNAL(attre) == sizeof(toast_pointer) + VARHDRSZ_EXTERNAL); \
+	memcpy(&(toast_pointer), VARDATA_EXTERNAL(attre), sizeof(toast_pointer)); \
+} while (0)
+
+#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) \
+	((toast_pointer).va_extsize < (toast_pointer).va_rawsize - VARHDRSZ)
+
+/*
+ * Initialize per tuple toast reconstruction support.
+ */
+static void
+ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	HASHCTL		hash_ctl;
+
+	Assert(txn->toast_hash == NULL);
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+	hash_ctl.keysize = sizeof(Oid);
+	hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
+	hash_ctl.hash = tag_hash;
+	hash_ctl.hcxt = rb->context;
+	txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
+								  HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+}
+
+/*
+ * Per toast-chunk handling for toast reconstruction
+ *
+ * Appends a toast chunk so we can reconstruct it when the tuple "owning" the
+ * toasted Datum comes along.
+ */
+static void
+ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							  Relation relation, ReorderBufferChange *change)
+{
+	ReorderBufferToastEnt *ent;
+	bool		found;
+	int32		chunksize;
+	bool		isnull;
+	Pointer		chunk;
+	TupleDesc	desc = RelationGetDescr(relation);
+	Oid			chunk_id;
+	Oid			chunk_seq;
+
+	if (txn->toast_hash == NULL)
+		ReorderBufferToastInitHash(rb, txn);
+
+	Assert(IsToastRelation(relation));
+
+	chunk_id = DatumGetObjectId(fastgetattr(&change->newtuple->tuple, 1, desc, &isnull));
+	Assert(!isnull);
+	chunk_seq = DatumGetInt32(fastgetattr(&change->newtuple->tuple, 2, desc, &isnull));
+	Assert(!isnull);
+
+	ent = (ReorderBufferToastEnt *)
+		hash_search(txn->toast_hash,
+					(void *) &chunk_id,
+					HASH_ENTER,
+					&found);
+
+	if (!found)
+	{
+		Assert(ent->chunk_id == chunk_id);
+		ent->num_chunks = 0;
+		ent->last_chunk_seq = 0;
+		ent->size = 0;
+		ent->reconstructed = NULL;
+		dlist_init(&ent->chunks);
+
+		if (chunk_seq != 0)
+			elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0",
+				 chunk_seq, chunk_id);
+	}
+	else if (found && chunk_seq != ent->last_chunk_seq + 1)
+		elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d",
+			 chunk_seq, chunk_id, ent->last_chunk_seq + 1);
+
+	chunk = DatumGetPointer(fastgetattr(&change->newtuple->tuple, 3, desc, &isnull));
+	Assert(!isnull);
+
+	/* calculate size so we can allocate the right size at once later */
+	if (!VARATT_IS_EXTENDED(chunk))
+		chunksize = VARSIZE(chunk) - VARHDRSZ;
+	else if (VARATT_IS_SHORT(chunk))
+		/* could happen due to heap_form_tuple doing its thing */
+		chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
+	else
+		elog(ERROR, "unexpected type of toast chunk");
+
+	ent->size += chunksize;
+	ent->last_chunk_seq = chunk_seq;
+	ent->num_chunks++;
+	dlist_push_tail(&ent->chunks, &change->node);
+}
+
+/*
+ * Rejigger change->newtuple to point to in-memory toast tuples instead to
+ * on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).
+ *
+ * We cannot replace unchanged toast tuples though, so those will still point
+ * to on-disk toast data.
+ */
+static void
+ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
+						  Relation relation, ReorderBufferChange *change)
+{
+	TupleDesc	desc;
+	int			natt;
+	Datum	   *attrs;
+	bool	   *isnull;
+	bool	   *free;
+	HeapTuple	newtup;
+	Relation	toast_rel;
+	TupleDesc	toast_desc;
+	MemoryContext oldcontext;
+
+	/* no toast tuples changed */
+	if (txn->toast_hash == NULL)
+		return;
+
+	oldcontext = MemoryContextSwitchTo(rb->context);
+
+	/* we should only have toast tuples in an INSERT or UPDATE */
+	Assert(change->newtuple);
+
+	desc = RelationGetDescr(relation);
+
+	toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
+	toast_desc = RelationGetDescr(toast_rel);
+
+	/* should we allocate from stack instead? */
+	attrs = palloc0(sizeof(Datum) * desc->natts);
+	isnull = palloc0(sizeof(bool) * desc->natts);
+	free = palloc0(sizeof(bool) * desc->natts);
+
+	heap_deform_tuple(&change->newtuple->tuple, desc,
+					  attrs, isnull);
+
+	for (natt = 0; natt < desc->natts; natt++)
+	{
+		Form_pg_attribute attr = desc->attrs[natt];
+		ReorderBufferToastEnt *ent;
+		struct varlena *varlena;
+
+		/* va_rawsize is the size of the original datum -- including header */
+		struct varatt_external toast_pointer;
+		struct varatt_indirect redirect_pointer;
+		struct varlena *new_datum = NULL;
+		struct varlena *reconstructed;
+		dlist_iter	it;
+		Size		data_done = 0;
+
+		/* system columns aren't toasted */
+		if (attr->attnum < 0)
+			continue;
+
+		if (attr->attisdropped)
+			continue;
+
+		/* not a varlena datatype */
+		if (attr->attlen != -1)
+			continue;
+
+		/* no data */
+		if (isnull[natt])
+			continue;
+
+		/* ok, we know we have a toast datum */
+		varlena = (struct varlena *) DatumGetPointer(attrs[natt]);
+
+		/* no need to do anything if the tuple isn't external */
+		if (!VARATT_IS_EXTERNAL(varlena))
+			continue;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena);
+
+		/*
+		 * check whether the toast tuple changed, replace if so.
+		 */
+		ent = (ReorderBufferToastEnt *)
+			hash_search(txn->toast_hash,
+						(void *) &toast_pointer.va_valueid,
+						HASH_FIND,
+						NULL);
+		if (ent == NULL)
+			continue;
+
+		new_datum =
+			(struct varlena *) palloc0(INDIRECT_POINTER_SIZE);
+
+		free[natt] = true;
+
+		reconstructed = palloc0(toast_pointer.va_rawsize);
+
+		ent->reconstructed = reconstructed;
+
+		/* stitch toast tuple back together from its parts */
+		dlist_foreach(it, &ent->chunks)
+		{
+			bool		isnull;
+			ReorderBufferTupleBuf *tup =
+			dlist_container(ReorderBufferChange, node, it.cur)->newtuple;
+			Pointer		chunk =
+			DatumGetPointer(fastgetattr(&tup->tuple, 3, toast_desc, &isnull));
+
+			Assert(!isnull);
+			Assert(!VARATT_IS_EXTERNAL(chunk));
+			Assert(!VARATT_IS_SHORT(chunk));
+
+			memcpy(VARDATA(reconstructed) + data_done,
+				   VARDATA(chunk),
+				   VARSIZE(chunk) - VARHDRSZ);
+			data_done += VARSIZE(chunk) - VARHDRSZ;
+		}
+		Assert(data_done == toast_pointer.va_extsize);
+
+		/* make sure its marked as compressed or not */
+		if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
+			SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
+		else
+			SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
+
+		memset(&redirect_pointer, 0, sizeof(redirect_pointer));
+		redirect_pointer.pointer = reconstructed;
+
+		SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
+		memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
+			   sizeof(redirect_pointer));
+
+		attrs[natt] = PointerGetDatum(new_datum);
+	}
+
+	/*
+	 * Build tuple in separate memory & copy tuple back into the tuplebuf
+	 * passed to the output plugin. We can't directly heap_fill_tuple() into
+	 * the tuplebuf because attrs[] will point back into the current content.
+	 */
+	newtup = heap_form_tuple(desc, attrs, isnull);
+	Assert(change->newtuple->tuple.t_len <= MaxHeapTupleSize);
+	Assert(&change->newtuple->header == change->newtuple->tuple.t_data);
+
+	memcpy(change->newtuple->tuple.t_data,
+		   newtup->t_data,
+		   newtup->t_len);
+	change->newtuple->tuple.t_len = newtup->t_len;
+
+	/*
+	 * free resources we won't further need, more persistent stuff will be
+	 * free'd in ReorderBufferToastReset().
+	 */
+	RelationClose(toast_rel);
+	pfree(newtup);
+	for (natt = 0; natt < desc->natts; natt++)
+	{
+		if (free[natt])
+			pfree(DatumGetPointer(attrs[natt]));
+	}
+	pfree(attrs);
+	pfree(free);
+	pfree(isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * Free all resources allocated for toast reconstruction.
+ */
+static void
+ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
+{
+	HASH_SEQ_STATUS hstat;
+	ReorderBufferToastEnt *ent;
+
+	if (txn->toast_hash == NULL)
+		return;
+
+	/* sequentially walk over the hash and free everything */
+	hash_seq_init(&hstat, txn->toast_hash);
+	while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
+	{
+		dlist_mutable_iter it;
+
+		if (ent->reconstructed != NULL)
+			pfree(ent->reconstructed);
+
+		dlist_foreach_modify(it, &ent->chunks)
+		{
+			ReorderBufferChange *change =
+			dlist_container(ReorderBufferChange, node, it.cur);
+
+			dlist_delete(&change->node);
+			ReorderBufferReturnChange(rb, change);
+		}
+	}
+
+	hash_destroy(txn->toast_hash);
+	txn->toast_hash = NULL;
+}
+
+
+/*
+ * Visibility support routines
+ */
+
+/*-------------------------------------------------------------------------
+ * Lookup actual cmin/cmax values during timetravel access. We can't always
+ * rely on stored cmin/cmax values because of two scenarios:
+ *
+ * * A tuple got changed multiple times during a single transaction and thus
+ *	 has got a combocid. Combocid's are only valid for the duration of a single
+ *	 transaction.
+ * * A tuple with a cmin but no cmax (and thus no combocid) got deleted/updated
+ *	 in another transaction than the one which created it which we are looking
+ *	 at right now. As only one of cmin, cmax or combocid is actually stored in
+ *	 the heap we don't have access to the the value we need anymore.
+ *
+ * To resolve those problems we have a per-transaction hash of (cmin, cmax)
+ * tuples keyed by (relfilenode, ctid) which contains the actual (cmin, cmax)
+ * values. That also takes care of combocids by simply not caring about them at
+ * all. As we have the real cmin/cmax values thats enough.
+ *
+ * As we only care about catalog tuples here the overhead of this hashtable
+ * should be acceptable.
+ * -------------------------------------------------------------------------
+ */
+extern bool
+ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
+							  HeapTuple htup, Buffer buffer,
+							  CommandId *cmin, CommandId *cmax)
+{
+	ReorderBufferTupleCidKey key;
+	ReorderBufferTupleCidEnt *ent;
+	ForkNumber	forkno;
+	BlockNumber blockno;
+
+	/* be careful about padding */
+	memset(&key, 0, sizeof(key));
+
+	Assert(!BufferIsLocal(buffer));
+
+	/*
+	 * get relfilenode from the buffer, no convenient way to access it other
+	 * than that.
+	 */
+	BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
+
+	/* tuples can only be in the main fork */
+	Assert(forkno == MAIN_FORKNUM);
+	Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
+
+	ItemPointerCopy(&htup->t_self,
+					&key.tid);
+
+	ent = (ReorderBufferTupleCidEnt *)
+		hash_search(tuplecid_data,
+					(void *) &key,
+					HASH_FIND,
+					NULL);
+
+	if (ent == NULL)
+		return false;
+
+	if (cmin)
+		*cmin = ent->cmin;
+	if (cmax)
+		*cmax = ent->cmax;
+	return true;
+}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
new file mode 100644
index 0000000..6547e3f
--- /dev/null
+++ b/src/backend/replication/logical/snapbuild.c
@@ -0,0 +1,1581 @@
+/*-------------------------------------------------------------------------
+ *
+ * snapbuild.c
+ *
+ *	  Support for building timetravel snapshots based on the contents of the
+ *	  WAL which then can be used to decode the contents of the WAL.
+ *
+ * NOTES:
+ *
+ * We build snapshots which can *only* be used to read catalog contents by
+ * reading and interpreting the WAL stream. The aim is to build a snapshot that
+ * behaves the same as a freshly taken MVCC snapshot would have at the time the
+ * XLogRecord was generated.
+ *
+ * To build the snapshots we reuse the infrastructure built for hot
+ * standby. The snapshots we build look different than HS' because we have
+ * different needs. To successfully decode data from the WAL we only need to
+ * access catalogs/(sys|rel|cat)cache, not the actual user tables since the
+ * data we decode is contained in the WAL records. Also, our snapshots need to
+ * be different in comparison to normal MVCC ones because in contrast to those
+ * we cannot fully rely on the clog and pg_subtrans for information about
+ * committed transactions because they might commit in the future from the POV
+ * of the wal entry we're currently decoding.
+ *
+ * As the percentage of transactions modifying the catalog normally is fairly
+ * small in comparisons to ones only manipulating user data we keep track of
+ * the committed catalog modifying ones inside (xmin, xmax) instead of keeping
+ * track of all running transactions like its done in a normal snapshot. Note
+ * that we're generally only looking at transactions that have acquired an
+ * xid. That is we keep a list of transactions between snapshot->(xmin, xmax)
+ * that we consider committed, everything else is considered aborted/in
+ * progress. That also allows us not to care about subtransactions before they
+ * have committed which means this modules, in contrast to HS, doesn't have to
+ * care about suboverflowed subtransactions and similar.
+ *
+ * One complexity of doing this is that to e.g. handle mixed DDL/DML
+ * transactions we need Snapshots that see intermediate versions of the catalog
+ * in a transaction. During normal operation this is achieved by using
+ * CommandIds/cmin/cmax. The problem with that however is that for space
+ * efficiency reasons only one value of that is stored (c.f. combocid.c). Since
+ * Combocids are only available in memory we log additional information which
+ * allows us to get the original (cmin, cmax) pair during visibility
+ * checks. Check the reorderbuffer.c's comment above
+ * ResolveCminCmaxDuringDecoding() for details.
+ *
+ * To facilitate all this we need our own visibility routine, as the normal
+ * ones are optimized for different usecases. To make sure no unexpected
+ * database access bypassing our special snapshot is possible - which would
+ * possibly load invalid data into caches - we temporarily overload the
+ * .satisfies methods of the usual snapshots while doing timetravel.
+ *
+ * To replace the normal catalog snapshots with timetravel ones use the
+ * SetupDecodingSnapshots and RevertFromDecodingSnapshots functions.
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/snapbuild.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "miscadmin.h"
+
+#include "access/heapam_xlog.h"
+#include "access/transam.h"
+#include "access/xact.h"
+
+#include "replication/logical.h"
+#include "replication/reorderbuffer.h"
+#include "replication/snapbuild.h"
+
+#include "utils/builtins.h"
+#include "utils/catcache.h" /* FIXME: Use */
+#include "utils/memutils.h"
+#include "utils/snapshot.h"
+#include "utils/snapmgr.h"
+#include "utils/tqual.h"
+
+#include "storage/block.h"		/* debugging output */
+#include "storage/fd.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/standby.h"
+
+typedef struct SnapBuild
+{
+	/* how far are we along building our first full snapshot */
+	SnapBuildState state;
+
+	/* private memory context used to allocate memory for this module. */
+	MemoryContext context;
+
+	/* all transactions < than this have committed/aborted */
+	TransactionId xmin;
+
+	/* all transactions >= than this are uncommitted */
+	TransactionId xmax;
+
+	/*
+	 * Don't replay commits from an LSN <= this LSN. This can be set
+	 * externally but it will also be advanced (never retreat) from within
+	 * snapbuild.c.
+	 */
+	XLogRecPtr	transactions_after;
+
+	/*
+	 * Don't start decoding WAL until the "xl_running_xacts" information
+	 * indicates there are no running xids with a xid smaller than this.
+	 */
+	TransactionId initial_xmin_horizon;
+
+	/*
+	 * Snapshot thats valid to see all currently committed transactions that
+	 * see catalog modifications.
+	 */
+	Snapshot	snapshot;
+
+	/*
+	 * LSN of the last location we are sure a snapshot has been serialized to.
+	 */
+	XLogRecPtr	last_serialized_snapshot;
+
+	ReorderBuffer *reorder;
+
+	/*
+	 * Information about initially running transactions
+	 *
+	 * When we start building a snapshot there already may be transactions in
+	 * progress.  Those are stored in running.xip.	We don't have enough
+	 * information about those to decode their contents, so until they are
+	 * finished (xcnt=0) we cannot switch to a CONSISTENT state.
+	 */
+	struct
+	{
+		/*
+		 * As long as running.xcnt all XIDs < running.xmin and > running.xmax
+		 * have to be checked whether they still are running.
+		 */
+		TransactionId xmin;
+		TransactionId xmax;
+
+		size_t		xcnt;		/* number of used xip entries */
+		size_t		xcnt_space; /* allocated size of xip */
+		TransactionId *xip;		/* running xacts array, xidComparator-sorted */
+	}			running;
+
+	/*
+	 * Array of transactions which could have catalog changes that committed
+	 * between xmin and xmax
+	 */
+	struct
+	{
+		/* number of committed transactions */
+		size_t		xcnt;
+
+		/* available space for committed transactions */
+		size_t		xcnt_space;
+
+		/*
+		 * Until we reach a CONSISTENT state, we record commits of all
+		 * transactions, not just the catalog changing ones. Record when that
+		 * changes so we know we cannot export a snapshot safely anymore.
+		 */
+		bool		includes_all_transactions;
+
+		/*
+		 * Array of committed transactions that have modified the catalog.
+		 *
+		 * As this array is frequently modified we do *not* keep it in
+		 * xidComparator order. Instead we sort the array when building &
+		 * distributing a snapshot.
+		 *
+		 * XXX: That doesn't seem to be good reasoning anymore. Everytime we
+		 * add something here after becoming consistent will also require
+		 * distributing a snapshot. Storing them sorted would potentially make
+		 * it easier to purge as well (but more complicated wrt wraparound?).
+		 */
+		TransactionId *xip;
+	}			committed;
+} SnapBuild;
+
+/*
+ * Starting a transaction -- which we need to do while exporting a snapshot --
+ * removes knowledge about the previously used resowner, so we save it here.
+ */
+ResourceOwner SavedResourceOwnerDuringExport = NULL;
+
+/* transaction state manipulation functions */
+static void SnapBuildEndTxn(SnapBuild *builder, TransactionId xid);
+
+/* ->running manipulation */
+static bool SnapBuildTxnIsRunning(SnapBuild *builder, TransactionId xid);
+
+/* ->committed manipulation */
+static void SnapBuildPurgeCommittedTxn(SnapBuild *builder);
+
+/* snapshot building/manipulation/distribution functions */
+static Snapshot SnapBuildBuildSnapshot(SnapBuild *builder, TransactionId xid);
+
+static void SnapBuildFreeSnapshot(Snapshot snap);
+
+static void SnapBuildSnapIncRefcount(Snapshot snap);
+
+static void SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn);
+
+/* xlog reading helper functions for SnapBuildProcessRecord */
+static bool SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running);
+
+/* serialization functions */
+static void SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn);
+static bool SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn);
+
+
+/*
+ * Allocate a new snapshot builder.
+ */
+SnapBuild *
+AllocateSnapshotBuilder(ReorderBuffer *reorder,
+						TransactionId xmin_horizon,
+						XLogRecPtr start_lsn)
+{
+	MemoryContext context;
+	MemoryContext oldcontext;
+	SnapBuild  *builder;
+
+	context = AllocSetContextCreate(TopMemoryContext,
+									"snapshot builder context",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	oldcontext = MemoryContextSwitchTo(context);
+
+	builder = palloc0(sizeof(SnapBuild));
+
+	builder->state = SNAPBUILD_START;
+	builder->context = context;
+	builder->reorder = reorder;
+	/* Other struct members initialized by zeroing, above */
+
+	/* builder->running is initialized by zeroing, above */
+
+	builder->committed.xcnt = 0;
+	builder->committed.xcnt_space = 128;		/* arbitrary number */
+	builder->committed.xip =
+		palloc0(builder->committed.xcnt_space * sizeof(TransactionId));
+	builder->committed.includes_all_transactions = true;
+	builder->committed.xip =
+		palloc0(builder->committed.xcnt_space * sizeof(TransactionId));
+	builder->initial_xmin_horizon = xmin_horizon;
+	builder->transactions_after = start_lsn;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return builder;
+}
+
+/*
+ * Free a snapshot builder.
+ */
+void
+FreeSnapshotBuilder(SnapBuild *builder)
+{
+	MemoryContext context = builder->context;
+
+	if (builder->snapshot)
+		SnapBuildFreeSnapshot(builder->snapshot);
+
+	if (builder->running.xip)
+		pfree(builder->running.xip);
+
+	if (builder->committed.xip)
+		pfree(builder->committed.xip);
+
+	pfree(builder);
+
+	MemoryContextDelete(context);
+}
+
+/*
+ * Free an unreferenced snapshot that has previously been built by us.
+ */
+static void
+SnapBuildFreeSnapshot(Snapshot snap)
+{
+	/* make sure we don't get passed an external snapshot */
+	Assert(snap->satisfies == HeapTupleSatisfiesMVCCDuringDecoding);
+
+	/* make sure nobody modified our snapshot */
+	Assert(snap->curcid == FirstCommandId);
+	Assert(!snap->suboverflowed);
+	Assert(!snap->takenDuringRecovery);
+	Assert(!snap->regd_count);
+
+	/* slightly more likely, so it's checked even without c-asserts */
+	if (snap->copied)
+		elog(ERROR, "can't free a copied snapshot");
+
+	if (snap->active_count)
+		elog(ERROR, "can't free an active snapshot");
+
+	pfree(snap);
+}
+
+/*
+ * In which state of snapshot building ar we?
+ */
+SnapBuildState
+SnapBuildCurrentState(SnapBuild *builder)
+{
+	return builder->state;
+}
+
+/*
+ * Should the contents of transaction ending at 'ptr' be decoded?
+ */
+bool
+SnapBuildXactNeedsSkip(SnapBuild *builder, XLogRecPtr ptr)
+{
+	return ptr <= builder->transactions_after;
+}
+
+/*
+ * Increase refcount of a snapshot.
+ *
+ * This is used when handing out a snapshot to some external resource or when
+ * adding a Snapshot as builder->snapshot.
+ */
+static void
+SnapBuildSnapIncRefcount(Snapshot snap)
+{
+	snap->active_count++;
+}
+
+/*
+ * Decrease refcount of a snapshot and free if the refcount reaches zero.
+ *
+ * Externally visible so external resources that have been handed an IncRef'ed
+ * Snapshot can free it easily.
+ */
+void
+SnapBuildSnapDecRefcount(Snapshot snap)
+{
+	/* make sure we don't get passed an external snapshot */
+	Assert(snap->satisfies == HeapTupleSatisfiesMVCCDuringDecoding);
+
+	/* make sure nobody modified our snapshot */
+	Assert(snap->curcid == FirstCommandId);
+	Assert(!snap->suboverflowed);
+	Assert(!snap->takenDuringRecovery);
+	Assert(!snap->regd_count);
+
+	Assert(snap->active_count);
+
+	/* slightly more likely, so its checked even without casserts */
+	if (snap->copied)
+		elog(ERROR, "can't free a copied snapshot");
+
+	snap->active_count--;
+	if (!snap->active_count)
+		SnapBuildFreeSnapshot(snap);
+}
+
+/*
+ * Build a new snapshot, based on currently committed catalog-modifying
+ * transactions.
+ *
+ * In-progress transactions with catalog access are *not* allowed to modify
+ * these snapshots; they have to copy them and fill in appropriate ->curcid and
+ * ->subxip/subxcnt values.
+ */
+static Snapshot
+SnapBuildBuildSnapshot(SnapBuild *builder, TransactionId xid)
+{
+	Snapshot	snapshot;
+	Size		ssize;
+
+	Assert(builder->state >= SNAPBUILD_FULL_SNAPSHOT);
+
+	ssize = sizeof(SnapshotData)
+		+ sizeof(TransactionId) * builder->committed.xcnt
+		+ sizeof(TransactionId) * 1 /* toplevel xid */ ;
+
+	snapshot = MemoryContextAllocZero(builder->context, ssize);
+
+	snapshot->satisfies = HeapTupleSatisfiesMVCCDuringDecoding;
+
+	/*
+	 * We misuse the original meaning of SnapshotData's xip and subxip fields
+	 * to make the more fitting for our needs.
+	 *
+	 * In the 'xip' array we store transactions that have to be treated as
+	 * committed. Since we will only ever look at tuples from transactions
+	 * that have modified the catalog its more efficient to store those few
+	 * that exist between xmin and xmax (frequently there are none).
+	 *
+	 * Snapshots that are used in transactions that have modified the catalog
+	 * also use the 'subxip' array to store their toplevel xid and all the
+	 * subtransaction xids so we can recognize when we need to treat rows as
+	 * visible that are not in xip but still need to be visible. Subxip only
+	 * gets filled when the transaction is copied into the context of a
+	 * catalog modifying transaction since we otherwise share a snapshot
+	 * between transactions. As long as a txn hasn't modified the catalog it
+	 * doesn't need to treat any uncommitted rows as visible, so there is no
+	 * need for those xids.
+	 *
+	 * Both arrays are qsort'ed so that we can use bsearch() on them.
+	 *
+	 * XXX: Do we want extra fields instead of misusing existing ones instead?
+	 */
+	Assert(TransactionIdIsNormal(builder->xmin));
+	Assert(TransactionIdIsNormal(builder->xmax));
+
+	snapshot->xmin = builder->xmin;
+	snapshot->xmax = builder->xmax;
+
+	/* store all transactions to be treated as committed by this snapshot */
+	snapshot->xip =
+		(TransactionId *) ((char *) snapshot + sizeof(SnapshotData));
+	snapshot->xcnt = builder->committed.xcnt;
+	memcpy(snapshot->xip,
+		   builder->committed.xip,
+		   builder->committed.xcnt * sizeof(TransactionId));
+
+	/* sort so we can bsearch() */
+	qsort(snapshot->xip, snapshot->xcnt, sizeof(TransactionId), xidComparator);
+
+	/*
+	 * Initially, subxip is empty, i.e. it's a snapshot to be used by
+	 * transactions that don't modify the catalog. Will be filled by
+	 * ReorderBufferCopySnap() if necessary.
+	 */
+	snapshot->subxcnt = 0;
+	snapshot->subxip = NULL;
+
+	snapshot->suboverflowed = false;
+	snapshot->takenDuringRecovery = false;
+	snapshot->copied = false;
+	snapshot->curcid = FirstCommandId;
+	snapshot->active_count = 0;
+	snapshot->regd_count = 0;
+
+	return snapshot;
+}
+
+/*
+ * Export a snapshot so it can be set in another session with SET TRANSACTION
+ * SNAPSHOT.
+ *
+ * For that we need to start a transaction in the current backend as the
+ * importing side checks whether the source transaction is still open to make
+ * sure the xmin horizon hasn't advanced since then.
+ *
+ * After that we convert a locally built snapshot into the normal variant
+ * understood by HeapTupleSatisfiesMVCC et al.
+ */
+const char *
+SnapBuildExportSnapshot(SnapBuild *builder)
+{
+	Snapshot	snap;
+	char	   *snapname;
+	TransactionId xid;
+	TransactionId *newxip;
+	int			newxcnt = 0;
+
+	elog(LOG, "building snapshot");
+
+	if (builder->state != SNAPBUILD_CONSISTENT)
+		elog(ERROR, "cannot export a snapshot before reaching a consistent state");
+
+	if (!builder->committed.includes_all_transactions)
+		elog(ERROR, "cannot export a snapshot, not all transactions are monitored anymore");
+
+	/* so we don't overwrite the existing value */
+	if (TransactionIdIsValid(MyPgXact->xmin))
+		elog(ERROR, "cannot export a snapshot when MyPgXact->xmin already is valid");
+
+	if (IsTransactionOrTransactionBlock())
+		elog(ERROR, "cannot export a snapshot from within a transaction");
+
+	if (SavedResourceOwnerDuringExport)
+		elog(ERROR, "can only export one snapshot at a time");
+
+	SavedResourceOwnerDuringExport = CurrentResourceOwner;
+
+	StartTransactionCommand();
+
+	Assert(!FirstSnapshotSet);
+
+	/* There doesn't seem to a nice API to set these */
+	XactIsoLevel = XACT_REPEATABLE_READ;
+	XactReadOnly = true;
+
+	snap = SnapBuildBuildSnapshot(builder, GetTopTransactionId());
+
+	/*
+	 * We know that snap->xmin is alive, enforced by the logical xmin
+	 * mechanism. Due to that we can do this without locks, we're only
+	 * changing our own value.
+	 */
+	MyPgXact->xmin = snap->xmin;
+
+	/* allocate in transaction context */
+	newxip = (TransactionId *)
+		palloc(sizeof(TransactionId) * GetMaxSnapshotXidCount());
+
+	/*
+	 * snapbuild.c builds transactions in an "inverted" manner, which means it
+	 * stores committed transactions in ->xip, not ones in progress. Build a
+	 * classical snapshot by marking all non-committed transactions as
+	 * in-progress. This can be expensive.
+	 */
+	for (xid = snap->xmin; NormalTransactionIdPrecedes(xid, snap->xmax);)
+	{
+		void	   *test;
+
+		/*
+		 * check whether transaction committed using the timetravel meaning of
+		 * ->xip
+		 */
+		test = bsearch(&xid, snap->xip, snap->xcnt,
+					   sizeof(TransactionId), xidComparator);
+
+		elog(DEBUG2, "checking xid %u.. %d (xmin %u, xmax %u)",
+			 xid, test == NULL, snap->xmin, snap->xmax);
+
+		if (test == NULL)
+		{
+			if (newxcnt >= GetMaxSnapshotXidCount())
+				elog(ERROR, "snapshot too large");
+
+			newxip[newxcnt++] = xid;
+
+			elog(DEBUG2, "treat %u as in-progress", xid);
+		}
+
+		TransactionIdAdvance(xid);
+	}
+
+	snap->xcnt = newxcnt;
+	snap->xip = newxip;
+
+	/*
+	 * now that we've built a plain snapshot, use the normal mechanisms for
+	 * exporting it
+	 */
+	snapname = ExportSnapshot(snap);
+
+	elog(LOG, "exported snapbuild snapshot: %s xcnt %u", snapname, snap->xcnt);
+	return snapname;
+}
+
+/*
+ * Reset a previously SnapBuildExportSnapshot()'ed snapshot if there is
+ * any. Aborts the previously started transaction and resets the resource owner
+ * back to it's original value.
+ */
+void
+SnapBuildClearExportedSnapshot()
+{
+	/* nothing exported, thats the usual case */
+	if (SavedResourceOwnerDuringExport == NULL)
+		return;
+
+	Assert(IsTransactionState());
+
+	/* make sure nothing  could have ever happened */
+	AbortCurrentTransaction();
+
+	CurrentResourceOwner = SavedResourceOwnerDuringExport;
+	SavedResourceOwnerDuringExport = NULL;
+}
+
+/*
+ * Handle the effects of a single heap change, appropriate to the current state
+ * of the snapshot builder and returns whether changes made at (xid, lsn) may
+ * be decoded.
+ */
+bool
+SnapBuildProcessChange(SnapBuild *builder, TransactionId xid, XLogRecPtr lsn)
+{
+	bool is_old_tx;
+
+	/*
+	 * We can't handle data in transactions if we haven't built a snapshot
+	 * yet, so don't store them.
+	 */
+	if (builder->state < SNAPBUILD_FULL_SNAPSHOT)
+		return false;
+
+	/*
+	 * No point in keeping track of changes in transactions that we don't have
+	 * enough information about to decode.
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT &&
+		SnapBuildTxnIsRunning(builder, xid))
+		return false;
+
+	is_old_tx = ReorderBufferIsXidKnown(builder->reorder, xid);
+
+	if (!is_old_tx || !ReorderBufferXidHasBaseSnapshot(builder->reorder, xid))
+	{
+		/* only build a new snapshot if we don't have a prebuilt one */
+		if (builder->snapshot == NULL)
+		{
+			builder->snapshot = SnapBuildBuildSnapshot(builder, xid);
+			/* inrease refcount for the snapshot builder */
+			SnapBuildSnapIncRefcount(builder->snapshot);
+		}
+
+		/* increase refcount for the transaction */
+		SnapBuildSnapIncRefcount(builder->snapshot);
+		ReorderBufferSetBaseSnapshot(builder->reorder, xid, lsn,
+									 builder->snapshot);
+	}
+
+	return true;
+}
+
+/*
+ * Do CommandId/ComboCid handling after reading a xl_heap_new_cid record. This
+ * implies that a transaction has done some for of write to system catalogs.
+ */
+void
+SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
+					   XLogRecPtr lsn, xl_heap_new_cid *xlrec)
+{
+	CommandId	cid;
+
+	/*
+	 * we only log new_cid's if a catalog tuple was modified, so
+	 * set transaction to timetravelling.
+	 */
+	ReorderBufferXidSetTimetravel(builder->reorder, xid,lsn);
+
+	ReorderBufferAddNewTupleCids(builder->reorder, xlrec->top_xid, lsn,
+								 xlrec->target.node, xlrec->target.tid,
+								 xlrec->cmin, xlrec->cmax,
+								 xlrec->combocid);
+
+	/* figure out new command id */
+	if (xlrec->cmin != InvalidCommandId &&
+		xlrec->cmax != InvalidCommandId)
+		cid = Max(xlrec->cmin, xlrec->cmax);
+	else if (xlrec->cmax != InvalidCommandId)
+		cid = xlrec->cmax;
+	else if (xlrec->cmin != InvalidCommandId)
+		cid = xlrec->cmin;
+	else
+	{
+		cid = InvalidCommandId;		/* silence compiler */
+		elog(ERROR, "broken arrow, no cid?");
+	}
+
+	/*
+	 * FIXME: potential race condition here: if multiple snapshots were running
+	 * & generating changes in the same transaction on the source side this
+	 * could be problematic. But this cannot happen for system catalogs, right?
+	 */
+	ReorderBufferAddNewCommandId(builder->reorder, xid, lsn, cid + 1);
+}
+
+/*
+ * Check whether `xid` is currently 'running'. Running transactions in our
+ * parlance are transactions which we didn't observe from the start so we can't
+ * properly decode them. They only exist after we freshly started from an
+ * < CONSISTENT snapshot.
+ */
+static bool
+SnapBuildTxnIsRunning(SnapBuild *builder, TransactionId xid)
+{
+	Assert(builder->state < SNAPBUILD_CONSISTENT);
+	Assert(TransactionIdIsValid(builder->running.xmin));
+	Assert(TransactionIdIsValid(builder->running.xmax));
+
+	if (builder->running.xcnt &&
+		NormalTransactionIdFollows(xid, builder->running.xmin) &&
+		NormalTransactionIdPrecedes(xid, builder->running.xmax))
+	{
+		TransactionId *search =
+		bsearch(&xid, builder->running.xip, builder->running.xcnt_space,
+				sizeof(TransactionId), xidComparator);
+
+		if (search != NULL)
+		{
+			Assert(*search == xid);
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Add a new Snapshot to all transactions we're decoding that currently are
+ * in-progress so they can see new catalog contents made by the transaction
+ * that just committed. This is necessary because those in-progress
+ * transactions will use the new catalog's contents from here on (at the very
+ * least everything they do needs to be compatible with newer catalog contents).
+ */
+static void
+SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn)
+{
+	dlist_iter	txn_i;
+	ReorderBufferTXN *txn;
+
+	/*
+	 * Iterate through all toplevel transactions. This can include
+	 * subtransactions which we just don't yet know to be that, but that's
+	 * fine, they will just get an unneccesary snapshot queued.
+	 */
+	dlist_foreach(txn_i, &builder->reorder->toplevel_by_lsn)
+	{
+		txn = dlist_container(ReorderBufferTXN, node, txn_i.cur);
+
+		Assert(TransactionIdIsValid(txn->xid));
+
+		/*
+		 * If we don't have a base snapshot yet, there are no changes in this
+		 * transaction which in turn implies we don't yet need a snapshot at
+		 * all. We'll add add a snapshot when the first change gets queued.
+		 *
+		 * XXX: is that fine if only a subtransaction has a base snapshot so
+		 * far?
+		 */
+		if (!ReorderBufferXidHasBaseSnapshot(builder->reorder, txn->xid))
+			continue;
+
+		elog(DEBUG2, "adding a new snapshot to %u at %X/%X",
+			 txn->xid, (uint32) (lsn >> 32), (uint32) lsn);
+
+		/* increase refcount for the transaction */
+		SnapBuildSnapIncRefcount(builder->snapshot);
+		ReorderBufferAddSnapshot(builder->reorder, txn->xid, lsn,
+								 builder->snapshot);
+	}
+}
+
+/*
+ * Keep track of a new catalog changing transaction that has committed.
+ */
+static void
+SnapBuildAddCommittedTxn(SnapBuild *builder, TransactionId xid)
+{
+	Assert(TransactionIdIsValid(xid));
+
+	if (builder->committed.xcnt == builder->committed.xcnt_space)
+	{
+		builder->committed.xcnt_space = builder->committed.xcnt_space * 2 + 1;
+
+		/* XXX: put in a limit here as a defense against bugs? */
+
+		elog(DEBUG1, "increasing space for committed transactions to %zu",
+			 builder->committed.xcnt_space);
+
+		builder->committed.xip = repalloc(builder->committed.xip,
+					builder->committed.xcnt_space * sizeof(TransactionId));
+	}
+
+	/*
+	 * XXX: It might make sense to keep the array sorted here instead of doing
+	 * it everytime we build a new snapshot. On the other hand this gets called
+	 * repeatedly when a transaction with subtransactions commits.
+	 */
+	builder->committed.xip[builder->committed.xcnt++] = xid;
+}
+
+/*
+ * Remove knowledge about transactions we treat as committed that are smaller
+ * than ->xmin. Those won't ever get checked via the ->commited array but via
+ * the clog machinery, so we don't need to waste memory on them.
+ */
+static void
+SnapBuildPurgeCommittedTxn(SnapBuild *builder)
+{
+	int			off;
+	TransactionId *workspace;
+	int			surviving_xids = 0;
+
+	/* not ready yet */
+	if (!TransactionIdIsNormal(builder->xmin))
+		return;
+
+	/* XXX: Neater algorithm? */
+	workspace =
+		MemoryContextAlloc(builder->context,
+						   builder->committed.xcnt * sizeof(TransactionId));
+
+	/* copy xids that still are interesting to workspace */
+	for (off = 0; off < builder->committed.xcnt; off++)
+	{
+		if (NormalTransactionIdPrecedes(builder->committed.xip[off],
+										builder->xmin))
+			;					/* remove */
+		else
+			workspace[surviving_xids++] = builder->committed.xip[off];
+	}
+
+	/* copy workspace back to persistent state */
+	memcpy(builder->committed.xip, workspace,
+		   surviving_xids * sizeof(TransactionId));
+
+	elog(DEBUG1, "purged committed transactions from %u to %u, xmin: %u, xmax: %u",
+		 (uint32) builder->committed.xcnt, (uint32) surviving_xids,
+		 builder->xmin, builder->xmax);
+	builder->committed.xcnt = surviving_xids;
+
+	pfree(workspace);
+}
+
+/*
+ * Common logic for SnapBuildAbortTxn and SnapBuildCommitTxn dealing with
+ * keeping track of the amount of running transactions.
+ */
+static void
+SnapBuildEndTxn(SnapBuild *builder, TransactionId xid)
+{
+	if (builder->state == SNAPBUILD_CONSISTENT)
+		return;
+
+	if (SnapBuildTxnIsRunning(builder, xid))
+	{
+		Assert(builder->running.xcnt > 0);
+
+		if (!--builder->running.xcnt)
+		{
+			/*
+			 * None of the originally running transaction is running anymore.
+			 * Due to that our incrementaly built snapshot now is complete.
+			 */
+			elog(LOG, "found consistent point due to SnapBuildEndTxn + running: %u", xid);
+			builder->state = SNAPBUILD_CONSISTENT;
+		}
+	}
+}
+
+/*
+ * Abort a transaction, throw away all state we kept
+ */
+void
+SnapBuildAbortTxn(SnapBuild *builder, TransactionId xid,
+				  int nsubxacts, TransactionId *subxacts)
+{
+	int			i;
+
+	for (i = 0; i < nsubxacts; i++)
+	{
+		TransactionId subxid = subxacts[i];
+
+		SnapBuildEndTxn(builder, subxid);
+	}
+
+	SnapBuildEndTxn(builder, xid);
+}
+
+/*
+ * Handle everything that needs to be done when a transaction commits
+ */
+void
+SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
+				   int nsubxacts, TransactionId *subxacts)
+{
+	int			nxact;
+
+	bool		forced_timetravel = false;
+	bool		sub_does_timetravel = false;
+	bool		top_does_timetravel = false;
+
+	TransactionId xmax = xid;
+
+	/*
+	 * If we couldn't observe every change of a transaction because it was
+	 * already running at the point we started to observe we have to assume it
+	 * made catalog changes.
+	 *
+	 * This has the positive benefit that we afterwards have enough
+	 * information to build an exportable snapshot thats usable by pg_dump et
+	 * al.
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT)
+	{
+		/* ensure that only commits after this are getting replayed */
+		if (builder->transactions_after < lsn)
+			builder->transactions_after = lsn;
+
+		/*
+		 * we could avoid treating !SnapBuildTxnIsRunning transactions as
+		 * timetravel ones, but we want to be able to export a snapshot when
+		 * we reached consistency.
+		 */
+		forced_timetravel = true;
+		elog(DEBUG1, "forced to assume catalog changes for xid %u because it was running to early", xid);
+	}
+
+	for (nxact = 0; nxact < nsubxacts; nxact++)
+	{
+		TransactionId subxid = subxacts[nxact];
+
+		/*
+		 * make sure txn is not tracked in running txn's anymore, switch state
+		 */
+		SnapBuildEndTxn(builder, subxid);
+
+		/*
+		 * If we're forcing timetravel we also need accurate subtransaction
+		 * status.
+		 */
+		if (forced_timetravel)
+		{
+			SnapBuildAddCommittedTxn(builder, subxid);
+			if (NormalTransactionIdFollows(subxid, xmax))
+				xmax = subxid;
+		}
+
+		/*
+		 * add subtransaction to base snapshot, we don't distinguish to
+		 * toplevel transactions there.
+		 */
+		else if (ReorderBufferXidDoesTimetravel(builder->reorder, subxid))
+		{
+			sub_does_timetravel = true;
+
+			elog(DEBUG1, "found subtransaction %u:%u with catalog changes.",
+				 xid, subxid);
+
+			SnapBuildAddCommittedTxn(builder, subxid);
+
+			if (NormalTransactionIdFollows(subxid, xmax))
+				xmax = subxid;
+		}
+	}
+
+	/*
+	 * make sure txn is not tracked in running txn's anymore, switch state
+	 */
+	SnapBuildEndTxn(builder, xid);
+
+	if (forced_timetravel)
+	{
+		elog(DEBUG1, "forced transaction %u to do timetravel.", xid);
+
+		SnapBuildAddCommittedTxn(builder, xid);
+	}
+	/* add toplevel transaction to base snapshot */
+	else if (ReorderBufferXidDoesTimetravel(builder->reorder, xid))
+	{
+		elog(DEBUG1, "found top level transaction %u, with catalog changes!",
+			 xid);
+
+		top_does_timetravel = true;
+		SnapBuildAddCommittedTxn(builder, xid);
+	}
+	else if (sub_does_timetravel)
+	{
+		/* mark toplevel txn as timetravel as well */
+		SnapBuildAddCommittedTxn(builder, xid);
+	}
+
+	if (forced_timetravel || top_does_timetravel || sub_does_timetravel)
+	{
+		if (!TransactionIdIsValid(builder->xmax) ||
+			TransactionIdFollowsOrEquals(xmax, builder->xmax))
+		{
+			builder->xmax = xmax;
+			TransactionIdAdvance(builder->xmax);
+		}
+
+		if (builder->state < SNAPBUILD_FULL_SNAPSHOT)
+			return;
+
+		/* decrease the snapshot builder's refcount of the old snapshot */
+		if (builder->snapshot)
+			SnapBuildSnapDecRefcount(builder->snapshot);
+
+		builder->snapshot = SnapBuildBuildSnapshot(builder, xid);
+
+		/* refcount of the snapshot builder for the new snapshot */
+		SnapBuildSnapIncRefcount(builder->snapshot);
+
+		/* add a new SnapshotNow to all currently running transactions */
+		SnapBuildDistributeNewCatalogSnapshot(builder, lsn);
+	}
+	else
+	{
+		/* record that we cannot export a general snapshot anymore */
+		builder->committed.includes_all_transactions = false;
+	}
+}
+
+
+/* -----------------------------------
+ * Snapshot building functions dealing with xlog records
+ * -----------------------------------
+ */
+void
+SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
+{
+	ReorderBufferTXN *txn;
+
+	if (builder->state < SNAPBUILD_CONSISTENT)
+	{
+		/* returns false if there's no point in performing cleanup just yet */
+		if (!SnapBuildFindSnapshot(builder, lsn, running))
+			return;
+	}
+	else
+	{
+		SnapBuildSerialize(builder, lsn);
+	}
+
+	/*
+	 * update range of interesting xids. We don't increase ->xmax because once
+	 * we are in a consistent state we can do that ourselves and much more
+	 * efficiently so because we only need to do it for catalog transactions.
+	 */
+	builder->xmin = running->oldestRunningXid;
+
+	/*
+	 * xmax can be lower than xmin here because we only increase xmax when we
+	 * hit a transaction with catalog changes. While odd looking, its correct
+	 * and actually more efficient this way since we hit fast paths in tqual.c.
+	 */
+
+	/* Remove transactions we don't need to keep track off anymore */
+	SnapBuildPurgeCommittedTxn(builder);
+
+	elog(DEBUG1, "xmin: %u, xmax: %u, oldestrunning: %u",
+		 builder->xmin, builder->xmax,
+		 running->oldestRunningXid);
+
+	/*
+	 * inrease shared memory state, so vacuum can work on tuples we prevent
+	 * from being pruned till now.
+	 */
+	IncreaseLogicalXminForSlot(lsn, running->oldestRunningXid);
+
+	/*
+	 * Also tell the slot where we can restart decoding from. We don't want to
+	 * do that after every commit because changing that implies an fsync of the
+	 * logical slot's state file, so we only do it everytime we see a running
+	 * xacts record.
+	 *
+	 * Do so by looking for the oldest in progress transaction (determined by
+	 * the first LSN of any of its relevant records). Every transaction
+	 * remembers the last location we stored the snapshot to disk before its
+	 * beginning. That point is where we can restart from.
+	 */
+
+	/*
+	 * Can't know about a serialized snapshot's location if we're not
+	 * consistent
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT)
+		return;
+
+	txn = ReorderBufferGetOldestTXN(builder->reorder);
+
+	/*
+	 * oldest ongoing txn might have started when we didn't yet serialize
+	 * anything because we hadn't reached a consistent state yet.
+	 */
+	if (txn != NULL && txn->restart_decoding_lsn != InvalidXLogRecPtr)
+		IncreaseRestartDecodingForSlot(lsn, txn->restart_decoding_lsn);
+
+	/*
+	 * No in-progress transaction, can reuse the last serialized snapshot if we
+	 * have one.
+	 */
+	else if (txn == NULL &&
+			 builder->reorder->current_restart_decoding_lsn != InvalidXLogRecPtr &&
+			 builder->last_serialized_snapshot != InvalidXLogRecPtr)
+		IncreaseRestartDecodingForSlot(lsn, builder->last_serialized_snapshot);
+}
+
+
+/*
+ * Build the start of a snapshot that's capable of decoding the catalog. Helper
+ * function for SnapBuildProcessRunningXacts() while we're not yet consistent.
+ *
+ * Returns true if there is a point in performing internal maintenance/cleanup
+ * using the xl_running_xacts record.
+ */
+static bool
+SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
+{
+	/* ---
+	 * Build catalog decoding snapshot incrementally using information about
+	 * the currently running transactions. There are several ways to do that:
+
+	 * a) There were no running transactions when the xl_running_xacts record
+	 *    was inserted, jump to CONSISTENT immediately. We might find such a
+	 *    state we were waiting for b) and c).
+
+	 * b) Wait for all toplevel transactions that were running to end. We
+	 *    simply track the number of in-progress toplevel transactions and
+	 *    lower it whenever one commits or aborts. When that number
+	 *    (builder->running.xcnt) reaches zero, we can go from FULL_SNAPSHOT to
+	 *    CONSISTENT.
+	 *	  NB: We need to search running.xip when seeing a transaction's end to
+	 *    make sure it's a toplevel transaction and it's been one of the
+	 *    intially running ones.
+	 *	  Interestingly, in contrast to HS this allows us not to care about
+	 *	  subtransactions - and by extension suboverflowed xl_running_xacts -
+	 *	  at all.
+	 *
+	 * c) This (in a previous run) or another decoding slot serialized a
+	 *    snapshot to disk that we can use.
+	 * ---
+	 */
+
+	/*
+	 * xl_running_xact record is older than what we can use, we might not have
+	 * all necessary catalog rows anymore.
+	 */
+	if (TransactionIdIsNormal(builder->initial_xmin_horizon) &&
+		NormalTransactionIdPrecedes(running->oldestRunningXid,
+									builder->initial_xmin_horizon))
+	{
+		elog(LOG, "skipping snapshot at %X/%X due to initial xmin horizon of %u vs the snapshot's %u",
+			 (uint32) (lsn >> 32), (uint32) lsn,
+			 builder->initial_xmin_horizon, running->oldestRunningXid);
+		return true;
+	}
+
+	/*
+	 * a) No transaction were running, we can jump to consistent.
+	 *
+	 * NB: We might have already started to incrementally assemble a snapshot,
+	 * so we need to be careful to deal with that.
+	 */
+	if (running->xcnt == 0)
+	{
+		if (builder->transactions_after == InvalidXLogRecPtr ||
+			builder->transactions_after < lsn)
+			builder->transactions_after = lsn;
+
+		builder->xmin = running->oldestRunningXid;
+		builder->xmax = running->latestCompletedXid;
+		TransactionIdAdvance(builder->xmax);
+
+		Assert(TransactionIdIsNormal(builder->xmin));
+		Assert(TransactionIdIsNormal(builder->xmax));
+
+		/* no transactions running now */
+		builder->running.xcnt = 0;
+		builder->running.xmin = InvalidTransactionId;
+		builder->running.xmax = InvalidTransactionId;
+
+		/*
+		 * FIXME: abort everything we have stored about running transactions,
+		 * relevant e.g. after a crash.
+		 */
+		builder->state = SNAPBUILD_CONSISTENT;
+
+		elog(LOG, "found initial snapshot (xmin %u) due to running xacts with xcnt == 0",
+			 builder->xmin);
+
+		return false;
+	}
+	/* c) valid on disk state */
+	else if (SnapBuildRestore(builder, lsn))
+	{
+		/* there won't be any state to cleanup */
+		return false;
+	}
+
+	/*
+	 * b) first encounter of a useable xl_running_xacts record. If we had found
+	 * one earlier we would either track running transactions
+	 * (i.e. builder->running.xcnt != 0) or be consistent (this function
+	 * wouldn't get called)..
+	 */
+	else if (!builder->running.xcnt)
+	{
+		/*
+		 * We only care about toplevel xids as those are the ones we definitely
+		 * see in the wal stream. As snapbuild.c tracks committed instead of
+		 * running transactions we don't need to know anything about
+		 * uncommitted subtransactions.
+		 */
+		builder->xmin = running->oldestRunningXid;
+		builder->xmax = running->latestCompletedXid;
+		TransactionIdAdvance(builder->xmax);
+
+		/* so we can safely use the faster comparisons */
+		Assert(TransactionIdIsNormal(builder->xmin));
+		Assert(TransactionIdIsNormal(builder->xmax));
+
+		builder->running.xcnt = running->xcnt;
+		builder->running.xcnt_space = running->xcnt;
+		builder->running.xip =
+			MemoryContextAlloc(builder->context,
+							builder->running.xcnt * sizeof(TransactionId));
+		memcpy(builder->running.xip, running->xids,
+			   builder->running.xcnt * sizeof(TransactionId));
+
+		/* sort so we can do a binary search */
+		qsort(builder->running.xip, builder->running.xcnt,
+			  sizeof(TransactionId), xidComparator);
+
+		builder->running.xmin = builder->running.xip[0];
+		builder->running.xmax = builder->running.xip[running->xcnt - 1];
+
+		/* makes comparisons cheaper later */
+		TransactionIdRetreat(builder->running.xmin);
+		TransactionIdAdvance(builder->running.xmax);
+
+		builder->state = SNAPBUILD_FULL_SNAPSHOT;
+
+		elog(LOG, "found initial snapshot (xmin %u) due to running xacts, %u xacts need to finish",
+			 builder->xmin, (uint32) builder->running.xcnt);
+
+		/* nothing could have built up so far */
+		return false;
+	}
+
+	/*
+	 * We already started to track running xacts and need to wait for all
+	 * in-progress ones to finish. We fall through to the normal processing of
+	 * records so incremental cleanup can be performed.
+	 */
+	return true;
+}
+
+
+/* -----------------------------------
+ * Snapshot serialization support
+ * -----------------------------------
+ */
+
+/*
+ * We store current state of struct SnapBuild on disk in the following manner:
+ *
+ * struct SnapBuildOnDisk;
+ * TransactionId * running.xcnt_space;
+ * TransactionId * committed.xcnt; (*not xcnt_space*)
+ *
+ */
+typedef struct SnapBuildOnDisk
+{
+	uint32		magic;
+	/* how large is the SnapBuildOnDisk including all data in state */
+	Size		size;
+	SnapBuild	builder;
+
+	/* XXX: Should we store a CRC32? */
+
+	/* variable amount of TransactionId's */
+} SnapBuildOnDisk;
+
+#define SNAPBUILD_MAGIC 0x51A1E001
+
+/*
+ * Store/Load a snapshot from disk, depending on the snapshot builder's state.
+ *
+ * Supposed to be used by external (i.e. not snapbuild.c) code that just reada
+ * record that's a potential location for a serialized snapshot.
+ */
+void
+SnapBuildSerializationPoint(SnapBuild *builder, XLogRecPtr lsn)
+{
+	if (builder->state < SNAPBUILD_CONSISTENT)
+		SnapBuildRestore(builder, lsn);
+	else
+		SnapBuildSerialize(builder, lsn);
+}
+
+/*
+ * Serialize the snapshot 'builder' at the location 'lsn' if it hasn't already
+ * been done by another decoding process.
+ */
+static void
+SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn)
+{
+	Size		needed_size;
+	SnapBuildOnDisk *ondisk;
+	char	   *ondisk_c;
+	int			fd;
+	char		tmppath[MAXPGPATH];
+	char		path[MAXPGPATH];
+	int			ret;
+	struct stat stat_buf;
+
+	needed_size = sizeof(SnapBuildOnDisk) +
+		sizeof(TransactionId) * builder->running.xcnt_space +
+		sizeof(TransactionId) * builder->committed.xcnt;
+
+	Assert(lsn != InvalidXLogRecPtr);
+	Assert(builder->last_serialized_snapshot == InvalidXLogRecPtr ||
+		   builder->last_serialized_snapshot <= lsn);
+
+	/*
+	 * no point in serializing if we cannot continue to work immediately after
+	 * restoring the snapshot
+	 */
+	if (builder->state < SNAPBUILD_CONSISTENT)
+		return;
+
+	/*
+	 * FIXME: Timeline handling/naming.
+	 */
+
+	/*
+	 * first check whether some other backend already has written the snapshot
+	 * for this LSN. It's perfectly fine if there's none, so we accept ENOENT
+	 * as a valid state. Everything else is an unexpected error.
+	 */
+	sprintf(path, "pg_llog/snapshots/%X-%X.snap",
+			(uint32) (lsn >> 32), (uint32) lsn);
+
+	ret = stat(path, &stat_buf);
+
+	if (ret != 0 && errno != ENOENT)
+		ereport(ERROR, (errmsg("could not stat snapbuild state file %s", path)));
+	else if (ret == 0)
+	{
+		/*
+		 * somebody else has already serialized to this point, don't overwrite
+		 * but remember location, so we don't need to read old data again.
+		 *
+		 * FIXME: Is it safe to set this as restartpoint below? While we can
+		 * see the file it's not guaranteed to persist after a crash...
+		 */
+		builder->last_serialized_snapshot = lsn;
+		goto out;
+	}
+
+	/*
+	 * there is an obvious race condition here between the time we stat(2) the
+	 * file and us writing the file. But we rename the file into place
+	 * atomically and all files created need to contain the same data anyway,
+	 * so this is perfectly fine, although a bit of a resource waste. Locking
+	 * seems like pointless complication.
+	 */
+	elog(DEBUG1, "serializing snapshot to %s", path);
+
+	/* to make sure only we will write to this tempfile, include pid */
+	sprintf(tmppath, "pg_llog/snapshots/%X-%X.snap.%u.tmp",
+			(uint32) (lsn >> 32), (uint32) lsn, MyProcPid);
+
+	/*
+	 * Unlink temporary file if it already exists, needs to have been before a
+	 * crash/error since we won't enter this function twice from within a
+	 * single decoding slot/backend and the temporary file contains the pid of
+	 * the current process.
+	 */
+	if (unlink(tmppath) != 0 && errno != ENOENT)
+		ereport(ERROR, (errmsg("could not unlink old snapbuild state file %s", path)));
+
+	ondisk = MemoryContextAllocZero(builder->context, needed_size);
+	ondisk_c = ((char *) ondisk) + sizeof(SnapBuildOnDisk);
+	ondisk->magic = SNAPBUILD_MAGIC;
+	ondisk->size = needed_size;
+
+	/* copy state per struct assignment, lalala lazy. */
+	ondisk->builder = *builder;
+
+	/* NULL-ify memory-only data */
+	ondisk->builder.context = NULL;
+	ondisk->builder.snapshot = NULL;
+	ondisk->builder.reorder = NULL;
+
+	/* copy running xacts */
+	memcpy(ondisk_c, builder->running.xip,
+		   sizeof(TransactionId) * builder->running.xcnt_space);
+	ondisk_c += sizeof(TransactionId) * builder->running.xcnt_space;
+
+	/* copy  committed xacts */
+	memcpy(ondisk_c, builder->committed.xip,
+		   sizeof(TransactionId) * builder->committed.xcnt);
+	ondisk_c += sizeof(TransactionId) * builder->committed.xcnt;
+
+	/* we have valid data now, open tempfile and write it there */
+	fd = OpenTransientFile(tmppath,
+						   O_CREAT | O_EXCL | O_WRONLY | PG_BINARY,
+						   S_IRUSR | S_IWUSR);
+	if (fd < 0)
+		ereport(ERROR, (errmsg("could not open snapbuild state file %s for writing: %m", path)));
+
+	if ((write(fd, ondisk, needed_size)) != needed_size)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not write to snapbuild state file \"%s\": %m",
+						tmppath)));
+	}
+
+	/*
+	 * fsync the file before renaming so that even if we crash after this we
+	 * have either a fully valid file or nothing.
+	 *
+	 * TODO: Do the fsync() via checkpoints/restartpoints, doing it here has
+	 * some noticeable overhead since it's performed synchronously during
+	 * decoding?
+	 */
+	if (pg_fsync(fd) != 0)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not fsync snapbuild state file \"%s\": %m",
+						tmppath)));
+	}
+
+	CloseTransientFile(fd);
+
+	/*
+	 * We may overwrite the work from some other backend, but that's ok, our
+	 * snapshot is valid as well.
+	 */
+	if (rename(tmppath, path) != 0)
+	{
+		ereport(PANIC,
+				(errcode_for_file_access(),
+				 errmsg("could not rename snapbuild state file from \"%s\" to \"%s\": %m",
+						tmppath, path)));
+	}
+
+	/* make sure we persist */
+	fsync_fname(path, false);
+	fsync_fname("pg_llog/snapshots", true);
+
+	/*
+	 * now there's no way we loose the dumped state anymore, remember
+	 * serialization point.
+	 */
+	builder->last_serialized_snapshot = lsn;
+
+out:
+	ReorderBufferSetRestartPoint(builder->reorder,
+								 builder->last_serialized_snapshot);
+}
+
+/*
+ * Restore a snapshot into 'builder' if previously one has been stored at the
+ * location indicated by 'lsn'. Returns true if successfull, false otherwise.
+ */
+static bool
+SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn)
+{
+	SnapBuildOnDisk ondisk;
+	int			fd;
+	char		path[MAXPGPATH];
+	Size		sz;
+
+	/* no point in loading a snapshot if we're already there */
+	if (builder->state == SNAPBUILD_CONSISTENT)
+		return false;
+
+	sprintf(path, "pg_llog/snapshots/%X-%X.snap",
+			(uint32) (lsn >> 32), (uint32) lsn);
+
+	fd = OpenTransientFile(path, O_RDONLY | PG_BINARY, 0);
+
+	elog(LOG, "restoring snapbuild state from %s", path);
+
+	if (fd < 0 && errno == ENOENT)
+		return false;
+	else if (fd < 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open snapbuild state file %s", path)));
+
+	elog(LOG, "really restoring from %s", path);
+
+	/* read statically sized portion of snapshot */
+	if (read(fd, &ondisk, sizeof(ondisk)) != sizeof(ondisk))
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not read snapbuild file \"%s\": %m",
+						path)));
+	}
+
+	if (ondisk.magic != SNAPBUILD_MAGIC)
+		ereport(ERROR, (errmsg("snapbuild state file has wrong magic %u instead of %u",
+							   ondisk.magic, SNAPBUILD_MAGIC)));
+
+	/* restore running xact information */
+	sz = sizeof(TransactionId) * ondisk.builder.running.xcnt_space;
+	ondisk.builder.running.xip = MemoryContextAlloc(builder->context, sz);
+	if (read(fd, ondisk.builder.running.xip, sz) != sz)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+		errmsg("could not read running xacts from snapbuild file \"%s\": %m",
+			   path)));
+	}
+
+	/* restore running xact information */
+	sz = sizeof(TransactionId) * ondisk.builder.committed.xcnt;
+	ondisk.builder.committed.xip = MemoryContextAlloc(builder->context, sz);
+	if (read(fd, ondisk.builder.committed.xip, sz) != sz)
+	{
+		CloseTransientFile(fd);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not read committed xacts from snapbuild file \"%s\": %m",
+						path)));
+	}
+
+	CloseTransientFile(fd);
+
+	/*
+	 * ok, we now have a sensible snapshot here, figure out if it has more
+	 * information than we have.
+	 */
+
+	/*
+	 * We are only interested in consistent snapshots for now, comparing
+	 * whether one imcomplete snapshot is more "advanced" seems to be
+	 * unnecessarily complex.
+	 */
+	if (ondisk.builder.state < SNAPBUILD_CONSISTENT)
+		goto snapshot_not_interesting;
+
+	/*
+	 * Don't use a snapshot that requires an xmin that we cannot guarantee to
+	 * be available.
+	 */
+	if (TransactionIdPrecedes(ondisk.builder.xmin, builder->initial_xmin_horizon))
+		goto snapshot_not_interesting;
+
+	/*
+	 * XXX: transactions_after needs to be updated differently, to be checked
+	 * here
+	 */
+
+	/* ok, we think the snapshot is sensible, copy over everything important */
+	builder->xmin = ondisk.builder.xmin;
+	builder->xmax = ondisk.builder.xmax;
+	builder->state = ondisk.builder.state;
+
+	builder->committed.xcnt = ondisk.builder.committed.xcnt;
+	/* We only allocated/stored xcnt, not xcnt_space xids ! */
+	/* don't overwrite preallocated xip, if we don't have anything here */
+	if (builder->committed.xcnt > 0)
+	{
+		pfree(builder->committed.xip);
+		builder->committed.xcnt_space = ondisk.builder.committed.xcnt;
+		builder->committed.xip = ondisk.builder.committed.xip;
+	}
+	ondisk.builder.committed.xip = NULL;
+
+	builder->running.xcnt = ondisk.builder.committed.xcnt;
+	if (builder->running.xip)
+		pfree(builder->running.xip);
+	builder->running.xcnt_space = ondisk.builder.committed.xcnt_space;
+	builder->running.xip = ondisk.builder.running.xip;
+
+	/* our snapshot is not interesting anymore, build a new one */
+	if (builder->snapshot != NULL)
+	{
+		SnapBuildSnapDecRefcount(builder->snapshot);
+	}
+	builder->snapshot = SnapBuildBuildSnapshot(builder, InvalidTransactionId);
+	SnapBuildSnapIncRefcount(builder->snapshot);
+
+	ReorderBufferSetRestartPoint(builder->reorder, lsn);
+
+	Assert(builder->state == SNAPBUILD_CONSISTENT);
+	elog(LOG, "recovered initial snapshot (xmin %u) from disk",	 builder->xmin);
+
+	return true;
+
+snapshot_not_interesting:
+	if (ondisk.builder.running.xip != NULL)
+		pfree(ondisk.builder.running.xip);
+	if (ondisk.builder.committed.xip != NULL)
+		pfree(ondisk.builder.committed.xip);
+	return false;
+}
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 8c83780..0d64156 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -65,7 +65,7 @@ Node *replication_parse_result;
 }
 
 /* Non-keyword tokens */
-%token <str> SCONST
+%token <str> SCONST IDENT
 %token <uintval> UCONST
 %token <recptr> RECPTR
 
@@ -73,6 +73,9 @@ Node *replication_parse_result;
 %token K_BASE_BACKUP
 %token K_IDENTIFY_SYSTEM
 %token K_START_REPLICATION
+%token K_INIT_LOGICAL_REPLICATION
+%token K_START_LOGICAL_REPLICATION
+%token K_FREE_LOGICAL_REPLICATION
 %token K_TIMELINE_HISTORY
 %token K_LABEL
 %token K_PROGRESS
@@ -82,10 +85,13 @@ Node *replication_parse_result;
 %token K_TIMELINE
 
 %type <node>	command
-%type <node>	base_backup start_replication identify_system timeline_history
+%type <node>	base_backup start_replication start_logical_replication init_logical_replication free_logical_replication identify_system timeline_history
 %type <list>	base_backup_opt_list
 %type <defelt>	base_backup_opt
 %type <uintval>	opt_timeline
+%type <list>	plugin_options plugin_opt_list
+%type <defelt>	plugin_opt_elem
+%type <node>	plugin_opt_arg
 %%
 
 firstcmd: command opt_semicolon
@@ -102,6 +108,9 @@ command:
 			identify_system
 			| base_backup
 			| start_replication
+			| init_logical_replication
+			| start_logical_replication
+			| free_logical_replication
 			| timeline_history
 			;
 
@@ -186,6 +195,67 @@ opt_timeline:
 				| /* nothing */			{ $$ = 0; }
 			;
 
+init_logical_replication:
+			K_INIT_LOGICAL_REPLICATION IDENT IDENT
+				{
+					InitLogicalReplicationCmd *cmd;
+					cmd = makeNode(InitLogicalReplicationCmd);
+					cmd->name = $2;
+					cmd->plugin = $3;
+					$$ = (Node *) cmd;
+				}
+			;
+
+start_logical_replication:
+			K_START_LOGICAL_REPLICATION IDENT RECPTR plugin_options
+				{
+					StartLogicalReplicationCmd *cmd;
+					cmd = makeNode(StartLogicalReplicationCmd);
+					cmd->name = $2;
+					cmd->startpoint = $3;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
+plugin_options:
+			'(' plugin_opt_list ')'			{ $$ = $2; }
+			| /* EMPTY */					{ $$ = NIL; }
+		;
+
+plugin_opt_list:
+			plugin_opt_elem
+				{
+					$$ = list_make1($1);
+				}
+			| plugin_opt_list ',' plugin_opt_elem
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+plugin_opt_elem:
+			IDENT plugin_opt_arg
+				{
+					$$ = makeDefElem($1, $2);
+				}
+		;
+
+plugin_opt_arg:
+			SCONST							{ $$ = (Node *) makeString($1); }
+			| /* EMPTY */					{ $$ = NULL; }
+		;
+
+free_logical_replication:
+			K_FREE_LOGICAL_REPLICATION IDENT
+				{
+					FreeLogicalReplicationCmd *cmd;
+					cmd = makeNode(FreeLogicalReplicationCmd);
+					cmd->name = $2;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * TIMELINE_HISTORY %d
  */
@@ -205,6 +275,7 @@ timeline_history:
 					$$ = (Node *) cmd;
 				}
 			;
+
 %%
 
 #include "repl_scanner.c"
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 3d930f1..2b0f2ff 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "utils/builtins.h"
+#include "parser/scansup.h"
 
 /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
 #undef fprintf
@@ -48,7 +49,7 @@ static void addlitchar(unsigned char ychar);
 %option warn
 %option prefix="replication_yy"
 
-%x xq
+%x xq xd
 
 /* Extended quote
  * xqdouble implements embedded quote, ''''
@@ -57,12 +58,26 @@ xqstart			{quote}
 xqdouble		{quote}{quote}
 xqinside		[^']+
 
+/* Double quote
+ * Allows embedded spaces and other special characters into identifiers.
+ */
+dquote			\"
+xdstart			{dquote}
+xdstop			{dquote}
+xddouble		{dquote}{dquote}
+xdinside		[^"]+
+
 digit			[0-9]+
 hexdigit		[0-9A-Za-z]+
 
 quote			'
 quotestop		{quote}
 
+ident_start		[A-Za-z\200-\377_]
+ident_cont		[A-Za-z\200-\377_0-9\$]
+
+identifier		{ident_start}{ident_cont}*
+
 %%
 
 BASE_BACKUP			{ return K_BASE_BACKUP; }
@@ -74,9 +89,14 @@ PROGRESS			{ return K_PROGRESS; }
 WAL			{ return K_WAL; }
 TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
+INIT_LOGICAL_REPLICATION	{ return K_INIT_LOGICAL_REPLICATION; }
+START_LOGICAL_REPLICATION	{ return K_START_LOGICAL_REPLICATION; }
+FREE_LOGICAL_REPLICATION	{ return K_FREE_LOGICAL_REPLICATION; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 ","				{ return ','; }
 ";"				{ return ';'; }
+"("				{ return '('; }
+")"				{ return ')'; }
 
 [\n]			;
 [\t]			;
@@ -100,20 +120,49 @@ TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 					BEGIN(xq);
 					startlit();
 				}
+
 <xq>{quotestop}	{
 					yyless(1);
 					BEGIN(INITIAL);
 					yylval.str = litbufdup();
 					return SCONST;
 				}
-<xq>{xqdouble} {
+
+<xq>{xqdouble}	{
 					addlitchar('\'');
 				}
+
 <xq>{xqinside}  {
 					addlit(yytext, yyleng);
 				}
 
-<xq><<EOF>>		{ yyerror("unterminated quoted string"); }
+{xdstart}		{
+					BEGIN(xd);
+					startlit();
+				}
+
+<xd>{xdstop}	{
+					int len;
+					yyless(1);
+					BEGIN(INITIAL);
+					yylval.str = litbufdup();
+					len = strlen(yylval.str);
+					truncate_identifier(yylval.str, len, true);
+					return IDENT;
+				}
+
+<xd>{xdinside}  {
+					addlit(yytext, yyleng);
+				}
+
+{identifier}	{
+					int len = strlen(yytext);
+
+					yylval.str = downcase_truncate_identifier(yytext, len, true);
+					return IDENT;
+				}
+
+<xq,xd><<EOF>>	{ yyerror("unterminated quoted string"); }
 
 
 <<EOF>>			{
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 413f0b9..e73f566 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1137,7 +1137,7 @@ XLogWalRcvSendHSFeedback(bool immed)
 	 * everything else has been checked.
 	 */
 	if (hot_standby_feedback)
-		xmin = GetOldestXmin(true, false);
+		xmin = GetOldestXmin(true, true, false, false);
 	else
 		xmin = InvalidTransactionId;
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b00a91a..2187d96 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -45,9 +45,8 @@
 
 #include "access/timeline.h"
 #include "access/transam.h"
-#include "access/xlog_internal.h"
 #include "access/xact.h"
-
+#include "access/xlog_internal.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
@@ -56,6 +55,10 @@
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "replication/basebackup.h"
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalfuncs.h"
+#include "replication/snapbuild.h"
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
@@ -157,6 +160,9 @@ static bool ping_sent = false;
 static bool streamingDoneSending;
 static bool streamingDoneReceiving;
 
+/* Are we there yet? */
+static bool		WalSndCaughtUp = false;
+
 /* Flags set by signal handlers for later service in main loop */
 static volatile sig_atomic_t got_SIGHUP = false;
 static volatile sig_atomic_t walsender_ready_to_stop = false;
@@ -169,24 +175,42 @@ static volatile sig_atomic_t walsender_ready_to_stop = false;
  */
 static volatile sig_atomic_t replication_active = false;
 
+/* XXX reader */
+static MemoryContext decoding_ctx = NULL;
+static MemoryContext old_decoding_ctx = NULL;
+
+static LogicalDecodingContext *logical_decoding_ctx = NULL;
+static XLogRecPtr  logical_startptr = InvalidXLogRecPtr;
+
 /* Signal handlers */
 static void WalSndSigHupHandler(SIGNAL_ARGS);
 static void WalSndXLogSendHandler(SIGNAL_ARGS);
 static void WalSndLastCycleHandler(SIGNAL_ARGS);
 
 /* Prototypes for private functions */
-static void WalSndLoop(void);
+typedef void (*WalSndSendData)(void);
+static void WalSndLoop(WalSndSendData send_data);
 static void InitWalSenderSlot(void);
 static void WalSndKill(int code, Datum arg);
-static void XLogSend(bool *caughtup);
+static void XLogSendPhysical(void);
+static void XLogSendLogical(void);
+static void WalSndDone(WalSndSendData send_data);
 static XLogRecPtr GetStandbyFlushRecPtr(void);
 static void IdentifySystem(void);
 static void StartReplication(StartReplicationCmd *cmd);
+static void InitLogicalReplication(InitLogicalReplicationCmd *cmd);
+static void StartLogicalReplication(StartLogicalReplicationCmd *cmd);
+static void FreeLogicalReplication(FreeLogicalReplicationCmd *cmd);
 static void ProcessStandbyMessage(void);
 static void ProcessStandbyReplyMessage(void);
 static void ProcessStandbyHSFeedbackMessage(void);
 static void ProcessRepliesIfAny(void);
 static void WalSndKeepalive(bool requestReply);
+static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid);
+static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid);
+static void XLogRead(char *buf, XLogRecPtr startptr, Size count);
+
+
 
 
 /* Initialize walsender process before entering the main command loop */
@@ -247,14 +271,13 @@ IdentifySystem(void)
 	char		tli[11];
 	char		xpos[MAXFNAMELEN];
 	XLogRecPtr	logptr;
-	char*        dbname = NULL;
+	char	   *dbname = NULL;
 
 	/*
 	 * 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,
 			 GetSystemIdentifier());
 
@@ -308,22 +331,22 @@ 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");
-	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, "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 */
@@ -335,9 +358,16 @@ IdentifySystem(void)
 	pq_sendbytes(&buf, (char *) tli, strlen(tli));
 	pq_sendint(&buf, strlen(xpos), 4);	/* col3 len */
 	pq_sendbytes(&buf, (char *) xpos, strlen(xpos));
-	pq_sendint(&buf, strlen(dbname), 4);	/* col4 len */
-	pq_sendbytes(&buf, (char *) dbname, strlen(dbname));
-
+	/* 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);
 }
 
@@ -586,7 +616,7 @@ StartReplication(StartReplicationCmd *cmd)
 		/* Main loop of walsender */
 		replication_active = true;
 
-		WalSndLoop();
+		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
 		if (walsender_ready_to_stop)
@@ -653,6 +683,497 @@ StartReplication(StartReplicationCmd *cmd)
 	pq_puttextmessage('C', "START_STREAMING");
 }
 
+static int
+replay_read_page(XLogReaderState* state, XLogRecPtr targetPagePtr, int reqLen,
+				 XLogRecPtr targetRecPtr, char* cur_page, TimeLineID *pageTLI)
+{
+	XLogRecPtr flushptr;
+	int		count;
+
+	flushptr = WalSndWaitForWal(targetPagePtr + reqLen);
+
+	/* more than one block available */
+	if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
+		count = XLOG_BLCKSZ;
+	/* not enough data there */
+	else if (targetPagePtr + reqLen > flushptr)
+		return -1;
+	/* part of the page available */
+	else
+		count = flushptr - targetPagePtr;
+
+	/* FIXME: more sensible/efficient implementation */
+	XLogRead(cur_page, targetPagePtr, XLOG_BLCKSZ);
+
+	return count;
+}
+
+/*
+ * Initialize logical replication and wait for an initial consistent point to
+ * start sending changes from.
+ */
+static void
+InitLogicalReplication(InitLogicalReplicationCmd *cmd)
+{
+	const char *slot_name;
+	StringInfoData buf;
+	char		xpos[MAXFNAMELEN];
+	const char *snapshot_name = NULL;
+	LogicalDecodingContext *ctx;
+	XLogRecPtr startptr;
+
+	CheckLogicalReplicationRequirements();
+
+	Assert(!MyLogicalDecodingSlot);
+
+	/* XXX apply sanity checking to slot name? */
+	LogicalDecodingAcquireFreeSlot(cmd->name, cmd->plugin);
+
+	Assert(MyLogicalDecodingSlot);
+
+	decoding_ctx = AllocSetContextCreate(TopMemoryContext,
+										 "decoding context",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+	old_decoding_ctx = MemoryContextSwitchTo(decoding_ctx);
+
+	/* setup state for XLogReadPage */
+	sendTimeLineIsHistoric = false;
+	sendTimeLine = ThisTimeLineID;
+
+	initStringInfo(&output_message);
+	ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, false, InvalidXLogRecPtr,
+									   NIL,	replay_read_page,
+									   WalSndPrepareWrite, WalSndWriteData);
+
+	MemoryContextSwitchTo(old_decoding_ctx);
+
+	startptr = MyLogicalDecodingSlot->restart_decoding;
+
+	elog(WARNING, "Initiating logical rep from %X/%X",
+		 (uint32)(startptr >> 32), (uint32)startptr);
+
+	for (;;)
+	{
+		XLogRecord *record;
+		XLogRecordBuffer buf;
+		char *err = NULL;
+
+		/* the read_page callback waits for new WAL */
+		record = XLogReadRecord(ctx->reader, startptr, &err);
+		/* xlog record was invalid */
+		if (err)
+			elog(ERROR, "%s", err);
+
+		/* read up from last position next time round */
+		startptr = InvalidXLogRecPtr;
+
+		Assert(record);
+
+		buf.origptr = ctx->reader->ReadRecPtr;
+		buf.endptr = ctx->reader->EndRecPtr;
+		buf.record = *record;
+		buf.record_data = XLogRecGetData(record);
+		DecodeRecordIntoReorderBuffer(ctx, &buf);
+
+		/* only continue till we found a consistent spot */
+		if (LogicalDecodingContextReady(ctx))
+		{
+			/* export plain, importable, snapshot to the user */
+			snapshot_name = SnapBuildExportSnapshot(ctx->snapshot_builder);
+			break;
+		}
+	}
+
+	MyLogicalDecodingSlot->confirmed_flush = ctx->reader->EndRecPtr;
+	slot_name = NameStr(MyLogicalDecodingSlot->name);
+	snprintf(xpos, sizeof(xpos), "%X/%X",
+			 (uint32) (MyLogicalDecodingSlot->confirmed_flush >> 32),
+			 (uint32) MyLogicalDecodingSlot->confirmed_flush);
+
+	pq_beginmessage(&buf, 'T');
+	pq_sendint(&buf, 4, 2);		/* 4 fields */
+
+	/* first field */
+	pq_sendstring(&buf, "replication_id");	/* 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_sendstring(&buf, "consistent_point");	/* 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_sendstring(&buf, "snapshot_name");	/* 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_sendstring(&buf, "plugin");	/* 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, 4, 2);		/* # of columns */
+
+	/* replication_id */
+	pq_sendint(&buf, strlen(slot_name), 4); /* col1 len */
+	pq_sendbytes(&buf, slot_name, strlen(slot_name));
+
+	/* consistent wal location */
+	pq_sendint(&buf, strlen(xpos), 4); /* col2 len */
+	pq_sendbytes(&buf, xpos, strlen(xpos));
+
+	/* snapshot name */
+	pq_sendint(&buf, strlen(snapshot_name), 4); /* col3 len */
+	pq_sendbytes(&buf, snapshot_name, strlen(snapshot_name));
+
+	/* plugin */
+	pq_sendint(&buf, strlen(cmd->plugin), 4); /* col4 len */
+	pq_sendbytes(&buf, cmd->plugin, strlen(cmd->plugin));
+
+	pq_endmessage(&buf);
+
+	/*
+	 * release active status again, START_LOGICAL_REPLICATION will reacquire it
+	 */
+	LogicalDecodingReleaseSlot();
+}
+
+/*
+ * Load previously initiated logical slot and prepare for sending data (via
+ * WalSndLoop).
+ */
+static void
+StartLogicalReplication(StartLogicalReplicationCmd *cmd)
+{
+	StringInfoData buf;
+	XLogRecPtr confirmed_flush;
+
+	elog(WARNING, "Starting logical replication from %x/%x",
+		 (uint32)(cmd->startpoint >> 32), (uint32)cmd->startpoint);
+
+	/* make sure that our requirements are still fulfilled */
+	CheckLogicalReplicationRequirements();
+
+	Assert(!MyLogicalDecodingSlot);
+
+	LogicalDecodingReAcquireSlot(cmd->name);
+
+	if (am_cascading_walsender && !RecoveryInProgress())
+	{
+		ereport(LOG,
+				(errmsg("terminating walsender process to force cascaded standby to update timeline and reconnect")));
+		walsender_ready_to_stop = true;
+	}
+
+	WalSndSetState(WALSNDSTATE_CATCHUP);
+
+	/* Send a CopyBothResponse message, and start streaming */
+	pq_beginmessage(&buf, 'W');
+	pq_sendbyte(&buf, 0);
+	pq_sendint(&buf, 0, 2);
+	pq_endmessage(&buf);
+	pq_flush();
+
+	/* setup state for XLogReadPage */
+	sendTimeLineIsHistoric = false;
+	sendTimeLine = ThisTimeLineID;
+
+	confirmed_flush = MyLogicalDecodingSlot->confirmed_flush;
+
+	Assert(confirmed_flush != InvalidXLogRecPtr);
+
+	/* continue from last position */
+	if (cmd->startpoint == InvalidXLogRecPtr)
+		cmd->startpoint = MyLogicalDecodingSlot->confirmed_flush;
+	else if (cmd->startpoint > MyLogicalDecodingSlot->confirmed_flush)
+		elog(ERROR, "cannot stream from %X/%X, minimum is %X/%X",
+			 (uint32)(cmd->startpoint >> 32), (uint32)cmd->startpoint,
+			 (uint32)(confirmed_flush >> 32), (uint32)confirmed_flush);
+
+	/*
+	 * Initialize position to the last ack'ed one, then the xlog records begin
+	 * to be shipped from that position.
+	 */
+	logical_decoding_ctx = CreateLogicalDecodingContext(
+		MyLogicalDecodingSlot, false, cmd->startpoint, cmd->options,
+		replay_read_page, WalSndPrepareWrite, WalSndWriteData);
+
+	/*
+	 * XXX: For feedback purposes it would be nicer to set sentPtr to
+	 * cmd->startpoint, but we use it to know where to read xlog in the main
+	 * loop...
+	 */
+	sentPtr = MyLogicalDecodingSlot->restart_decoding;
+	logical_startptr = sentPtr;
+
+	/* Also update the start position status in shared memory */
+	{
+		/* use volatile pointer to prevent code rearrangement */
+		volatile WalSnd *walsnd = MyWalSnd;
+
+		SpinLockAcquire(&walsnd->mutex);
+		walsnd->sentPtr = MyLogicalDecodingSlot->restart_decoding;
+		SpinLockRelease(&walsnd->mutex);
+	}
+
+	elog(LOG, "starting to decode from %X/%X, replay %X/%X",
+		 (uint32)(MyWalSnd->sentPtr >> 32), (uint32)MyWalSnd->sentPtr,
+		 (uint32)(cmd->startpoint >> 32), (uint32)cmd->startpoint);
+
+	replication_active = true;
+
+	SyncRepInitConfig();
+
+	/* Main loop of walsender */
+	WalSndLoop(XLogSendLogical);
+
+	LogicalDecodingReleaseSlot();
+
+	replication_active = false;
+	if (walsender_ready_to_stop)
+		proc_exit(0);
+	WalSndSetState(WALSNDSTATE_STARTUP);
+
+	/* Get out of COPY mode (CommandComplete). */
+	EndCommand("COPY 0", DestRemote);
+}
+
+/*
+ * Free permanent state by a now inactive but defined logical slot.
+ */
+static void
+FreeLogicalReplication(FreeLogicalReplicationCmd *cmd)
+{
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingFreeSlot(cmd->name);
+	EndCommand("FREE_LOGICAL_REPLICATION", DestRemote);
+}
+
+/*
+ * LogicalDecodingContext 'prepare_write' callback.
+ *
+ * Prepare a write into a StringInfo.
+ *
+ * Don't do anything lasting in here, it's quite possible that nothing will done
+ * with the data.
+ */
+static void
+WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	AssertVariableIsOfType(&WalSndPrepareWrite, LogicalOutputPluginWriterPrepareWrite);
+
+	resetStringInfo(ctx->out);
+
+	pq_sendbyte(ctx->out, 'w');
+	pq_sendint64(ctx->out, lsn);	/* dataStart */
+	/* XXX: overwrite when data is assembled */
+	pq_sendint64(ctx->out, lsn);	/* walEnd */
+	/* XXX: gather that value later just as it's done in XLogSendPhysical */
+	pq_sendint64(ctx->out, 0 /*GetCurrentIntegerTimestamp() */);/* sendtime */
+}
+
+/*
+ * LogicalDecodingContext 'write' callback.
+ *
+ * Actually write out data previously prepared by WalSndPrepareWrite out to the
+ * network, take as long as needed but process replies from the other side
+ * during that.
+ */
+static void
+WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	AssertVariableIsOfType(&WalSndWriteData, LogicalOutputPluginWriterWrite);
+
+	/* output previously gathered data in a CopyData packet */
+	pq_putmessage_noblock('d', ctx->out->data, ctx->out->len);
+
+	/* fast path */
+	/* Try to flush pending output to the client */
+	if (pq_flush_if_writable() != 0)
+		return;
+
+	if (!pq_is_send_pending())
+		return;
+
+	for (;;)
+	{
+		int			wakeEvents;
+		long		sleeptime = 10000;		/* 10s */
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (!PostmasterIsAlive())
+			exit(1);
+
+		/* Process any requests or signals received recently */
+		if (got_SIGHUP)
+		{
+			got_SIGHUP = false;
+			ProcessConfigFile(PGC_SIGHUP);
+			SyncRepInitConfig();
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Check for input from the client */
+		ProcessRepliesIfAny();
+
+		/* Clear any already-pending wakeups */
+		ResetLatch(&MyWalSnd->latch);
+
+		/* Try to flush pending output to the client */
+		if (pq_flush_if_writable() != 0)
+			break;
+
+		/* If we finished clearing the buffered data, we're done here. */
+		if (!pq_is_send_pending())
+			break;
+
+		/*
+		 * Note we don't set a timeout here.  It would be pointless, because
+		 * if the socket is not writable there's not much we can do elsewhere
+		 * anyway.
+		 */
+		wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH |
+			WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE | WL_TIMEOUT;
+
+		ImmediateInterruptOK = true;
+		CHECK_FOR_INTERRUPTS();
+		WaitLatchOrSocket(&MyWalSnd->latch, wakeEvents,
+						  MyProcPort->sock, sleeptime);
+		ImmediateInterruptOK = false;
+	}
+
+	/* reactivate latch so WalSndLoop knows to continue */
+	SetLatch(&MyWalSnd->latch);
+}
+
+/*
+ * Wait till WAL < loc is flushed to disk so it can be safely read.
+ */
+XLogRecPtr
+WalSndWaitForWal(XLogRecPtr loc)
+{
+	int			wakeEvents;
+	XLogRecPtr  flushptr;
+
+	/* fast path if everything is there already */
+	/*
+	 * XXX: introduce RecentFlushPtr to avoid acquiring the spinlock in the
+	 * fast path case where we already know we have enough WAL available.
+	 */
+	flushptr = GetFlushRecPtr();
+	if (loc <= flushptr)
+		return flushptr;
+
+	for (;;)
+	{
+		long		sleeptime = 10000;		/* 10 s */
+
+		/*
+		 * Emergency bailout if postmaster has died.  This is to avoid the
+		 * necessity for manual cleanup of all postmaster children.
+		 */
+		if (!PostmasterIsAlive())
+			exit(1);
+
+		/* Process any requests or signals received recently */
+		if (got_SIGHUP)
+		{
+			got_SIGHUP = false;
+			ProcessConfigFile(PGC_SIGHUP);
+			SyncRepInitConfig();
+		}
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Check for input from the client */
+		ProcessRepliesIfAny();
+
+		/* Clear any already-pending wakeups */
+		ResetLatch(&MyWalSnd->latch);
+
+		/* Update our idea of flushed position. */
+		flushptr = GetFlushRecPtr();
+
+		/* If postmaster asked us to stop, don't wait here anymore */
+		if (walsender_ready_to_stop)
+			break;
+
+		/* check whether we're done */
+		if (loc <= flushptr)
+			break;
+
+		/* Determine time until replication timeout */
+		if (wal_sender_timeout > 0)
+		{
+			if (!ping_sent)
+			{
+				TimestampTz timeout;
+
+				/*
+				 * If half of wal_sender_timeout has lapsed without receiving
+				 * any reply from standby, send a keep-alive message to standby
+				 * requesting an immediate reply.
+				 */
+				timeout = TimestampTzPlusMilliseconds(last_reply_timestamp,
+													  wal_sender_timeout / 2);
+				if (GetCurrentTimestamp() >= timeout)
+				{
+					WalSndKeepalive(true);
+					ping_sent = true;
+					/* Try to flush pending output to the client */
+					if (pq_flush_if_writable() != 0)
+						break;
+				}
+			}
+
+			sleeptime = 1 + (wal_sender_timeout / 10);
+		}
+
+		wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH |
+			WL_SOCKET_READABLE | WL_TIMEOUT;
+
+		ImmediateInterruptOK = true;
+		CHECK_FOR_INTERRUPTS();
+		WaitLatchOrSocket(&MyWalSnd->latch, wakeEvents,
+						  MyProcPort->sock, sleeptime);
+		ImmediateInterruptOK = false;
+
+		/*
+		 * The equivalent code in WalSndLoop checks here that replication
+		 * timeout hasn't been exceeded.  We don't do that here.   XXX explain
+		 * why.
+		 */
+	}
+
+	/* reactivate latch so WalSndLoop knows to continue */
+	SetLatch(&MyWalSnd->latch);
+	return flushptr;
+}
+
 /*
  * Execute an incoming replication command.
  */
@@ -664,6 +1185,12 @@ exec_replication_command(const char *cmd_string)
 	MemoryContext cmd_context;
 	MemoryContext old_context;
 
+	/*
+	 * INIT_LOGICAL_REPLICATION exports a snapshot until the next command
+	 * arrives. Clean up the old stuff if there's anything.
+	 */
+	SnapBuildClearExportedSnapshot();
+
 	elog(DEBUG1, "received replication command: %s", cmd_string);
 
 	CHECK_FOR_INTERRUPTS();
@@ -695,6 +1222,18 @@ exec_replication_command(const char *cmd_string)
 			StartReplication((StartReplicationCmd *) cmd_node);
 			break;
 
+		case T_InitLogicalReplicationCmd:
+			InitLogicalReplication((InitLogicalReplicationCmd *) cmd_node);
+			break;
+
+		case T_StartLogicalReplicationCmd:
+			StartLogicalReplication((StartLogicalReplicationCmd *) cmd_node);
+			break;
+
+		case T_FreeLogicalReplicationCmd:
+			FreeLogicalReplication((FreeLogicalReplicationCmd *) cmd_node);
+			break;
+
 		case T_BaseBackupCmd:
 			SendBaseBackup((BaseBackupCmd *) cmd_node);
 			break;
@@ -904,6 +1443,12 @@ ProcessStandbyReplyMessage(void)
 		SpinLockRelease(&walsnd->mutex);
 	}
 
+	/*
+	 * Advance our local xmin horizon when the client confirmed a flush.
+	 */
+	if (MyLogicalDecodingSlot && flushPtr != InvalidXLogRecPtr)
+		LogicalConfirmReceivedLocation(flushPtr);
+
 	if (!am_cascading_walsender)
 		SyncRepReleaseWaiters();
 }
@@ -988,10 +1533,8 @@ ProcessStandbyHSFeedbackMessage(void)
 
 /* Main loop of walsender process that streams the WAL over Copy messages. */
 static void
-WalSndLoop(void)
+WalSndLoop(WalSndSendData send_data)
 {
-	bool		caughtup = false;
-
 	/*
 	 * Allocate buffers that will be used for each outgoing and incoming
 	 * message.  We do this just once to reduce palloc overhead.
@@ -1043,21 +1586,21 @@ WalSndLoop(void)
 
 		/*
 		 * If we don't have any pending data in the output buffer, try to send
-		 * some more.  If there is some, we don't bother to call XLogSend
+		 * some more.  If there is some, we don't bother to call send_data
 		 * again until we've flushed it ... but we'd better assume we are not
 		 * caught up.
 		 */
 		if (!pq_is_send_pending())
-			XLogSend(&caughtup);
+			send_data();
 		else
-			caughtup = false;
+			WalSndCaughtUp = false;
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			goto send_failure;
 
 		/* If nothing remains to be sent right now ... */
-		if (caughtup && !pq_is_send_pending())
+		if (WalSndCaughtUp && !pq_is_send_pending())
 		{
 			/*
 			 * If we're in catchup state, move to streaming.  This is an
@@ -1083,29 +1626,17 @@ WalSndLoop(void)
 			 * the walsender is not sure which.
 			 */
 			if (walsender_ready_to_stop)
-			{
-				/* ... let's just be real sure we're caught up ... */
-				XLogSend(&caughtup);
-				if (caughtup && sentPtr == MyWalSnd->flush &&
-					!pq_is_send_pending())
-				{
-					/* Inform the standby that XLOG streaming is done */
-					EndCommand("COPY 0", DestRemote);
-					pq_flush();
-
-					proc_exit(0);
-				}
-			}
+				WalSndDone(send_data);
 		}
 
 		/*
 		 * We don't block if not caught up, unless there is unsent data
 		 * pending in which case we'd better block until the socket is
-		 * write-ready.  This test is only needed for the case where XLogSend
+		 * write-ready.  This test is only needed for the case where send_data
 		 * loaded a subset of the available data but then pq_flush_if_writable
 		 * flushed it all --- we should immediately try to send more.
 		 */
-		if ((caughtup && !streamingDoneSending) || pq_is_send_pending())
+		if ((WalSndCaughtUp && !streamingDoneSending) || pq_is_send_pending())
 		{
 			TimestampTz timeout = 0;
 			long		sleeptime = 10000;		/* 10 s */
@@ -1434,15 +1965,17 @@ retry:
 }
 
 /*
+ * Send out the WAL in its normal physical/stored form.
+ *
  * Read up to MAX_SEND_SIZE bytes of WAL that's been flushed to disk,
  * but not yet sent to the client, and buffer it in the libpq output
  * buffer.
  *
- * If there is no unsent WAL remaining, *caughtup is set to true, otherwise
- * *caughtup is set to false.
+ * If there is no unsent WAL remaining, WalSndCaughtUp is set to true,
+ * otherwise WalSndCaughtUp is set to false.
  */
 static void
-XLogSend(bool *caughtup)
+XLogSendPhysical(void)
 {
 	XLogRecPtr	SendRqstPtr;
 	XLogRecPtr	startptr;
@@ -1451,7 +1984,7 @@ XLogSend(bool *caughtup)
 
 	if (streamingDoneSending)
 	{
-		*caughtup = true;
+		WalSndCaughtUp = true;
 		return;
 	}
 
@@ -1568,7 +2101,7 @@ XLogSend(bool *caughtup)
 		pq_putmessage_noblock('c', NULL, 0);
 		streamingDoneSending = true;
 
-		*caughtup = true;
+		WalSndCaughtUp = true;
 
 		elog(DEBUG1, "walsender reached end of timeline at %X/%X (sent up to %X/%X)",
 			 (uint32) (sendTimeLineValidUpto >> 32), (uint32) sendTimeLineValidUpto,
@@ -1580,7 +2113,7 @@ XLogSend(bool *caughtup)
 	Assert(sentPtr <= SendRqstPtr);
 	if (SendRqstPtr <= sentPtr)
 	{
-		*caughtup = true;
+		WalSndCaughtUp = true;
 		return;
 	}
 
@@ -1604,15 +2137,15 @@ XLogSend(bool *caughtup)
 	{
 		endptr = SendRqstPtr;
 		if (sendTimeLineIsHistoric)
-			*caughtup = false;
+			WalSndCaughtUp = false;
 		else
-			*caughtup = true;
+			WalSndCaughtUp = true;
 	}
 	else
 	{
 		/* round down to page boundary. */
 		endptr -= (endptr % XLOG_BLCKSZ);
-		*caughtup = false;
+		WalSndCaughtUp = false;
 	}
 
 	nbytes = endptr - startptr;
@@ -1673,6 +2206,96 @@ XLogSend(bool *caughtup)
 }
 
 /*
+ * Send out the WAL after it being decoded into a logical format by the output
+ * plugin specified in INIT_LOGICAL_DECODING
+ */
+static void
+XLogSendLogical(void)
+{
+	XLogRecord *record;
+	char	   *errm;
+
+	if (decoding_ctx == NULL)
+	{
+		decoding_ctx = AllocSetContextCreate(TopMemoryContext,
+											 "decoding context",
+											 ALLOCSET_DEFAULT_MINSIZE,
+											 ALLOCSET_DEFAULT_INITSIZE,
+											 ALLOCSET_DEFAULT_MAXSIZE);
+	}
+
+	record = XLogReadRecord(logical_decoding_ctx->reader, logical_startptr, &errm);
+	logical_startptr = InvalidXLogRecPtr;
+
+	/* xlog record was invalid */
+	if (errm != NULL)
+		elog(ERROR, "%s", errm);
+
+	if (record != NULL)
+	{
+		XLogRecordBuffer buf;
+
+		buf.origptr = logical_decoding_ctx->reader->ReadRecPtr;
+		buf.endptr = logical_decoding_ctx->reader->EndRecPtr;
+		buf.record = *record;
+		buf.record_data = XLogRecGetData(record);
+
+		old_decoding_ctx = MemoryContextSwitchTo(decoding_ctx);
+
+		DecodeRecordIntoReorderBuffer(logical_decoding_ctx, &buf);
+
+		MemoryContextSwitchTo(old_decoding_ctx);
+
+		/*
+		 * If the record we just read is at or beyond the flushed point, then
+		 * we're caught up.
+		 */
+		WalSndCaughtUp =
+			logical_decoding_ctx->reader->EndRecPtr >= GetFlushRecPtr();
+	}
+	else
+		/*
+		 * xlogreader failed, and no error was reported? we must be caught up.
+		 */
+		WalSndCaughtUp = true;
+
+	/* Update shared memory status */
+	{
+		/* use volatile pointer to prevent code rearrangement */
+		volatile WalSnd *walsnd = MyWalSnd;
+
+		SpinLockAcquire(&walsnd->mutex);
+		walsnd->sentPtr = logical_decoding_ctx->reader->ReadRecPtr;
+		SpinLockRelease(&walsnd->mutex);
+	}
+}
+
+/*
+ * The sender is caught up, so we can go away for shutdown processing
+ * to finish normally.  (This should only be called when the shutdown
+ * signal has been received from postmaster.)
+ *
+ * Note that if while doing this we determine that there's still more
+ * data to send, this function will return control to the caller.
+ */
+static void
+WalSndDone(WalSndSendData send_data)
+{
+	/* ... let's just be real sure we're caught up ... */
+	send_data();
+
+	if (WalSndCaughtUp && sentPtr == MyWalSnd->flush &&
+		!pq_is_send_pending())
+	{
+		/* Inform the standby that XLOG streaming is done */
+		EndCommand("COPY 0", DestRemote);
+		pq_flush();
+
+		proc_exit(0);
+	}
+}
+
+/*
  * Returns the latest point in WAL that has been safely flushed to disk, and
  * can be sent to the standby. This should only be called when in recovery,
  * ie. we're streaming to a cascaded standby.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index a0b741b..71d8f04 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -27,6 +27,7 @@
 #include "postmaster/bgworker_internals.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/postmaster.h"
+#include "replication/logical.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
@@ -124,6 +125,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, ProcSignalShmemSize());
 		size = add_size(size, CheckpointerShmemSize());
 		size = add_size(size, AutoVacuumShmemSize());
+		size = add_size(size, LogicalDecodingShmemSize());
 		size = add_size(size, WalSndShmemSize());
 		size = add_size(size, WalRcvShmemSize());
 		size = add_size(size, BTreeShmemSize());
@@ -230,6 +232,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	ProcSignalShmemInit();
 	CheckpointerShmemInit();
 	AutoVacuumShmemInit();
+	LogicalDecodingShmemInit();
 	WalSndShmemInit();
 	WalRcvShmemInit();
 
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c2f86ff..11aa1f5 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -51,6 +51,9 @@
 #include "access/xact.h"
 #include "access/twophase.h"
 #include "miscadmin.h"
+#include "replication/logical.h"
+#include "replication/walsender.h"
+#include "replication/walsender_private.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
@@ -1141,16 +1144,18 @@ TransactionIdIsActive(TransactionId xid)
  * GetOldestXmin() move backwards, with no consequences for data integrity.
  */
 TransactionId
-GetOldestXmin(bool allDbs, bool ignoreVacuum)
+GetOldestXmin(bool allDbs, bool ignoreVacuum, bool systable, bool alreadyLocked)
 {
 	ProcArrayStruct *arrayP = procArray;
 	TransactionId result;
 	int			index;
+	volatile TransactionId logical_xmin = InvalidTransactionId;
 
 	/* Cannot look for individual databases during recovery */
 	Assert(allDbs || !RecoveryInProgress());
 
-	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (!alreadyLocked)
+		LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
 	 * We initialize the MIN() calculation with latestCompletedXid + 1. This
@@ -1197,6 +1202,10 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 		}
 	}
 
+	/* fetch into volatile var while ProcArrayLock is held */
+	if (max_logical_slots > 0)
+		logical_xmin = LogicalDecodingCtl->xmin;
+
 	if (RecoveryInProgress())
 	{
 		/*
@@ -1205,7 +1214,8 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 		 */
 		TransactionId kaxmin = KnownAssignedXidsGetOldestXmin();
 
-		LWLockRelease(ProcArrayLock);
+		if (!alreadyLocked)
+			LWLockRelease(ProcArrayLock);
 
 		if (TransactionIdIsNormal(kaxmin) &&
 			TransactionIdPrecedes(kaxmin, result))
@@ -1213,10 +1223,8 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 	}
 	else
 	{
-		/*
-		 * No other information needed, so release the lock immediately.
-		 */
-		LWLockRelease(ProcArrayLock);
+		if (!alreadyLocked)
+			LWLockRelease(ProcArrayLock);
 
 		/*
 		 * Compute the cutoff XID by subtracting vacuum_defer_cleanup_age,
@@ -1237,6 +1245,15 @@ GetOldestXmin(bool allDbs, bool ignoreVacuum)
 			result = FirstNormalTransactionId;
 	}
 
+	/*
+	 * after locks are released and defer_cleanup_age has been applied, check
+	 * whether we need to back up further to make logical decoding possible.
+	 */
+	if (systable &&
+		TransactionIdIsValid(logical_xmin) &&
+		NormalTransactionIdPrecedes(logical_xmin, result))
+		result = logical_xmin;
+
 	return result;
 }
 
@@ -1290,7 +1307,9 @@ GetMaxSnapshotSubxidCount(void)
  *			older than this are known not running any more.
  *		RecentGlobalXmin: the global xmin (oldest TransactionXmin across all
  *			running transactions, except those running LAZY VACUUM).  This is
- *			the same computation done by GetOldestXmin(true, true).
+ *			the same computation done by GetOldestXmin(true, true, ...).
+ *		RecentGlobalDataXmin: the global xmin for non-catalog tables
+ *			>= RecentGlobalXmin
  *
  * Note: this function should probably not be called with an argument that's
  * not statically allocated (see xip allocation below).
@@ -1306,6 +1325,7 @@ GetSnapshotData(Snapshot snapshot)
 	int			count = 0;
 	int			subcount = 0;
 	bool		suboverflowed = false;
+	volatile TransactionId logical_xmin = InvalidTransactionId;
 
 	Assert(snapshot != NULL);
 
@@ -1483,8 +1503,14 @@ GetSnapshotData(Snapshot snapshot)
 			suboverflowed = true;
 	}
 
+
+	/* fetch into volatile var while ProcArrayLock is held */
+	if (max_logical_slots > 0)
+		logical_xmin = LogicalDecodingCtl->xmin;
+
 	if (!TransactionIdIsValid(MyPgXact->xmin))
 		MyPgXact->xmin = TransactionXmin = xmin;
+
 	LWLockRelease(ProcArrayLock);
 
 	/*
@@ -1499,6 +1525,17 @@ GetSnapshotData(Snapshot snapshot)
 	RecentGlobalXmin = globalxmin - vacuum_defer_cleanup_age;
 	if (!TransactionIdIsNormal(RecentGlobalXmin))
 		RecentGlobalXmin = FirstNormalTransactionId;
+
+	/* Non-catalog tables can be vacuumed if older than this xid */
+	RecentGlobalDataXmin = RecentGlobalXmin;
+
+	/*
+	 * peg the global xmin to the one required for logical decoding if required
+	 */
+	if (TransactionIdIsNormal(logical_xmin) &&
+		NormalTransactionIdPrecedes(logical_xmin, RecentGlobalXmin))
+		RecentGlobalXmin = logical_xmin;
+
 	RecentXmin = xmin;
 
 	snapshot->xmin = xmin;
@@ -1599,9 +1636,11 @@ ProcArrayInstallImportedXmin(TransactionId xmin, TransactionId sourcexid)
  * Similar to GetSnapshotData but returns more information. We include
  * all PGXACTs with an assigned TransactionId, even VACUUM processes.
  *
- * We acquire XidGenLock, but the caller is responsible for releasing it.
- * This ensures that no new XIDs enter the proc array until the caller has
- * WAL-logged this snapshot, and releases the lock.
+ * We acquire XidGenLock and ProcArrayLock, but the caller is responsible for
+ * releasing them. Acquiring XidGenLock ensures that no new XIDs enter the proc
+ * array until the caller has WAL-logged this snapshot, and releases the
+ * lock. Acquiring ProcArrayLock ensures that no transactions commit until the
+ * lock is released.
  *
  * The returned data structure is statically allocated; caller should not
  * modify it, and must not assume it is valid past the next call.
@@ -1736,6 +1775,12 @@ GetRunningTransactionData(void)
 		}
 	}
 
+	/*
+	 * Its important *not* to track decoding tasks here because snapbuild.c
+	 * uses ->oldestRunningXid to manage its xmin. If it were to be included
+	 * here the initial value could never increase.
+	 */
+
 	CurrentRunningXacts->xcnt = count - subcount;
 	CurrentRunningXacts->subxcnt = subcount;
 	CurrentRunningXacts->subxid_overflow = suboverflowed;
@@ -1743,13 +1788,12 @@ GetRunningTransactionData(void)
 	CurrentRunningXacts->oldestRunningXid = oldestRunningXid;
 	CurrentRunningXacts->latestCompletedXid = latestCompletedXid;
 
-	/* We don't release XidGenLock here, the caller is responsible for that */
-	LWLockRelease(ProcArrayLock);
-
 	Assert(TransactionIdIsValid(CurrentRunningXacts->nextXid));
 	Assert(TransactionIdIsValid(CurrentRunningXacts->oldestRunningXid));
 	Assert(TransactionIdIsNormal(CurrentRunningXacts->latestCompletedXid));
 
+	/* We don't release the locks here, the caller is responsible for that */
+
 	return CurrentRunningXacts;
 }
 
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 97da1a0..5f74c3e 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -879,8 +879,23 @@ LogStandbySnapshot(void)
 	 * record we write, because standby will open up when it sees this.
 	 */
 	running = GetRunningTransactionData();
+
+	/*
+	 * GetRunningTransactionData() acquired ProcArrayLock, we must release
+	 * it. We can do that before inserting the WAL record because
+	 * ProcArrayApplyRecoveryInfo can recheck the commit status using the
+	 * clog. If we're doing logical replication we can't do that though, so
+	 * hold the lock for a moment longer.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		LWLockRelease(ProcArrayLock);
+
 	recptr = LogCurrentRunningXacts(running);
 
+	/* Release lock if we kept it longer ... */
+	if (wal_level >= WAL_LEVEL_LOGICAL)
+		LWLockRelease(ProcArrayLock);
+
 	/* GetRunningTransactionData() acquired XidGenLock, we must release it */
 	LWLockRelease(XidGenLock);
 
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index bfe7d78..015970a 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -512,7 +512,7 @@ RegisterSnapshotInvalidation(Oid dbId, Oid relId)
  * Only the local caches are flushed; this does not transmit the message
  * to other backends.
  */
-static void
+void
 LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
 {
 	if (msg->id >= 0)
@@ -596,7 +596,7 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
  *		since that tells us we've lost some shared-inval messages and hence
  *		don't know what needs to be invalidated.
  */
-static void
+void
 InvalidateSystemCaches(void)
 {
 	int			i;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 44dd0d2..5d304ce 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1601,6 +1601,10 @@ RelationIdGetRelation(Oid relationId)
 		return rd;
 	}
 
+	/* up2date system relations, even during timetravel */
+	if (IsSystemRelationId(relationId))
+		SuspendDecodingSnapshots();
+
 	/*
 	 * no reldesc in the cache, so have RelationBuildDesc() build one and add
 	 * it.
@@ -1608,6 +1612,10 @@ RelationIdGetRelation(Oid relationId)
 	rd = RelationBuildDesc(relationId, true);
 	if (RelationIsValid(rd))
 		RelationIncrementReferenceCount(rd);
+
+	if (IsSystemRelationId(relationId))
+		UnSuspendDecodingSnapshots();
+
 	return rd;
 }
 
@@ -1729,6 +1737,10 @@ RelationReloadIndexInfo(Relation relation)
 		return;
 	}
 
+	/* up2date system relations, even during timetravel */
+	if (IsSystemRelation(relation))
+		SuspendDecodingSnapshots();
+
 	/*
 	 * Read the pg_class row
 	 *
@@ -1796,6 +1808,9 @@ RelationReloadIndexInfo(Relation relation)
 
 	/* Okay, now it's valid again */
 	relation->rd_isvalid = true;
+
+	if (IsSystemRelation(relation))
+		UnSuspendDecodingSnapshots();
 }
 
 /*
@@ -1977,6 +1992,10 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 
+		/* up2date system relations, even during timetravel */
+		if (IsSystemRelation(relation))
+			SuspendDecodingSnapshots();
+
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
 		if (newrel == NULL)
@@ -2046,6 +2065,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 
 		/* And now we can throw away the temporary entry */
 		RelationDestroyRelation(newrel);
+
+		if (IsSystemRelation(relation))
+			UnSuspendDecodingSnapshots();
 	}
 }
 
@@ -3551,7 +3573,10 @@ RelationGetIndexList(Relation relation)
 					Form_pg_attribute attr;
 					/* internal column, like oid */
 					if (attno <= 0)
-						continue;
+					{
+						found = false;
+						break;
+					}
 
 					attr = relation->rd_att->attrs[attno - 1];
 					if (!attr->attnotnull)
@@ -3839,17 +3864,26 @@ RelationGetIndexPredicate(Relation relation)
  * be bms_free'd when not needed anymore.
  */
 Bitmapset *
-RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
+RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
 	Bitmapset  *indexattrs;
-	Bitmapset  *uindexattrs;
+	Bitmapset  *uindexattrs; /* unique keys */
+	Bitmapset  *cindexattrs; /* best candidate key */
 	List	   *indexoidlist;
 	ListCell   *l;
 	MemoryContext oldcxt;
 
 	/* Quick exit if we already computed the result. */
 	if (relation->rd_indexattr != NULL)
-		return bms_copy(keyAttrs ? relation->rd_keyattr : relation->rd_indexattr);
+		switch(attrKind)
+		{
+			case INDEX_ATTR_BITMAP_CANDIDATE_KEY:
+				return bms_copy(relation->rd_ckeyattr);
+			case INDEX_ATTR_BITMAP_KEY:
+				return bms_copy(relation->rd_keyattr);
+			case INDEX_ATTR_BITMAP_ALL:
+				return bms_copy(relation->rd_indexattr);
+		}
 
 	/* Fast path if definitely no indexes */
 	if (!RelationGetForm(relation)->relhasindex)
@@ -3876,13 +3910,16 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 	 */
 	indexattrs = NULL;
 	uindexattrs = NULL;
+	cindexattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
 		Relation	indexDesc;
 		IndexInfo  *indexInfo;
 		int			i;
-		bool		isKey;
+		bool		isCKey;/* candidate or primary key */
+		bool		isKey;/* key member */
+
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -3894,6 +3931,8 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 			indexInfo->ii_Expressions == NIL &&
 			indexInfo->ii_Predicate == NIL;
 
+		isCKey = indexOid == relation->rd_primary;
+
 		/* Collect simple attribute references */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 		{
@@ -3903,6 +3942,11 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 			{
 				indexattrs = bms_add_member(indexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isCKey)
+					cindexattrs = bms_add_member(cindexattrs,
+												 attrnum - FirstLowInvalidHeapAttributeNumber);
+
 				if (isKey)
 					uindexattrs = bms_add_member(uindexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
@@ -3924,10 +3968,21 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	relation->rd_indexattr = bms_copy(indexattrs);
 	relation->rd_keyattr = bms_copy(uindexattrs);
+	relation->rd_ckeyattr = bms_copy(cindexattrs);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
-	return keyAttrs ? uindexattrs : indexattrs;
+	switch(attrKind)
+	{
+		case INDEX_ATTR_BITMAP_CANDIDATE_KEY:
+			return cindexattrs;
+		case INDEX_ATTR_BITMAP_KEY:
+			return uindexattrs;
+		case INDEX_ATTR_BITMAP_ALL:
+			return indexattrs;
+		default:
+			elog(ERROR, "unknown attrKind %u", attrKind);
+	}
 }
 
 /*
@@ -4902,3 +4957,49 @@ unlink_initfile(const char *initfilename)
 			elog(LOG, "could not remove cache file \"%s\": %m", initfilename);
 	}
 }
+
+bool
+RelationIsDoingTimetravelInternal(Relation relation)
+{
+	Assert(wal_level >= WAL_LEVEL_LOGICAL);
+
+	if (!RelationNeedsWAL(relation))
+		return false;
+
+	/*
+	 * XXX: Doing this test instead of using IsSystemNamespace has the
+	 * advantage of classifying a catalog relation's toast tables as a
+	 * timetravel relation as well. This is safe since even a oid wraparound
+	 * will preserve this property (c.f. GetNewObjectId()).
+	 */
+	if (IsSystemRelation(relation))
+		return true;
+
+	/*
+	 * Also log relevant data if we want the table to behave as a catalog
+	 * table, although its not a system provided one.
+	 * XXX: we need to make sure both the relation and its toast relation have
+	 * the flag set!
+	 */
+	if (RelationIsTreatedAsCatalogTable(relation))
+	    return true;
+
+	return false;
+}
+
+bool
+RelationIsLogicallyLoggedInternal(Relation relation)
+{
+	Assert(wal_level >= WAL_LEVEL_LOGICAL);
+	if (!RelationNeedsWAL(relation))
+		return false;
+	/*
+	 * XXX: In addition to the above comment, we could decide to always log
+	 * data even for real system catalogs, although the benefits of that seem
+	 * unclear.
+	 */
+	if (IsSystemRelation(relation))
+		return false;
+
+	return true;
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3107f9c..4a81018 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -57,6 +57,7 @@
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
+#include "replication/logical.h"
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
@@ -2072,6 +2073,17 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
+		/* see max_connections */
+		{"max_logical_slots", PGC_POSTMASTER, REPLICATION_SENDING,
+			gettext_noop("Sets the maximum number of simultaneously defined WAL decoding slots."),
+			NULL
+		},
+		&max_logical_slots,
+		0, 0, MAX_BACKENDS /*?*/,
+		NULL, NULL, NULL
+	},
+
+	{
 		{"wal_sender_timeout", PGC_SIGHUP, REPLICATION_SENDING,
 			gettext_noop("Sets the maximum time to wait for WAL replication."),
 			NULL,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d69a02b..b04291c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -161,7 +161,7 @@
 
 # - Settings -
 
-#wal_level = minimal			# minimal, archive, or hot_standby
+#wal_level = minimal			# minimal, archive, logical or hot_standby
 					# (change requires restart)
 #fsync = on				# turns forced synchronization on or off
 #synchronous_commit = on		# synchronization level;
@@ -208,11 +208,18 @@
 
 # Set these on the master and on any standby that will send replication data.
 
-#max_wal_senders = 0		# max number of walsender processes
+#max_wal_senders = 0		# max number of walsender processes, including
+				# both physical and logical replication senders.
 				# (change requires restart)
 #wal_keep_segments = 0		# in logfile segments, 16MB each; 0 disables
 #wal_sender_timeout = 60s	# in milliseconds; 0 disables
 
+#max_logical_slots = 0		# max number of logical replication sender
+				# and receiver processes. Logical senders
+				# (but not receivers) also consume a
+				# max_wal_senders slot.
+				# (change requires restart)
+
 # - Master Server -
 
 # These settings are ignored on a standby server.
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 584d70c..f63bafa 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -69,7 +69,7 @@
  */
 static SnapshotData CurrentSnapshotData = {HeapTupleSatisfiesMVCC};
 static SnapshotData SecondarySnapshotData = {HeapTupleSatisfiesMVCC};
-static SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
+SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
 
 /* Pointers to valid snapshots */
 static Snapshot CurrentSnapshot = NULL;
@@ -86,13 +86,14 @@ static bool CatalogSnapshotStale = true;
  * for the convenience of TransactionIdIsInProgress: even in bootstrap
  * mode, we don't want it to say that BootstrapTransactionId is in progress.
  *
- * RecentGlobalXmin is initialized to InvalidTransactionId, to ensure that no
+ * RecentGlobal(Data)?Xmin is initialized to InvalidTransactionId, to ensure that no
  * one tries to use a stale value.	Readers should ensure that it has been set
  * to something else before using it.
  */
 TransactionId TransactionXmin = FirstNormalTransactionId;
 TransactionId RecentXmin = FirstNormalTransactionId;
 TransactionId RecentGlobalXmin = InvalidTransactionId;
+TransactionId RecentGlobalDataXmin = InvalidTransactionId;
 
 /*
  * Elements of the active snapshot stack.
@@ -796,7 +797,7 @@ AtEOXact_Snapshot(bool isCommit)
  *		Returns the token (the file name) that can be used to import this
  *		snapshot.
  */
-static char *
+char *
 ExportSnapshot(Snapshot snapshot)
 {
 	TransactionId topXid;
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index ed66c49..28ce805 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -62,6 +62,8 @@
 #include "access/xact.h"
 #include "storage/bufmgr.h"
 #include "storage/procarray.h"
+#include "utils/builtins.h"
+#include "utils/combocid.h"
 #include "utils/tqual.h"
 
 
@@ -70,9 +72,17 @@ SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
 SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
 SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
 
+static Snapshot TimetravelSnapshot;
+/* (table, ctid) => (cmin, cmax) mapping during timetravel */
+static HTAB *tuplecid_data = NULL;
+static int timetravel_suspended = 0;
+
+
 /* local functions */
 static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
+static bool FailsSatisfies(HeapTuple htup, Snapshot snapshot, Buffer buffer);
+static bool RedirectSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
+								 Buffer buffer);
 
 /*
  * SetHintBits()
@@ -1490,3 +1500,261 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 	 */
 	return true;
 }
+
+/*
+ * check whether the transaciont id 'xid' in in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+	               sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+bool
+HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup, Snapshot snapshot,
+                                     Buffer buffer)
+{
+	HeapTupleHeader tuple = htup->t_data;
+	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
+	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (tuple->t_infomask & HEAP_XMIN_INVALID)
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+    /* check if its one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		CommandId cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId cmax = InvalidCommandId;
+
+		/*
+		 * If another transaction deleted this tuple or if cmin/cmax is stored
+		 * in a combocid we need to to lookup the actual values externally. We
+		 * need to do so in the deleted case because the deletion will have
+		 * overwritten the cmin value when setting cmax (c.f. combocid.c).
+		 */
+		if ((!(tuple->t_infomask & HEAP_XMAX_INVALID) &&
+			 !TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt)) ||
+			tuple->t_infomask & HEAP_COMBOCID
+			)
+		{
+			bool resolved;
+
+			resolved = ResolveCminCmaxDuringDecoding(tuplecid_data, htup,
+													 buffer, &cmin, &cmax);
+
+			if (!resolved)
+				elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+		}
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;	/* inserted after scan started */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMIN_COMMITTED &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if(TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+	}
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't
+	 * committed. I.e. invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* why should those be in catalog tables? */
+	Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+    /* check if its one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		CommandId cmin;
+		CommandId cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		if (tuple->t_infomask & HEAP_COMBOCID)
+		{
+			bool resolved;
+
+			resolved = ResolveCminCmaxDuringDecoding(tuplecid_data, htup,
+													 buffer, &cmin, &cmax);
+
+			if (!resolved)
+				elog(ERROR, "could not resolve combocid to cmax");
+		}
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;	/* deleted after scan started */
+		else
+			return false;	/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
+
+/*
+ * Setup a snapshot that replaces normal catalog snapshots that allows catalog
+ * access to behave just like it did at a certain point in the past.
+ *
+ * Needed for after-the-fact WAL decoding.
+ */
+void
+SetupDecodingSnapshots(Snapshot timetravel_snapshot, HTAB *tuplecids)
+{
+	/* prevent recursively setting up decoding snapshots */
+	Assert(CatalogSnapshotData.satisfies != RedirectSatisfiesMVCC);
+
+	CatalogSnapshotData.satisfies = RedirectSatisfiesMVCC;
+	/* make sure normal snapshots aren't used*/
+	SnapshotSelfData.satisfies = FailsSatisfies;
+	SnapshotAnyData.satisfies = FailsSatisfies;
+	SnapshotToastData.satisfies = FailsSatisfies;
+	/* don't overwrite SnapshotToastData, we want that to behave normally */
+
+	/* setup the timetravel snapshot */
+	TimetravelSnapshot = timetravel_snapshot;
+
+	/* setup (cmin, cmax) lookup hash */
+	tuplecid_data = tuplecids;
+
+	timetravel_suspended = 0;
+}
+
+
+/*
+ * Make catalog snapshots behave normally again.
+ */
+void
+RevertFromDecodingSnapshots(void)
+{
+	Assert(timetravel_suspended == 0);
+
+	TimetravelSnapshot = NULL;
+	tuplecid_data = NULL;
+
+	/* rally to restore sanity and/or boredom */
+	CatalogSnapshotData.satisfies = HeapTupleSatisfiesMVCC;
+	SnapshotSelfData.satisfies = HeapTupleSatisfiesSelf;
+	SnapshotAnyData.satisfies = HeapTupleSatisfiesAny;
+	SnapshotToastData.satisfies = HeapTupleSatisfiesToast;
+	timetravel_suspended = 0;
+}
+
+/*
+ * Disable catalog snapshot timetravel and perform old-fashioned access but
+ * make re-enabling cheap.. This is useful for accessing catalog entries which
+ * must stay up2date like the pg_class entries of system relations.
+ *
+ * Can be called several times in a nested fashion since several of it's
+ * callers suspend timetravel access on several code levels.
+ */
+void
+SuspendDecodingSnapshots(void)
+{
+	timetravel_suspended++;
+}
+
+/*
+ * Enable timetravel again, After SuspendDecodingSnapshots it.
+ */
+void
+UnSuspendDecodingSnapshots(void)
+{
+	Assert(timetravel_suspended > 0);
+	timetravel_suspended--;
+}
+
+/*
+ * Error out if a normal snapshot is used. That is neither legal nor expected
+ * during timetravel, so this is just extra assurance.
+ */
+static bool
+FailsSatisfies(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+{
+	elog(ERROR, "Normal snapshots cannot be used during timetravel access.");
+	return false;
+}
+
+
+/*
+ * Call the replacement SatisifiesMVCC with the required Snapshot data.
+ */
+static bool
+RedirectSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+{
+	Assert(TimetravelSnapshot != NULL);
+	if (timetravel_suspended > 0)
+		return HeapTupleSatisfiesMVCC(htup, snapshot, buffer);
+	return HeapTupleSatisfiesMVCCDuringDecoding(htup, TimetravelSnapshot,
+	                                            buffer);
+}
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f66f530..a887035 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -193,7 +193,9 @@ const char *subdirs[] = {
 	"base/1",
 	"pg_tblspc",
 	"pg_stat",
-	"pg_stat_tmp"
+	"pg_stat_tmp",
+	"pg_llog",
+	"pg_llog/snapshots"
 };
 
 
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index fde483a..8c6cf24 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -77,6 +77,8 @@ wal_level_str(WalLevel wal_level)
 			return "archive";
 		case WAL_LEVEL_HOT_STANDBY:
 			return "hot_standby";
+		case WAL_LEVEL_LOGICAL:
+			return "logical";
 	}
 	return _("unrecognized wal_level");
 }
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 4381778..42f3e6b 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -55,6 +55,18 @@
 #define XLOG_HEAP2_VISIBLE		0x40
 #define XLOG_HEAP2_MULTI_INSERT 0x50
 #define XLOG_HEAP2_LOCK_UPDATED 0x60
+#define XLOG_HEAP2_NEW_CID		0x70
+
+/*
+ * xl_heap_* ->flag values
+ */
+/* PD_ALL_VISIBLE was cleared */
+#define XLOG_HEAP_ALL_VISIBLE_CLEARED		(1<<0)
+/* PD_ALL_VISIBLE was cleared in the 2nd page */
+#define XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED	(1<<1)
+#define XLOG_HEAP_CONTAINS_OLD_TUPLE		(1<<2)
+#define XLOG_HEAP_CONTAINS_OLD_KEY			(1<<3)
+#define XLOG_HEAP_CONTAINS_NEW_TUPLE		(1<<4)
 
 /*
  * All what we need to find changed tuple
@@ -78,10 +90,10 @@ typedef struct xl_heap_delete
 	xl_heaptid	target;			/* deleted tuple id */
 	TransactionId xmax;			/* xmax of the deleted tuple */
 	uint8		infobits_set;	/* infomask bits */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
+	uint8		flags;
 } xl_heap_delete;
 
-#define SizeOfHeapDelete	(offsetof(xl_heap_delete, all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapDelete	(offsetof(xl_heap_delete, flags) + sizeof(uint8))
 
 /*
  * We don't store the whole fixed part (HeapTupleHeaderData) of an inserted
@@ -100,15 +112,23 @@ typedef struct xl_heap_header
 
 #define SizeOfHeapHeader	(offsetof(xl_heap_header, t_hoff) + sizeof(uint8))
 
+typedef struct xl_heap_header_len
+{
+	uint16      t_len;
+	xl_heap_header header;
+} xl_heap_header_len;
+
+#define SizeOfHeapHeaderLen	(offsetof(xl_heap_header_len, header) + SizeOfHeapHeader)
+
 /* This is what we need to know about insert */
 typedef struct xl_heap_insert
 {
 	xl_heaptid	target;			/* inserted tuple id */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
+	uint8		flags;
 	/* xl_heap_header & TUPLE DATA FOLLOWS AT END OF STRUCT */
 } xl_heap_insert;
 
-#define SizeOfHeapInsert	(offsetof(xl_heap_insert, all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapInsert	(offsetof(xl_heap_insert, flags) + sizeof(uint8))
 
 /*
  * This is what we need to know about a multi-insert. The record consists of
@@ -120,7 +140,7 @@ typedef struct xl_heap_multi_insert
 {
 	RelFileNode node;
 	BlockNumber blkno;
-	bool		all_visible_cleared;
+	uint8		flags;
 	uint16		ntuples;
 	OffsetNumber offsets[1];
 
@@ -147,13 +167,12 @@ typedef struct xl_heap_update
 	TransactionId old_xmax;		/* xmax of the old tuple */
 	TransactionId new_xmax;		/* xmax of the new tuple */
 	ItemPointerData newtid;		/* new inserted tuple id */
-	uint8		old_infobits_set;		/* infomask bits to set on old tuple */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
-	bool		new_all_visible_cleared;		/* same for the page of newtid */
+	uint8		old_infobits_set;	/* infomask bits to set on old tuple */
+	uint8		flags;
 	/* NEW TUPLE xl_heap_header AND TUPLE DATA FOLLOWS AT END OF STRUCT */
 } xl_heap_update;
 
-#define SizeOfHeapUpdate	(offsetof(xl_heap_update, new_all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapUpdate	(offsetof(xl_heap_update, flags) + sizeof(uint8))
 
 /*
  * This is what we need to know about vacuum page cleanup/redirect
@@ -261,6 +280,28 @@ typedef struct xl_heap_visible
 
 #define SizeOfHeapVisible (offsetof(xl_heap_visible, cutoff_xid) + sizeof(TransactionId))
 
+typedef struct xl_heap_new_cid
+{
+	/*
+	 * store toplevel xid so we don't have to merge cids from different
+	 * transactions
+	 */
+	TransactionId top_xid;
+	CommandId cmin;
+	CommandId cmax;
+	/*
+	 * don't really need the combocid but the padding makes it free and its
+	 * useful for debugging.
+	 */
+	CommandId combocid;
+	/*
+	 * Store the relfilenode/ctid pair to facilitate lookups.
+	 */
+	xl_heaptid target;
+} xl_heap_new_cid;
+
+#define SizeOfHeapNewCid (offsetof(xl_heap_new_cid, target) + SizeOfHeapTid)
+
 extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 									   TransactionId *latestRemovedXid);
 
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 23a41fd..8452ec5 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -63,6 +63,11 @@
 	(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
 	(int32) ((id1) - (id2)) < 0)
 
+/* compare two XIDs already known to be normal; this is a macro for speed */
+#define NormalTransactionIdFollows(id1, id2) \
+	(AssertMacro(TransactionIdIsNormal(id1) && TransactionIdIsNormal(id2)), \
+	(int32) ((id1) - (id2)) > 0)
+
 /* ----------
  *		Object ID (OID) zero is InvalidOid.
  *
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 835f6ac..96502ce 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -215,6 +215,7 @@ extern TransactionId GetCurrentTransactionId(void);
 extern TransactionId GetCurrentTransactionIdIfAny(void);
 extern TransactionId GetStableLatestTransactionId(void);
 extern SubTransactionId GetCurrentSubTransactionId(void);
+extern void MarkCurrentTransactionIdLoggedIfAny(void);
 extern bool SubTransactionIsActive(SubTransactionId subxid);
 extern CommandId GetCurrentCommandId(bool used);
 extern TimestampTz GetCurrentTransactionStartTimestamp(void);
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 002862c..7415a26 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -197,7 +197,8 @@ typedef enum WalLevel
 {
 	WAL_LEVEL_MINIMAL = 0,
 	WAL_LEVEL_ARCHIVE,
-	WAL_LEVEL_HOT_STANDBY
+	WAL_LEVEL_HOT_STANDBY,
+	WAL_LEVEL_LOGICAL
 } WalLevel;
 extern int	wal_level;
 
@@ -210,9 +211,12 @@ extern int	wal_level;
  */
 #define XLogIsNeeded() (wal_level >= WAL_LEVEL_ARCHIVE)
 
-/* Do we need to WAL-log information required only for Hot Standby? */
+/* Do we need to WAL-log information required only for Hot Standby and logical replication? */
 #define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_HOT_STANDBY)
 
+/* Do we need to WAL-log information required only for logical replication? */
+#define XLogLogicalInfoActive() (wal_level >= WAL_LEVEL_LOGICAL)
+
 #ifdef WAL_DEBUG
 extern bool XLOG_DEBUG;
 #endif
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 3829ce2..fdc8cc2 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -19,6 +19,7 @@
 #ifndef XLOGREADER_H
 #define XLOGREADER_H
 
+#include "access/xlog.h"
 #include "access/xlog_internal.h"
 
 typedef struct XLogReaderState XLogReaderState;
@@ -108,10 +109,20 @@ struct XLogReaderState
 	char	   *errormsg_buf;
 };
 
-/* Get a new XLogReader */
+
 extern XLogReaderState *XLogReaderAllocate(XLogPageReadCB pagereadfunc,
 				   void *private_data);
 
+
+typedef struct XLogRecordBuffer
+{
+	XLogRecPtr origptr;
+	XLogRecPtr endptr;
+	XLogRecord record;
+	char *record_data;
+} XLogRecordBuffer;
+
+
 /* Free an XLogReader */
 extern void XLogReaderFree(XLogReaderState *state);
 
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index 44b6f38..a96ed69 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -23,6 +23,7 @@ extern ForkNumber forkname_to_number(char *forkName);
 extern char *GetDatabasePath(Oid dbNode, Oid spcNode);
 
 
+extern bool IsSystemRelationId(Oid relid);
 extern bool IsSystemRelation(Relation relation);
 extern bool IsToastRelation(Relation relation);
 
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f03dd0b..cf9c143 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2621,6 +2621,8 @@ DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f
 DESCR("statistics: information about currently active backends");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{23,25,25,25,25,25,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
+DATA(insert OID = 3457 (  pg_stat_get_logical_decoding_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{25,25,26,16,28,25}" "{o,o,o,o,o,o}" "{slot_name,plugin,database,active,xmin,restart_decoding_lsn}" _null_ pg_stat_get_logical_decoding_slots _null_ _null_ _null_ ));
+DESCR("statistics: information about logical replication slots currently in use");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
 DESCR("statistics: current backend PID");
 DATA(insert OID = 1937 (  pg_stat_get_backend_pid		PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 23 "23" _null_ _null_ _null_ _null_ pg_stat_get_backend_pid _null_ _null_ _null_ ));
@@ -4725,6 +4727,10 @@ DESCR("SP-GiST support for quad tree over range");
 DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
 DESCR("SP-GiST support for quad tree over range");
 
+DATA(insert OID = 3779 (  init_logical_replication PGNSP PGUID 12 1 0 0 0 f f f f f f v 2 0 2249 "19 19" "{19,19,25,25}" "{i,i,o,o}" "{slotname,plugin,slotname,xlog_position}" _null_ init_logical_replication _null_ _null_ _null_ ));
+DESCR("set up a logical replication slot");
+DATA(insert OID = 3780 (  stop_logical_replication PGNSP PGUID 12 1 0 0 0 f f f f f f v 1 0 23 "19" _null_ _null_ _null_ _null_ stop_logical_replication _null_ _null_ _null_ ));
+DESCR("stop logical replication");
 
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 08bec25..66b8263 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -156,7 +156,7 @@ extern void vac_update_relstats(Relation relation,
 					TransactionId frozenxid,
 					MultiXactId minmulti);
 extern void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age,
-					  bool sharedRel,
+					  bool sharedRel, bool catalogRel,
 					  TransactionId *oldestXmin,
 					  TransactionId *freezeLimit,
 					  TransactionId *freezeTableLimit,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..360f98c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -409,6 +409,9 @@ typedef enum NodeTag
 	T_IdentifySystemCmd,
 	T_BaseBackupCmd,
 	T_StartReplicationCmd,
+	T_InitLogicalReplicationCmd,
+	T_StartLogicalReplicationCmd,
+	T_FreeLogicalReplicationCmd,
 	T_TimeLineHistoryCmd,
 
 	/*
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 85b4544..3da8d40 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -52,6 +52,41 @@ typedef struct StartReplicationCmd
 
 
 /* ----------------------
+ *		INIT_LOGICAL_REPLICATION command
+ * ----------------------
+ */
+typedef struct InitLogicalReplicationCmd
+{
+	NodeTag		type;
+	char       *name;
+	char       *plugin;
+} InitLogicalReplicationCmd;
+
+
+/* ----------------------
+ *		START_LOGICAL_REPLICATION command
+ * ----------------------
+ */
+typedef struct StartLogicalReplicationCmd
+{
+	NodeTag		type;
+	char       *name;
+	XLogRecPtr	startpoint;
+	List       *options;
+} StartLogicalReplicationCmd;
+
+/* ----------------------
+ *		FREE_LOGICAL_REPLICATION command
+ * ----------------------
+ */
+typedef struct FreeLogicalReplicationCmd
+{
+	NodeTag		type;
+	char       *name;
+} FreeLogicalReplicationCmd;
+
+
+/* ----------------------
  *		TIMELINE_HISTORY command
  * ----------------------
  */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
new file mode 100644
index 0000000..dd3f2ca
--- /dev/null
+++ b/src/include/replication/decode.h
@@ -0,0 +1,20 @@
+/*-------------------------------------------------------------------------
+ * decode.h
+ *	   PostgreSQL WAL to logical transformation
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DECODE_H
+#define DECODE_H
+
+#include "access/xlogreader.h"
+#include "replication/reorderbuffer.h"
+#include "replication/logical.h"
+
+void DecodeRecordIntoReorderBuffer(LogicalDecodingContext *ctx,
+							  XLogRecordBuffer *buf);
+
+#endif
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
new file mode 100644
index 0000000..971180b
--- /dev/null
+++ b/src/include/replication/logical.h
@@ -0,0 +1,198 @@
+/*-------------------------------------------------------------------------
+ * logical.h
+ *	   PostgreSQL WAL to logical transformation
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGICAL_H
+#define LOGICAL_H
+
+#include "access/xlog.h"
+#include "access/xlogreader.h"
+#include "replication/output_plugin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+
+/*
+ * Shared memory state of a single logical decoding slot
+ */
+typedef struct LogicalDecodingSlot
+{
+	/* lock, on same cacheline as effective_xmin */
+	slock_t		mutex;
+
+	/* on-disk xmin, updated first */
+	TransactionId xmin;
+
+	/* in-memory xmin, updated after syncing to disk */
+	TransactionId effective_xmin;
+
+	/* is this slot defined */
+	bool		in_use;
+
+	/* is somebody streaming out changes for this slot */
+	bool		active;
+
+	/* have we been aborted while ->active */
+	bool		aborted;
+
+	/* ----
+	 * If we shutdown, crash, whatever where do we have to restart decoding
+	 * from to
+	 * a) find a valid & ready snapshot
+	 * b) the complete content for all in-progress xacts
+	 * ----
+	 */
+	XLogRecPtr	restart_decoding;
+
+	/*
+	 * Last location we know the client has confirmed to have safely received
+	 * data to. No earlier data can be decoded after a restart/crash.
+	 */
+	XLogRecPtr	confirmed_flush;
+
+	/* ----
+	 * When the client has confirmed flushes >= candidate_xmin_after we can
+	 * a) advance the pegged xmin
+	 * b) advance restart_decoding_from so we have to read/keep less WAL
+	 * ----
+	 */
+	XLogRecPtr	candidate_lsn;
+	TransactionId candidate_xmin;
+	XLogRecPtr	candidate_restart_decoding;
+
+	/* database the slot is active on */
+	Oid			database;
+
+	/* slot identifier */
+	NameData	name;
+
+	/* plugin name */
+	NameData	plugin;
+} LogicalDecodingSlot;
+
+/*
+ * Shared memory control area for all of logical decoding
+ */
+typedef struct LogicalDecodingCtlData
+{
+	/*
+	 * Xmin across all logical slots.
+	 *
+	 * Protected by ProcArrayLock.
+	 */
+	TransactionId xmin;
+
+	LogicalDecodingSlot logical_slots[FLEXIBLE_ARRAY_MEMBER];
+} LogicalDecodingCtlData;
+
+/*
+ * Pointers to shared memory
+ */
+extern LogicalDecodingCtlData *LogicalDecodingCtl;
+extern LogicalDecodingSlot *MyLogicalDecodingSlot;
+
+struct LogicalDecodingContext;
+
+typedef void (*LogicalOutputPluginWriterWrite) (
+										   struct LogicalDecodingContext *lr,
+															XLogRecPtr Ptr,
+															TransactionId xid
+);
+
+typedef LogicalOutputPluginWriterWrite LogicalOutputPluginWriterPrepareWrite;
+
+/*
+ * Output plugin callbacks
+ */
+typedef struct OutputPluginCallbacks
+{
+	LogicalDecodeInitCB init_cb;
+	LogicalDecodeBeginCB begin_cb;
+	LogicalDecodeChangeCB change_cb;
+	LogicalDecodeCommitCB commit_cb;
+	LogicalDecodeCleanupCB cleanup_cb;
+} OutputPluginCallbacks;
+
+typedef struct LogicalDecodingContext
+{
+	struct XLogReaderState *reader;
+	struct LogicalDecodingSlot *slot;
+	struct ReorderBuffer *reorder;
+	struct SnapBuild *snapshot_builder;
+
+	struct OutputPluginCallbacks callbacks;
+
+	bool		stop_after_consistent;
+
+	/*
+	 * User specified options
+	 */
+	List	   *output_plugin_options;
+
+	/*
+	 * User-Provided callback for writing/streaming out data.
+	 */
+	LogicalOutputPluginWriterPrepareWrite prepare_write;
+	LogicalOutputPluginWriterWrite write;
+
+	/*
+	 * Output buffer.
+	 */
+	StringInfo	out;
+
+	/*
+	 * Private data pointer for the creator of the logical decoding context.
+	 */
+	void	   *owner_private;
+
+	/*
+	 * Private data pointer of the output plugin.
+	 */
+	void	   *output_plugin_private;
+
+	/*
+	 * Private data pointer for the data writer.
+	 */
+	void	   *output_writer_private;
+} LogicalDecodingContext;
+
+/* GUCs */
+extern PGDLLIMPORT int max_logical_slots;
+
+extern Size LogicalDecodingShmemSize(void);
+extern void LogicalDecodingShmemInit(void);
+
+extern void LogicalDecodingAcquireFreeSlot(const char *name, const char *plugin);
+extern void LogicalDecodingReleaseSlot(void);
+extern void LogicalDecodingReAcquireSlot(const char *name);
+extern void LogicalDecodingFreeSlot(const char *name);
+
+extern void ComputeLogicalXmin(void);
+
+/* change logical xmin */
+extern void IncreaseLogicalXminForSlot(XLogRecPtr lsn, TransactionId xmin);
+
+/* change recovery restart location */
+extern void IncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn);
+
+extern void LogicalConfirmReceivedLocation(XLogRecPtr lsn);
+
+extern void CheckLogicalReplicationRequirements(void);
+
+extern void StartupLogicalReplication(XLogRecPtr checkPointRedo);
+
+extern LogicalDecodingContext *CreateLogicalDecodingContext(
+							 LogicalDecodingSlot *slot,
+							 bool is_init,
+							 XLogRecPtr	start_lsn,
+							 List *output_plugin_options,
+							 XLogPageReadCB read_page,
+						 LogicalOutputPluginWriterPrepareWrite prepare_write,
+							 LogicalOutputPluginWriterWrite do_write);
+extern bool LogicalDecodingContextReady(LogicalDecodingContext *ctx);
+extern void FreeLogicalDecodingContext(LogicalDecodingContext *ctx);
+
+#endif
diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h
new file mode 100644
index 0000000..d6fd19c
--- /dev/null
+++ b/src/include/replication/logicalfuncs.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ * logicalfuncs.h
+ *	   PostgreSQL WAL to logical transformation support functions
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGICALFUNCS_H
+#define LOGICALFUNCS_H
+
+#include "replication/logical.h"
+
+extern int logical_read_local_xlog_page(XLogReaderState *state,
+							 XLogRecPtr targetPagePtr,
+							 int reqLen, XLogRecPtr targetRecPtr,
+							 char *cur_page, TimeLineID *pageTLI);
+
+extern Datum pg_stat_get_logical_decoding_slots(PG_FUNCTION_ARGS);
+
+#endif
diff --git a/src/include/replication/output_plugin.h b/src/include/replication/output_plugin.h
new file mode 100644
index 0000000..a9fcc2d
--- /dev/null
+++ b/src/include/replication/output_plugin.h
@@ -0,0 +1,70 @@
+/*-------------------------------------------------------------------------
+ * output_plugin.h
+ *	   PostgreSQL Logical Decode Plugin Interface
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OUTPUT_PLUGIN_H
+#define OUTPUT_PLUGIN_H
+
+#include "replication/reorderbuffer.h"
+
+struct LogicalDecodingContext;
+
+/*
+ * Callback that gets called in a user-defined plugin. ctx->private_data can
+ * be set to some private data.
+ *
+ * "is_init" will be set to "true" if the decoding slot just got defined. When
+ * the same slot is used from there one, it will be "false".
+ *
+ * Gets looked up via the library symbol pg_decode_init.
+ */
+typedef void (*LogicalDecodeInitCB) (
+										  struct LogicalDecodingContext *ctx,
+												 bool is_init
+);
+
+/*
+ * Callback called for every BEGIN of a successful transaction.
+ *
+ * Gets looked up via the library symbol pg_decode_begin_txn.
+ */
+typedef void (*LogicalDecodeBeginCB) (
+											 struct LogicalDecodingContext *,
+												  ReorderBufferTXN *txn);
+
+/*
+ * Callback for every individual change in a successful transaction.
+ *
+ * Gets looked up via the library symbol pg_decode_change.
+ */
+typedef void (*LogicalDecodeChangeCB) (
+											 struct LogicalDecodingContext *,
+												   ReorderBufferTXN *txn,
+												   Relation relation,
+												   ReorderBufferChange *change
+);
+
+/*
+ * Called for every COMMIT of a successful transaction.
+ *
+ * Gets looked up via the library symbol pg_decode_commit_txn.
+ */
+typedef void (*LogicalDecodeCommitCB) (
+											 struct LogicalDecodingContext *,
+												   ReorderBufferTXN *txn,
+												   XLogRecPtr commit_lsn);
+
+/*
+ * Called to cleanup the state of an output plugin.
+ *
+ * Gets looked up via the library symbol pg_decode_cleanup.
+ */
+typedef void (*LogicalDecodeCleanupCB) (
+											  struct LogicalDecodingContext *
+);
+
+#endif   /* OUTPUT_PLUGIN_H */
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
new file mode 100644
index 0000000..7a4e046
--- /dev/null
+++ b/src/include/replication/reorderbuffer.h
@@ -0,0 +1,342 @@
+/*
+ * reorderbuffer.h
+ *
+ * PostgreSQL logical replay buffer management
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * src/include/replication/reorderbuffer.h
+ */
+#ifndef REORDERBUFFER_H
+#define REORDERBUFFER_H
+
+#include "access/htup_details.h"
+#include "utils/hsearch.h"
+#include "utils/rel.h"
+
+#include "lib/ilist.h"
+
+#include "storage/sinval.h"
+
+#include "utils/snapshot.h"
+
+/* an individual tuple, stored in one chunk of memory */
+typedef struct ReorderBufferTupleBuf
+{
+	/* position in preallocated list */
+	slist_node	node;
+
+	/* tuple, stored sequentially */
+	HeapTupleData tuple;
+	HeapTupleHeaderData header;
+	char		data[MaxHeapTupleSize];
+} ReorderBufferTupleBuf;
+
+/* types of the change passed to a 'change' callback */
+enum ReorderBufferChangeType
+{
+	REORDER_BUFFER_CHANGE_INSERT,
+	REORDER_BUFFER_CHANGE_UPDATE,
+	REORDER_BUFFER_CHANGE_DELETE
+};
+
+/*
+ * a single 'change', can be an insert (with one tuple), an update (old, new),
+ * or a delete (old).
+ *
+ * The same struct is also used internally for other purposes but that should
+ * never be visible outside reorderbuffer.c.
+ */
+typedef struct ReorderBufferChange
+{
+	XLogRecPtr	lsn;
+
+	/* type of change */
+	union
+	{
+		enum ReorderBufferChangeType action;
+		/* do not leak internal enum values to the outside */
+		int			action_internal;
+	};
+
+	/*
+	 * Context data for the change, which part of the union is valid depends
+	 * on action/action_internal.
+	 */
+	union
+	{
+		/* old, new tuples when action == *_INSERT|UPDATE|DELETE */
+		struct
+		{
+			/* relation that has been changed */
+			RelFileNode relnode;
+			/* valid for DELETE || UPDATE */
+			ReorderBufferTupleBuf *oldtuple;
+			/* valid for INSERT || UPDATE */
+			ReorderBufferTupleBuf *newtuple;
+		};
+
+		/* new snapshot */
+		Snapshot	snapshot;
+
+		/* new command id for existing snapshot in a catalog changing tx */
+		CommandId	command_id;
+
+		/* new cid mapping for catalog changing transaction */
+		struct
+		{
+			RelFileNode node;
+			ItemPointerData tid;
+			CommandId	cmin;
+			CommandId	cmax;
+			CommandId	combocid;
+		}			tuplecid;
+	};
+
+	/*
+	 * While in use this is how a change is linked into a transactions,
+	 * otherwise it's the preallocated list.
+	 */
+	dlist_node	node;
+} ReorderBufferChange;
+
+typedef struct ReorderBufferTXN
+{
+	/*
+	 * The transactions transaction id, can be a toplevel or sub xid.
+	 */
+	TransactionId xid;
+
+	/*
+	 * LSN of the first data carrying, WAL record with knowledge about this
+	 * xid. This is allowed to *not* be first record adorned with this xid, if
+	 * the previous records aren't relevant for logical decoding.
+	 */
+	XLogRecPtr	first_lsn;
+
+	/* ----
+	 * LSN of the record that lead to this xact to be committed or
+	 * aborted. This can be a
+	 * * plain commit record
+	 * * plain commit record, of a parent transaction
+	 * * prepared transaction commit
+	 * * plain abort record
+	 * * prepared transaction abort
+	 * * error during decoding
+	 * ----
+	 */
+	XLogRecPtr	final_lsn;
+
+	/*
+	 * LSN pointing to the end of the commit record + 1.
+	 */
+	XLogRecPtr	end_lsn;
+
+	/*
+	 * LSN of the last lsn at which snapshot information reside, so we can
+	 * restart decoding from there and fully recover this transaction from
+	 * WAL.
+	 */
+	XLogRecPtr	restart_decoding_lsn;
+
+	/*
+	 * Base snapshot or NULL.
+	 */
+	Snapshot	base_snapshot;
+
+	/* did the TX have catalog changes */
+	bool		does_timetravel;
+
+	/*
+	 * Do we know this is a subxact?
+	 */
+	bool		is_known_as_subxact;
+
+	/*
+	 * How many ReorderBufferChange's do we have in this txn.
+	 *
+	 * Changes in subtransactions are *not* included but tracked separately.
+	 */
+	Size		nentries;
+
+	/*
+	 * How many of the above entries are stored in memory in contrast to being
+	 * spilled to disk.
+	 */
+	Size		nentries_mem;
+
+	/*
+	 * List of ReorderBufferChange structs, including new Snapshots and new
+	 * CommandIds
+	 */
+	dlist_head	changes;
+
+	/*
+	 * List of (relation, ctid) => (cmin, cmax) mappings for catalog tuples.
+	 * Those are always assigned to the toplevel transaction. (Keep track of
+	 * #entries to create a hash of the right size)
+	 */
+	dlist_head	tuplecids;
+	size_t		ntuplecids;
+
+	/*
+	 * On-demand built hash for looking up the above values.
+	 */
+	HTAB	   *tuplecid_hash;
+
+	/*
+	 * Hash containing (potentially partial) toast entries. NULL if no toast
+	 * tuples have been found for the current change.
+	 */
+	HTAB	   *toast_hash;
+
+	/*
+	 * non-hierarchical list of subtransactions that are *not* aborted. Only
+	 * used in toplevel transactions.
+	 */
+	dlist_head	subtxns;
+	size_t		nsubtxns;
+
+	/* ---
+	 * Position in one of three lists:
+	 * * list of subtransactions if we are *known* to be subxact
+	 * * list of toplevel xacts (can be a as-yet unknown subxact)
+	 * * list of preallocated ReorderBufferTXNs
+	 * ---
+	 */
+	dlist_node	node;
+
+	/*
+	 * Stored cache invalidations. This is not a linked list because we get
+	 * all the invalidations at once.
+	 */
+	SharedInvalidationMessage *invalidations;
+	size_t		ninvalidations;
+
+} ReorderBufferTXN;
+
+/* so we can define the callbacks used inside struct ReorderBuffer itself */
+typedef struct ReorderBuffer ReorderBuffer;
+
+/* change callback signature */
+typedef void (*ReorderBufferApplyChangeCB) (
+														ReorderBuffer *rb,
+														ReorderBufferTXN *txn,
+														Relation relation,
+												ReorderBufferChange *change);
+
+/* begin callback signature */
+typedef void (*ReorderBufferBeginCB) (
+												  ReorderBuffer *rb,
+												  ReorderBufferTXN *txn);
+
+/* commit callback signature */
+typedef void (*ReorderBufferCommitCB) (
+												   ReorderBuffer *rb,
+												   ReorderBufferTXN *txn,
+												   XLogRecPtr commit_lsn);
+
+struct ReorderBuffer
+{
+	/*
+	 * xid => ReorderBufferTXN lookup table
+	 */
+	HTAB	   *by_txn;
+
+	/*
+	 * Transactions that could be a toplevel xact, ordered by LSN of the first
+	 * record bearing that xid..
+	 */
+	dlist_head	toplevel_by_lsn;
+
+	/*
+	 * one-entry sized cache for by_txn. Very frequently the same txn gets
+	 * looked up over and over again.
+	 */
+	TransactionId by_txn_last_xid;
+	ReorderBufferTXN *by_txn_last_txn;
+
+	/*
+	 * Callacks to be called when a transactions commits.
+	 */
+	ReorderBufferBeginCB begin;
+	ReorderBufferApplyChangeCB apply_change;
+	ReorderBufferCommitCB commit;
+
+	/*
+	 * Pointer that will be passed untouched to the callbacks.
+	 */
+	void	   *private_data;
+
+	/*
+	 * Private memory context.
+	 */
+	MemoryContext context;
+
+	/*
+	 * Data structure slab cache.
+	 *
+	 * We allocate/deallocate some structures very frequently, to avoid bigger
+	 * overhead we cache some unused ones here.
+	 *
+	 * The maximum number of cached entries is controlled by const variables
+	 * ontop of reorderbuffer.c
+	 */
+
+	/* cached ReorderBufferTXNs */
+	dlist_head	cached_transactions;
+	Size		nr_cached_transactions;
+
+	/* cached ReorderBufferChanges */
+	dlist_head	cached_changes;
+	Size		nr_cached_changes;
+
+	/* cached ReorderBufferTupleBufs */
+	slist_head	cached_tuplebufs;
+	Size		nr_cached_tuplebufs;
+
+	XLogRecPtr	current_restart_decoding_lsn;
+
+	/* buffer for disk<->memory conversions */
+	char	   *outbuf;
+	Size		outbufsize;
+};
+
+
+ReorderBuffer *ReorderBufferAllocate(void);
+void		ReorderBufferFree(ReorderBuffer *);
+
+ReorderBufferTupleBuf *ReorderBufferGetTupleBuf(ReorderBuffer *);
+void		ReorderBufferReturnTupleBuf(ReorderBuffer *, ReorderBufferTupleBuf *tuple);
+ReorderBufferChange *ReorderBufferGetChange(ReorderBuffer *);
+void		ReorderBufferReturnChange(ReorderBuffer *, ReorderBufferChange *);
+
+void		ReorderBufferQueueChange(ReorderBuffer *, TransactionId, XLogRecPtr lsn, ReorderBufferChange *);
+void		ReorderBufferCommit(ReorderBuffer *, TransactionId,
+								XLogRecPtr commit_lsn, XLogRecPtr end_lsn);
+void		ReorderBufferAssignChild(ReorderBuffer *, TransactionId, TransactionId, XLogRecPtr commit_lsn);
+void		ReorderBufferCommitChild(ReorderBuffer *, TransactionId, TransactionId,
+									 XLogRecPtr commit_lsn, XLogRecPtr end_lsn);
+void		ReorderBufferAbort(ReorderBuffer *, TransactionId, XLogRecPtr lsn);
+
+void		ReorderBufferSetBaseSnapshot(ReorderBuffer *, TransactionId, XLogRecPtr lsn, struct SnapshotData *snap);
+void		ReorderBufferAddSnapshot(ReorderBuffer *, TransactionId, XLogRecPtr lsn, struct SnapshotData *snap);
+void ReorderBufferAddNewCommandId(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+							 CommandId cid);
+void ReorderBufferAddNewTupleCids(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+							 RelFileNode node, ItemPointerData pt,
+						 CommandId cmin, CommandId cmax, CommandId combocid);
+void ReorderBufferAddInvalidations(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+							  Size nmsgs, SharedInvalidationMessage *msgs);
+bool		ReorderBufferIsXidKnown(ReorderBuffer *, TransactionId xid);
+void		ReorderBufferXidSetTimetravel(ReorderBuffer *, TransactionId xid, XLogRecPtr lsn);
+bool		ReorderBufferXidDoesTimetravel(ReorderBuffer *, TransactionId xid);
+bool		ReorderBufferXidHasBaseSnapshot(ReorderBuffer *, TransactionId xid);
+
+ReorderBufferTXN *ReorderBufferGetOldestTXN(ReorderBuffer *);
+
+void		ReorderBufferSetRestartPoint(ReorderBuffer *, XLogRecPtr ptr);
+
+void		ReorderBufferStartup(void);
+
+#endif
diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h
new file mode 100644
index 0000000..7a4a217
--- /dev/null
+++ b/src/include/replication/snapbuild.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * snapbuild.h
+ *	  Exports from replication/logical/snapbuild.c.
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * src/include/replication/snapbuild.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SNAPBUILD_H
+#define SNAPBUILD_H
+
+#include "access/xlogdefs.h"
+#include "utils/snapmgr.h"
+
+typedef enum
+{
+	/*
+	 * Initial state, we can't do much yet.
+	 */
+	SNAPBUILD_START,
+
+	/*
+	 * We have collected enough information to decode tuples in transactions
+	 * that started after this.
+	 *
+	 * Once we reached this we start to collect changes. We cannot apply them
+	 * yet because the might be based on transactions that were still running
+	 * when we reached them yet.
+	 */
+	SNAPBUILD_FULL_SNAPSHOT,
+
+	/*
+	 * Found a point after hitting built_full_snapshot where all transactions
+	 * that were running at that point finished. Till we reach that we hold
+	 * off calling any commit callbacks.
+	 */
+	SNAPBUILD_CONSISTENT
+} SnapBuildState;
+
+/* forward declare so we don't have to expose the struct to the public */
+struct SnapBuild;
+typedef struct SnapBuild SnapBuild;
+
+/* forward declare so we don't have to include xlogreader.h */
+struct XLogRecordBuffer;
+struct ReorderBuffer;
+
+extern SnapBuild *AllocateSnapshotBuilder(struct ReorderBuffer *cache,
+						  TransactionId xmin_horizon, XLogRecPtr start_lsn);
+extern void FreeSnapshotBuilder(SnapBuild *cache);
+
+extern void SnapBuildSnapDecRefcount(Snapshot snap);
+
+extern const char *SnapBuildExportSnapshot(SnapBuild *snapstate);
+extern void SnapBuildClearExportedSnapshot(void);
+
+extern SnapBuildState SnapBuildCurrentState(SnapBuild *snapstate);
+
+extern bool SnapBuildXactNeedsSkip(SnapBuild *snapstate, XLogRecPtr ptr);
+
+/* don't want to include heapam_xlog.h */
+struct xl_heap_new_cid;
+struct xl_running_xacts;
+
+extern void SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn,
+							   TransactionId xid, int nsubxacts,
+							   TransactionId *subxacts);
+extern void SnapBuildAbortTxn(SnapBuild *builder, TransactionId xid,
+							  int nsubxacts, TransactionId *subxacts);
+extern bool SnapBuildProcessChange(SnapBuild *builder, TransactionId xid,
+								   XLogRecPtr lsn);
+extern void SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
+								   XLogRecPtr lsn, struct xl_heap_new_cid *cid);
+extern void SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn,
+										 struct xl_running_xacts *running);
+extern void SnapBuildSerializationPoint(SnapBuild *builder, XLogRecPtr lsn);
+
+#endif   /* SNAPBUILD_H */
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 7eaa21b..daae320 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -66,6 +66,7 @@ typedef struct WalSnd
 
 extern WalSnd *MyWalSnd;
 
+
 /* There is one WalSndCtl struct for the whole database cluster */
 typedef struct
 {
@@ -93,7 +94,6 @@ typedef struct
 
 extern WalSndCtlData *WalSndCtl;
 
-
 extern void WalSndSetState(WalSndState state);
 
 /*
@@ -108,4 +108,8 @@ extern void replication_scanner_finish(void);
 
 extern Node *replication_parse_result;
 
+/* logical wal sender data gathering functions */
+extern XLogRecPtr WalSndWaitForWal(XLogRecPtr loc);
+
+
 #endif   /* _WALSENDER_PRIVATE_H */
diff --git a/src/include/storage/itemptr.h b/src/include/storage/itemptr.h
index e0eb184..75c56a9 100644
--- a/src/include/storage/itemptr.h
+++ b/src/include/storage/itemptr.h
@@ -116,6 +116,9 @@ typedef ItemPointerData *ItemPointer;
 /*
  * ItemPointerCopy
  *		Copies the contents of one disk item pointer to another.
+ *
+ * Should there ever be padding in an ItemPointer this would need to be handled
+ * differently as it's used as hash key.
  */
 #define ItemPointerCopy(fromPointer, toPointer) \
 ( \
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 39415a3..a33d6cf 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -80,6 +80,7 @@ typedef enum LWLockId
 	OldSerXidLock,
 	SyncRepLock,
 	BackgroundWorkerLock,
+	LogicalReplicationCtlLock,
 	/* Individual lock IDs end here */
 	FirstBufMappingLock,
 	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5f58b4..744317e 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -50,7 +50,7 @@ extern RunningTransactions GetRunningTransactionData(void);
 
 extern bool TransactionIdIsInProgress(TransactionId xid);
 extern bool TransactionIdIsActive(TransactionId xid);
-extern TransactionId GetOldestXmin(bool allDbs, bool ignoreVacuum);
+extern TransactionId GetOldestXmin(bool allDbs, bool ignoreVacuum, bool systable, bool alreadyLocked);
 extern TransactionId GetOldestActiveTransactionId(void);
 
 extern VirtualTransactionId *GetVirtualXIDsDelayingChkpt(int *nvxids);
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 7e70e57..5448818 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -147,4 +147,6 @@ extern void ProcessCommittedInvalidationMessages(SharedInvalidationMessage *msgs
 									 int nmsgs, bool RelcacheInitFileInval,
 									 Oid dbid, Oid tsid);
 
+extern void LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg);
+
 #endif   /* SINVAL_H */
diff --git a/src/include/utils/inval.h b/src/include/utils/inval.h
index 6fd6e1e..5424912 100644
--- a/src/include/utils/inval.h
+++ b/src/include/utils/inval.h
@@ -64,4 +64,5 @@ extern void CacheRegisterRelcacheCallback(RelcacheCallbackFunction func,
 
 extern void CallSyscacheCallbacks(int cacheid, uint32 hashvalue);
 
+extern void InvalidateSystemCaches(void);
 #endif   /* INVAL_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0281b4b..6a4d2d5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -104,6 +104,7 @@ typedef struct RelationData
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
+	Bitmapset  *rd_ckeyattr;	/* cols that are included ref'd by pkey */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
 	LockInfoData rd_lockInfo;	/* lock mgr's info for locking relation */
 	RuleLock   *rd_rules;		/* rewrite rules */
@@ -221,6 +222,7 @@ typedef struct StdRdOptions
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
 	bool		security_barrier;		/* for views */
 	int			check_option_offset;	/* for views */
+	bool        treat_as_catalog_table; /* treat as timetraveleable table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -290,6 +292,15 @@ typedef struct StdRdOptions
 			"cascaded") == 0 : false)
 
 /*
+ * RelationIsTreatedAsCatalogTable
+ *		Returns whether the relation should be treated as a catalog table
+ *      from the pov of logical decoding.
+ */
+#define RelationIsTreatedAsCatalogTable(relation)	\
+	((relation)->rd_options ?				\
+	 ((StdRdOptions *) (relation)->rd_options)->treat_as_catalog_table : false)
+
+/*
  * RelationIsValid
  *		True iff relation descriptor is valid.
  */
@@ -441,7 +452,6 @@ typedef struct StdRdOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
-
 /*
  * RelationIsScannable
  *		Currently can only be false for a materialized view which has not been
@@ -458,6 +468,24 @@ typedef struct StdRdOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+/*
+ * RelationIsDoingTimetravel
+ *		True if we need to log enough information to provide timetravel access
+ */
+#define RelationIsDoingTimetravel(relation) \
+	(wal_level >= WAL_LEVEL_LOGICAL && \
+	 RelationIsDoingTimetravelInternal(relation))
+
+/*
+ * RelationIsLogicallyLogged
+ *		True if we need to log enough information to provide timetravel access
+ */
+#define RelationIsLogicallyLogged(relation) \
+	(wal_level >= WAL_LEVEL_LOGICAL && \
+	 RelationIsLogicallyLoggedInternal(relation))
+
+extern bool RelationIsDoingTimetravelInternal(Relation relation);
+extern bool RelationIsLogicallyLoggedInternal(Relation relation);
 
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 8ac2549..cfeded8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -41,7 +41,16 @@ extern List *RelationGetIndexList(Relation relation);
 extern Oid	RelationGetOidIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
-extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs);
+
+typedef enum IndexAttrBitmapKind {
+	INDEX_ATTR_BITMAP_ALL,
+	INDEX_ATTR_BITMAP_KEY,
+	INDEX_ATTR_BITMAP_CANDIDATE_KEY
+}  IndexAttrBitmapKind;
+
+extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
+											 IndexAttrBitmapKind keyAttrs);
+
 extern void RelationGetExclusionInfo(Relation indexRelation,
 						 Oid **operators,
 						 Oid **procs,
diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h
index 81a286c..2187f58 100644
--- a/src/include/utils/snapmgr.h
+++ b/src/include/utils/snapmgr.h
@@ -23,6 +23,7 @@ extern bool FirstSnapshotSet;
 extern TransactionId TransactionXmin;
 extern TransactionId RecentXmin;
 extern TransactionId RecentGlobalXmin;
+extern TransactionId RecentGlobalDataXmin;
 
 extern Snapshot GetTransactionSnapshot(void);
 extern Snapshot GetLatestSnapshot(void);
@@ -53,4 +54,6 @@ extern bool XactHasExportedSnapshots(void);
 extern void DeleteAllExportedSnapshotFiles(void);
 extern bool ThereAreNoPriorRegisteredSnapshots(void);
 
+extern char *ExportSnapshot(Snapshot snapshot);
+
 #endif   /* SNAPMGR_H */
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 19f56e4..cd3f880 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -22,6 +22,7 @@
 extern PGDLLIMPORT SnapshotData SnapshotSelfData;
 extern PGDLLIMPORT SnapshotData SnapshotAnyData;
 extern PGDLLIMPORT SnapshotData SnapshotToastData;
+extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 
 #define SnapshotSelf		(&SnapshotSelfData)
 #define SnapshotAny			(&SnapshotAnyData)
@@ -37,7 +38,8 @@ extern PGDLLIMPORT SnapshotData SnapshotToastData;
 
 /* This macro encodes the knowledge of which snapshots are MVCC-safe */
 #define IsMVCCSnapshot(snapshot)  \
-	((snapshot)->satisfies == HeapTupleSatisfiesMVCC)
+	((snapshot)->satisfies == HeapTupleSatisfiesMVCC || \
+	 (snapshot)->satisfies == HeapTupleSatisfiesMVCCDuringDecoding)
 
 /*
  * HeapTupleSatisfiesVisibility
@@ -86,4 +88,21 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 
+/* Support for catalog timetravel */
+struct HTAB;
+extern bool HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup,
+                                                 Snapshot snapshot, Buffer buffer);
+extern void SetupDecodingSnapshots(Snapshot snapshot_now, struct HTAB *tuplecids);
+extern void RevertFromDecodingSnapshots(void);
+extern void SuspendDecodingSnapshots(void);
+extern void UnSuspendDecodingSnapshots(void);
+
+/*
+ * To avoid leaking to much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data, HeapTuple htup,
+										  Buffer buffer,
+										  CommandId *cmin, CommandId *cmax);
+
 #endif   /* TQUAL_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 8f24c51..d49e499 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1679,6 +1679,13 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
                                  |     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,                                                                                                                                               +
                                  |     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock                                                                                                                                          +
                                  |    FROM pg_database d;
+ pg_stat_logical_decoding        |  SELECT l.slot_name,                                                                                                                                                                                           +
+                                 |     l.plugin,                                                                                                                                                                                                  +
+                                 |     l.database,                                                                                                                                                                                                +
+                                 |     l.active,                                                                                                                                                                                                  +
+                                 |     l.xmin,                                                                                                                                                                                                    +
+                                 |     l.restart_decoding_lsn                                                                                                                                                                                     +
+                                 |    FROM pg_stat_get_logical_decoding_slots() l(slot_name, plugin, database, active, xmin, restart_decoding_lsn);
  pg_stat_replication             |  SELECT s.pid,                                                                                                                                                                                                 +
                                  |     s.usesysid,                                                                                                                                                                                                +
                                  |     u.rolname AS usename,                                                                                                                                                                                      +
@@ -2142,7 +2149,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
                                  |    FROM tv;
  tvvmv                           |  SELECT tvvm.grandtot                                                                                                                                                                                          +
                                  |    FROM tvvm;
-(64 rows)
+(65 rows)
 
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b20eb0d..648caa0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -621,6 +621,7 @@ Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
 FormatNode
+FreeLogicalReplicationCmd
 FromCharDateMode
 FromExpr
 FuncCall
@@ -791,6 +792,7 @@ IdentifySystemCmd
 IncrementVarSublevelsUp_context
 Index
 IndexArrayKeyInfo
+IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
 IndexBulkDeleteCallback
@@ -818,6 +820,7 @@ IndxInfo
 InfoItem
 InhInfo
 InhOption
+InitLogicalReplicationCmd
 InheritableSocket
 InlineCodeBlock
 InsertStmt
@@ -937,6 +940,17 @@ LockTupleMode
 LockingClause
 LogOpts
 LogStmtLevel
+LogicalDecodeBeginCB
+LogicalDecodeChangeCB
+LogicalDecodeCleanupCB
+LogicalDecodeCommitCB
+LogicalDecodeInitCB
+LogicalDecodingCheckpointData
+LogicalDecodingContext
+LogicalDecodingCtlData
+LogicalDecodingSlot
+LogicalOutputPluginWriterPrepareWrite
+LogicalOutputPluginWriterWrite
 LogicalTape
 LogicalTapeSet
 MAGIC
@@ -1050,6 +1064,7 @@ OprInfo
 OprProofCacheEntry
 OprProofCacheKey
 OutputContext
+OutputPluginCallbacks
 OverrideSearchPath
 OverrideStackEntry
 PACE_HEADER
@@ -1464,6 +1479,21 @@ Relids
 RelocationBufferInfo
 RenameStmt
 ReopenPtr
+ReorderBuffer
+ReorderBufferApplyChangeCB
+ReorderBufferBeginCB
+ReorderBufferChange
+ReorderBufferChangeTypeInternal
+ReorderBufferCommitCB
+ReorderBufferDiskChange
+ReorderBufferIterTXNEntry
+ReorderBufferIterTXNState
+ReorderBufferToastEnt
+ReorderBufferTupleBuf
+ReorderBufferTupleCidEnt
+ReorderBufferTupleCidKey
+ReorderBufferTXN
+ReorderBufferTXNByIdEnt
 ReplaceVarsFromTargetList_context
 ReplaceVarsNoMatchOption
 ResTarget
@@ -1518,6 +1548,8 @@ SID_NAME_USE
 SISeg
 SMgrRelation
 SMgrRelationData
+SnapBuildAction
+SnapBuildState
 SOCKADDR
 SOCKET
 SPELL
@@ -1609,6 +1641,8 @@ SlruSharedData
 Snapshot
 SnapshotData
 SnapshotSatisfiesFunc
+Snapstate
+SnapstateOnDisk
 SockAddr
 Sort
 SortBy
@@ -1651,6 +1685,7 @@ StandardChunkHeader
 StartBlobPtr
 StartBlobsPtr
 StartDataPtr
+StartLogicalReplicationCmd
 StartReplicationCmd
 StartupPacket
 StatEntry
@@ -1874,6 +1909,7 @@ WalRcvData
 WalRcvState
 WalSnd
 WalSndCtlData
+WalSndSendData
 WalSndState
 WholeRowVarExprState
 WindowAgg
@@ -1925,6 +1961,7 @@ XLogReaderState
 XLogRecData
 XLogRecPtr
 XLogRecord
+XLogRecordBuffer
 XLogSegNo
 XLogSource
 XLogwrtResult
@@ -2347,6 +2384,7 @@ symbol
 tablespaceinfo
 teReqs
 teSection
+TestDecodingData
 temp_tablespaces_extra
 text
 timeKEY
@@ -2419,11 +2457,13 @@ xl_heap_cleanup_info
 xl_heap_delete
 xl_heap_freeze
 xl_heap_header
+xl_heap_header_len
 xl_heap_inplace
 xl_heap_insert
 xl_heap_lock
 xl_heap_lock_updated
 xl_heap_multi_insert
+xl_heap_new_cid
 xl_heap_newpage
 xl_heap_update
 xl_heap_visible
-- 
1.8.4.21.g992c386.dirty

0005-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patchtext/x-patch; charset=us-asciiDownload
>From 9b7532b418d87087175f75bcbb5b7fb36ace4509 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 5/8] wal_decoding: test_decoding: Add a simple decoding module
 in contrib

This is mostly useful for testing, demonstration and documentation purposes.
---
 contrib/Makefile                      |   1 +
 contrib/test_decoding/Makefile        |  16 ++
 contrib/test_decoding/test_decoding.c | 322 ++++++++++++++++++++++++++++++++++
 3 files changed, 339 insertions(+)
 create mode 100644 contrib/test_decoding/Makefile
 create mode 100644 contrib/test_decoding/test_decoding.c

diff --git a/contrib/Makefile b/contrib/Makefile
index 8a2a937..6d2fe32 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,6 +50,7 @@ SUBDIRS = \
 		tablefunc	\
 		tcn		\
 		test_parser	\
+		test_decoding	\
 		tsearch2	\
 		unaccent	\
 		vacuumlo	\
diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
new file mode 100644
index 0000000..2ac9653
--- /dev/null
+++ b/contrib/test_decoding/Makefile
@@ -0,0 +1,16 @@
+# contrib/test_decoding/Makefile
+
+MODULE_big = test_decoding
+OBJS = test_decoding.o
+
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/test_decoding
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
new file mode 100644
index 0000000..fb9a240
--- /dev/null
+++ b/contrib/test_decoding/test_decoding.c
@@ -0,0 +1,322 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_decoding.c
+ *		  example output plugin for the logical replication functionality
+ *
+ * Copyright (c) 2012-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/test_decoding/test_decoding.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sysattr.h"
+
+#include "catalog/pg_class.h"
+#include "catalog/pg_type.h"
+#include "catalog/index.h"
+
+#include "nodes/parsenodes.h"
+
+#include "replication/output_plugin.h"
+#include "replication/logical.h"
+
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/relcache.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+
+PG_MODULE_MAGIC;
+
+void		_PG_init(void);
+
+typedef struct
+{
+	MemoryContext context;
+	bool		include_xids;
+} TestDecodingData;
+
+/* These must be available to pg_dlsym() */
+extern void pg_decode_init(LogicalDecodingContext *ctx, bool is_init);
+extern void pg_decode_begin_txn(LogicalDecodingContext *ctx,
+					ReorderBufferTXN *txn);
+extern void pg_decode_commit_txn(LogicalDecodingContext *ctx,
+					 ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
+extern void pg_decode_change(LogicalDecodingContext *ctx,
+				 ReorderBufferTXN *txn, Relation rel,
+				 ReorderBufferChange *change);
+
+void
+_PG_init(void)
+{
+}
+
+/* initialize this plugin */
+void
+pg_decode_init(LogicalDecodingContext *ctx, bool is_init)
+{
+	ListCell   *option;
+	TestDecodingData *data;
+
+	AssertVariableIsOfType(&pg_decode_init, LogicalDecodeInitCB);
+
+	data = palloc(sizeof(TestDecodingData));
+	data->context = AllocSetContextCreate(TopMemoryContext,
+										  "text conversion context",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+	data->include_xids = true;
+
+	ctx->output_plugin_private = data;
+
+	foreach(option, ctx->output_plugin_options)
+	{
+		DefElem    *elem = lfirst(option);
+
+		Assert(elem->arg == NULL || IsA(elem->arg, String));
+
+		if (strcmp(elem->defname, "hide-xids") == 0)
+		{
+			/* FIXME: parse argument */
+			data->include_xids = false;
+		}
+		else
+		{
+			elog(WARNING, "option %s = %s is unknown",
+				 elem->defname, elem->arg ? strVal(elem->arg) : "(null)");
+		}
+	}
+}
+
+/* BEGIN callback */
+void
+pg_decode_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+	TestDecodingData *data = ctx->output_plugin_private;
+
+	AssertVariableIsOfType(&pg_decode_begin_txn, LogicalDecodeBeginCB);
+
+	ctx->prepare_write(ctx, txn->end_lsn, txn->xid);
+	if (data->include_xids)
+		appendStringInfo(ctx->out, "BEGIN %u", txn->xid);
+	else
+		appendStringInfoString(ctx->out, "BEGIN");
+	ctx->write(ctx, txn->end_lsn, txn->xid);
+}
+
+/* COMMIT callback */
+void
+pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	TestDecodingData *data = ctx->output_plugin_private;
+
+	AssertVariableIsOfType(&pg_decode_commit_txn, LogicalDecodeCommitCB);
+
+	ctx->prepare_write(ctx, txn->end_lsn, txn->xid);
+	if (data->include_xids)
+		appendStringInfo(ctx->out, "COMMIT %u", txn->xid);
+	else
+		appendStringInfoString(ctx->out, "COMMIT");
+	ctx->write(ctx, txn->end_lsn, txn->xid);
+}
+
+/* print the tuple 'tuple' into the StringInfo s */
+static void
+tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple)
+{
+	int			natt;
+	Oid			oid;
+
+	/* print oid of tuple, it's not included in the TupleDesc */
+	if ((oid = HeapTupleHeaderGetOid(tuple->t_data)) != InvalidOid)
+	{
+		appendStringInfo(s, " oid[oid]:%u", oid);
+	}
+
+	/* print all columns individually */
+	for (natt = 0; natt < tupdesc->natts; natt++)
+	{
+		Form_pg_attribute attr; /* the attribute itself */
+		Oid			typid;		/* type of current attribute */
+		HeapTuple	type_tuple; /* information about a type */
+		Form_pg_type type_form;
+		Oid			typoutput;	/* output function */
+		bool		typisvarlena;
+		Datum		origval;	/* possibly toasted Datum */
+		Datum		val;		/* definitely detoasted Datum */
+		char	   *outputstr = NULL;
+		bool		isnull;		/* column is null? */
+
+		attr = tupdesc->attrs[natt];
+
+		/*
+		 * don't print dropped columns, we can't be sure everything is
+		 * available for them
+		 */
+		if (attr->attisdropped)
+			continue;
+
+		/*
+		 * Don't print system columns, oid will already have been printed if
+		 * present.
+		 */
+		if (attr->attnum < 0)
+			continue;
+
+		typid = attr->atttypid;
+
+		/* gather type name */
+		type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+		if (!HeapTupleIsValid(type_tuple))
+			elog(ERROR, "cache lookup failed for type %u", typid);
+		type_form = (Form_pg_type) GETSTRUCT(type_tuple);
+
+		/* print attribute name */
+		appendStringInfoChar(s, ' ');
+		appendStringInfoString(s, NameStr(attr->attname));
+
+		/* print attribute type */
+		appendStringInfoChar(s, '[');
+		appendStringInfoString(s, NameStr(type_form->typname));
+		appendStringInfoChar(s, ']');
+
+		/* query output function */
+		getTypeOutputInfo(typid,
+						  &typoutput, &typisvarlena);
+
+		ReleaseSysCache(type_tuple);
+
+		/* get Datum from tuple */
+		origval = fastgetattr(tuple, natt + 1, tupdesc, &isnull);
+
+		if (isnull)
+			outputstr = "(null)";
+		else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval))
+			outputstr = "(unchanged-toast-datum)";
+		else if (typisvarlena)
+			val = PointerGetDatum(PG_DETOAST_DATUM(origval));
+		else
+			val = origval;
+
+		/* call output function if necessary */
+		if (outputstr == NULL)
+			outputstr = OidOutputFunctionCall(typoutput, val);
+
+		/* print data */
+		appendStringInfoChar(s, ':');
+		appendStringInfoString(s, outputstr);
+	}
+}
+
+/*
+ * callback for individual changed tuples
+ */
+void
+pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+				 Relation relation, ReorderBufferChange *change)
+{
+	TestDecodingData *data;
+	Form_pg_class class_form;
+	TupleDesc	tupdesc;
+	MemoryContext old;
+
+	AssertVariableIsOfType(&pg_decode_change, LogicalDecodeChangeCB);
+
+	data = ctx->output_plugin_private;
+	class_form = RelationGetForm(relation);
+	tupdesc = RelationGetDescr(relation);
+
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	ctx->prepare_write(ctx, change->lsn, txn->xid);
+
+	appendStringInfoString(ctx->out, "table \"");
+	appendStringInfoString(ctx->out, NameStr(class_form->relname));
+	appendStringInfoString(ctx->out, "\":");
+
+	switch (change->action)
+	{
+		case REORDER_BUFFER_CHANGE_INSERT:
+			appendStringInfoString(ctx->out, " INSERT:");
+			if (change->newtuple == NULL)
+				appendStringInfoString(ctx->out, " (no-tuple-data)");
+			else
+				tuple_to_stringinfo(ctx->out, tupdesc, &change->newtuple->tuple);
+			break;
+		case REORDER_BUFFER_CHANGE_UPDATE:
+			appendStringInfoString(ctx->out, " UPDATE:");
+			if (change->oldtuple != NULL)
+			{
+				Relation	indexrel;
+				TupleDesc	indexdesc;
+
+				appendStringInfoString(ctx->out, " old-pkey:");
+				RelationGetIndexList(relation);
+
+				if (!OidIsValid(relation->rd_primary))
+				{
+					elog(LOG, "tuple in table with oid: %u without primary key",
+						 RelationGetRelid(relation));
+					break;
+				}
+
+				indexrel = RelationIdGetRelation(relation->rd_primary);
+
+				indexdesc = RelationGetDescr(indexrel);
+
+				tuple_to_stringinfo(ctx->out, indexdesc, &change->oldtuple->tuple);
+
+				RelationClose(indexrel);
+				appendStringInfoString(ctx->out, " new-tuple:");
+			}
+
+			if (change->newtuple == NULL)
+				appendStringInfoString(ctx->out, " (no-tuple-data)");
+			else
+				tuple_to_stringinfo(ctx->out, tupdesc, &change->newtuple->tuple);
+
+			break;
+		case REORDER_BUFFER_CHANGE_DELETE:
+			appendStringInfoString(ctx->out, " DELETE:");
+
+			/* if there was no PK, we only know that a delete happened */
+			if (change->oldtuple == NULL)
+				appendStringInfoString(ctx->out, " (no-tuple-data)");
+			/* In DELETE, only the PK is present; display that */
+			else
+			{
+				Relation	indexrel;
+
+				/* make sure rd_primary is set */
+				RelationGetIndexList(relation);
+
+				if (!OidIsValid(relation->rd_primary))
+				{
+					elog(LOG, "tuple in table with oid: %u without primary key",
+						 RelationGetRelid(relation));
+					break;
+				}
+
+				indexrel = RelationIdGetRelation(relation->rd_primary);
+
+				tuple_to_stringinfo(ctx->out, RelationGetDescr(indexrel),
+									&change->oldtuple->tuple);
+
+				RelationClose(indexrel);
+			}
+			break;
+	}
+
+	MemoryContextSwitchTo(old);
+	MemoryContextReset(data->context);
+
+	ctx->write(ctx, change->lsn, txn->xid);
+}
-- 
1.8.4.21.g992c386.dirty

0006-wal_decoding-pg_receivellog-Introduce-pg_receivexlog.patchtext/x-patch; charset=us-asciiDownload
>From e4e5016a34411c2a18cccd6ab4b3f749fe283ce1 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:30 +0200
Subject: [PATCH 6/8] wal_decoding: pg_receivellog: Introduce pg_receivexlog
 equivalent for logical changes

---
 src/backend/utils/cache/relcache.c     |   3 +
 src/bin/pg_basebackup/.gitignore       |   1 +
 src/bin/pg_basebackup/Makefile         |  11 +-
 src/bin/pg_basebackup/pg_receivellog.c | 860 +++++++++++++++++++++++++++++++++
 src/bin/pg_basebackup/receivelog.c     | 137 +-----
 src/bin/pg_basebackup/receivelog.h     |   2 +
 src/bin/pg_basebackup/streamutil.c     | 123 ++++-
 src/bin/pg_basebackup/streamutil.h     |  10 +
 8 files changed, 1023 insertions(+), 124 deletions(-)
 create mode 100644 src/bin/pg_basebackup/pg_receivellog.c

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5d304ce..1b66e64 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1577,6 +1577,9 @@ RelationIdGetRelation(Oid relationId)
 {
 	Relation	rd;
 
+	/* Make sure we're in a xact, even if this ends up being a cache hit */
+	Assert(IsTransactionState());
+
 	/*
 	 * first try to find reldesc in the cache
 	 */
diff --git a/src/bin/pg_basebackup/.gitignore b/src/bin/pg_basebackup/.gitignore
index 1334a1f..eb2978c 100644
--- a/src/bin/pg_basebackup/.gitignore
+++ b/src/bin/pg_basebackup/.gitignore
@@ -1,2 +1,3 @@
 /pg_basebackup
 /pg_receivexlog
+/pg_receivellog
diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile
index a707c93..c251249 100644
--- a/src/bin/pg_basebackup/Makefile
+++ b/src/bin/pg_basebackup/Makefile
@@ -20,7 +20,7 @@ override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 
 OBJS=receivelog.o streamutil.o $(WIN32RES)
 
-all: pg_basebackup pg_receivexlog
+all: pg_basebackup pg_receivexlog pg_receivellog
 
 pg_basebackup: pg_basebackup.o $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) pg_basebackup.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
@@ -28,9 +28,13 @@ pg_basebackup: pg_basebackup.o $(OBJS) | submake-libpq submake-libpgport
 pg_receivexlog: pg_receivexlog.o $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) pg_receivexlog.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
+pg_receivellog: pg_receivellog.o $(OBJS) | submake-libpq submake-libpgport
+	$(CC) $(CFLAGS) pg_receivellog.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
 install: all installdirs
 	$(INSTALL_PROGRAM) pg_basebackup$(X) '$(DESTDIR)$(bindir)/pg_basebackup$(X)'
 	$(INSTALL_PROGRAM) pg_receivexlog$(X) '$(DESTDIR)$(bindir)/pg_receivexlog$(X)'
+	$(INSTALL_PROGRAM) pg_receivellog$(X) '$(DESTDIR)$(bindir)/pg_receivellog$(X)'
 
 installdirs:
 	$(MKDIR_P) '$(DESTDIR)$(bindir)'
@@ -38,6 +42,9 @@ installdirs:
 uninstall:
 	rm -f '$(DESTDIR)$(bindir)/pg_basebackup$(X)'
 	rm -f '$(DESTDIR)$(bindir)/pg_receivexlog$(X)'
+	rm -f '$(DESTDIR)$(bindir)/pg_receivellog$(X)'
 
 clean distclean maintainer-clean:
-	rm -f pg_basebackup$(X) pg_receivexlog$(X) $(OBJS) pg_basebackup.o pg_receivexlog.o
+	rm -f pg_basebackup$(X) pg_receivexlog$(X) pg_receivellog$(X) \
+		pg_basebackup.o pg_receivexlog.o pg_receivellog.o \
+		$(OBJS)
diff --git a/src/bin/pg_basebackup/pg_receivellog.c b/src/bin/pg_basebackup/pg_receivellog.c
new file mode 100644
index 0000000..fc81608
--- /dev/null
+++ b/src/bin/pg_basebackup/pg_receivellog.c
@@ -0,0 +1,860 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_receivellog.c - receive streaming logical log data and write it
+ *					  to a local file.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/bin/pg_basebackup/pg_receivellog.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "streamutil.h"
+
+#include "getopt_long.h"
+
+#include "libpq-fe.h"
+#include "libpq/pqsignal.h"
+
+#include "access/xlog_internal.h"
+#include "common/fe_memutils.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Time to sleep between reconnection attempts */
+#define RECONNECT_SLEEP_TIME 5
+
+/* Global Options */
+static char    *outfile = NULL;
+static int		verbose = 0;
+static int		noloop = 0;
+static int		standby_message_timeout = 10 * 1000;		/* 10 sec = default */
+static const char *slot = NULL;
+static XLogRecPtr startpos = InvalidXLogRecPtr;
+static bool 	do_init_slot = false;
+static bool 	do_start_slot = false;
+static bool 	do_stop_slot = false;
+
+/* filled pairwise with option, value. value may be NULL */
+static char	  **options;
+static size_t	noptions = 0;
+static const char *plugin = "test_decoding";
+
+/* Global State */
+static int		outfd = -1;
+static volatile bool time_to_abort = false;
+
+static void usage(void);
+static void StreamLog();
+
+static void
+usage(void)
+{
+	printf(_("%s receives PostgreSQL logical change stream.\n\n"),
+		   progname);
+	printf(_("Usage:\n"));
+	printf(_("  %s [OPTION]...\n"), progname);
+	printf(_("\nOptions:\n"));
+	printf(_("  -f, --file=FILE        receive log into this file. - for stdout\n"));
+	printf(_("  -n, --no-loop          do not loop on connection lost\n"));
+	printf(_("  -v, --verbose          output verbose messages\n"));
+	printf(_("  -V, --version          output version information, then exit\n"));
+	printf(_("  -?, --help             show this help, then exit\n"));
+	printf(_("\nConnection options:\n"));
+	printf(_("  -d, --database=DBNAME  database to connect to\n"));
+	printf(_("  -h, --host=HOSTNAME    database server host or socket directory\n"));
+	printf(_("  -p, --port=PORT        database server port number\n"));
+	printf(_("  -U, --username=NAME    connect as specified database user\n"));
+	printf(_("  -w, --no-password      never prompt for password\n"));
+	printf(_("  -W, --password         force password prompt (should happen automatically)\n"));
+	printf(_("\nReplication options:\n"));
+	printf(_("  -o, --option=NAME[=VALUE]\n"
+			 "                         Specify option NAME with optional value VAL, to be passed\n"
+			 "                         to the output plugin\n"));
+	printf(_("  -P, --plugin=PLUGIN    use output plugin PLUGIN (defaults to test_decoding)\n"));
+	printf(_("  -s, --status-interval=INTERVAL\n"
+			 "                         time between status packets sent to server (in seconds)\n"));
+	printf(_("  -S, --slot=SLOT        use existing replication slot SLOT instead of starting a new one\n"));
+	printf(_("  -I, --startpos=PTR     Where in an existing slot should the streaming start"));
+	printf(_("\nAction to be performed:\n"));
+	printf(_("      --init             initiate a new replication slot (for the slotname see --slot)\n"));
+	printf(_("      --start            start streaming in a replication slot (for the slotname see --slot)\n"));
+	printf(_("      --stop             stop the replication slot (for the slotname see --slot)\n"));
+	printf(_("\nReport bugs to <pgsql-bugs@postgresql.org>.\n"));
+}
+
+/*
+ * Send a Standby Status Update message to server.
+ */
+static bool
+sendFeedback(PGconn *conn, XLogRecPtr blockpos, int64 now, bool force, bool replyRequested)
+{
+	char		replybuf[1 + 8 + 8 + 8 + 8 + 1];
+	int			len = 0;
+
+	/*
+	 * we normally don't want to send superflous feedbacks, but if
+	 * it's because of a timeout we need to, otherwise
+	 * replication_timeout will kill us.
+	 */
+	if (blockpos == startpos && !force)
+		return true;
+
+	if (verbose)
+		fprintf(stderr,
+				_("%s: confirming flush up to %X/%X (slot %s)\n"),
+				progname, (uint32) (blockpos >> 32), (uint32) blockpos,
+				slot);
+
+	replybuf[len] = 'r';
+	len += 1;
+	fe_sendint64(blockpos, &replybuf[len]);		/* write */
+	len += 8;
+	fe_sendint64(blockpos, &replybuf[len]);		/* flush */
+	len += 8;
+	fe_sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* apply */
+	len += 8;
+	fe_sendint64(now, &replybuf[len]);		/* sendTime */
+	len += 8;
+	replybuf[len] = replyRequested ? 1 : 0;		/* replyRequested */
+	len += 1;
+
+	startpos = blockpos;
+
+	if (PQputCopyData(conn, replybuf, len) <= 0 || PQflush(conn))
+	{
+		fprintf(stderr, _("%s: could not send feedback packet: %s"),
+				progname, PQerrorMessage(conn));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Start the log streaming
+ */
+static void
+StreamLog(void)
+{
+	PGresult   *res;
+	char		query[512];
+	char	   *copybuf = NULL;
+	int64		last_status = -1;
+	XLogRecPtr	logoff = InvalidXLogRecPtr;
+	int			written;
+	int			i;
+
+	/*
+	 * Connect in replication mode to the server
+	 */
+	if (!conn)
+		conn = GetConnection();
+	if (!conn)
+		/* Error message already written in GetConnection() */
+		return;
+
+	/*
+	 * Start the replication
+	 */
+	if (verbose)
+		fprintf(stderr,
+				_("%s: starting log streaming at %X/%X (slot %s)\n"),
+				progname, (uint32) (startpos >> 32), (uint32) startpos,
+				slot);
+
+	/* Initiate the replication stream at specified location */
+	written = snprintf(query, sizeof(query), "START_LOGICAL_REPLICATION \"%s\" %X/%X",
+			 slot, (uint32) (startpos >> 32), (uint32) startpos);
+
+	/*
+	 * add options to string, if present
+	 * Oh, if we just had stringinfo in src/common...
+	 */
+	if (noptions)
+		written += snprintf(query + written, sizeof(query) - written, " (");
+
+	for (i = 0; i < noptions; i++)
+	{
+		/* separator */
+		if (i > 0)
+			written += snprintf(query + written, sizeof(query) - written, ", ");
+
+		/* write option name */
+		written += snprintf(query + written, sizeof(query) - written, "\"%s\"",
+							options[(i * 2)]);
+
+		if (written >= sizeof(query) - 1)
+		{
+			fprintf(stderr, _("%s: option string too long\n"), progname);
+			exit(1); /* no point in retrying, fatal error */
+		}
+
+		/* write option name if specified */
+		if (options[(i * 2) + 1] != NULL)
+		{
+			written += snprintf(query + written, sizeof(query) - written, " '%s'",
+								options[(i * 2) + 1]);
+
+			if (written >= sizeof(query) - 1)
+			{
+				fprintf(stderr, _("%s: option string too long\n"), progname);
+				exit(1); /* no point in retrying, fatal error */
+			}
+		}
+	}
+
+	if (noptions)
+	{
+		written += snprintf(query + written, sizeof(query) - written, ")");
+		if (written >= sizeof(query) - 1)
+		{
+			fprintf(stderr, _("%s: option string too long\n"), progname);
+			exit(1); /* no point in retrying, fatal error */
+		}
+	}
+
+	res = PQexec(conn, query);
+	if (PQresultStatus(res) != PGRES_COPY_BOTH)
+	{
+		fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
+				progname, query, PQresultErrorMessage(res));
+		PQclear(res);
+		goto error;
+	}
+	PQclear(res);
+
+	if (verbose)
+		fprintf(stderr,
+				_("%s: initiated streaming\n"),
+				progname);
+
+	while (!time_to_abort)
+	{
+		int			r;
+		int			bytes_left;
+		int			bytes_written;
+		int64		now;
+		int			hdr_len;
+
+		if (copybuf != NULL)
+		{
+			PQfreemem(copybuf);
+			copybuf = NULL;
+		}
+
+		/*
+		 * Potentially send a status message to the master
+		 */
+		now = feGetCurrentTimestamp();
+		if (standby_message_timeout > 0 &&
+			feTimestampDifferenceExceeds(last_status, now,
+										 standby_message_timeout))
+		{
+			/* Time to send feedback! */
+			if (!sendFeedback(conn, logoff, now, true, false))
+				goto error;
+
+			last_status = now;
+		}
+
+		r = PQgetCopyData(conn, &copybuf, 1);
+		if (r == 0)
+		{
+			/*
+			 * In async mode, and no data available. We block on reading but
+			 * not more than the specified timeout, so that we can send a
+			 * response back to the client.
+			 */
+			fd_set		input_mask;
+			struct timeval timeout;
+			struct timeval *timeoutptr;
+
+			FD_ZERO(&input_mask);
+			FD_SET(PQsocket(conn), &input_mask);
+			if (standby_message_timeout)
+			{
+				int64		targettime;
+				long		secs;
+				int			usecs;
+
+				targettime = last_status + (standby_message_timeout - 1) *
+					((int64) 1000);
+				feTimestampDifference(now,
+									  targettime,
+									  &secs,
+									  &usecs);
+				if (secs <= 0)
+					timeout.tv_sec = 1; /* Always sleep at least 1 sec */
+				else
+					timeout.tv_sec = secs;
+				timeout.tv_usec = usecs;
+				timeoutptr = &timeout;
+			}
+			else
+				timeoutptr = NULL;
+
+			r = select(PQsocket(conn) + 1, &input_mask, NULL, NULL, timeoutptr);
+			if (r == 0 || (r < 0 && errno == EINTR))
+			{
+				/*
+				 * Got a timeout or signal. Continue the loop and either
+				 * deliver a status packet to the server or just go back into
+				 * blocking.
+				 */
+				continue;
+			}
+			else if (r < 0)
+			{
+				fprintf(stderr, _("%s: select() failed: %s\n"),
+						progname, strerror(errno));
+				goto error;
+			}
+			/* Else there is actually data on the socket */
+			if (PQconsumeInput(conn) == 0)
+			{
+				fprintf(stderr,
+						_("%s: could not receive data from WAL stream: %s"),
+						progname, PQerrorMessage(conn));
+				goto error;
+			}
+			continue;
+		}
+		if (r == -1)
+			/* End of copy stream */
+			break;
+		if (r == -2)
+		{
+			fprintf(stderr, _("%s: could not read COPY data: %s"),
+					progname, PQerrorMessage(conn));
+			goto error;
+		}
+
+		/* Check the message type. */
+		if (copybuf[0] == 'k')
+		{
+			int			pos;
+			bool		replyRequested;
+
+			/*
+			 * Parse the keepalive message, enclosed in the CopyData message.
+			 * We just check if the server requested a reply, and ignore the
+			 * rest.
+			 */
+			pos = 1;			/* skip msgtype 'k' */
+			pos += 8;			/* skip walEnd */
+			pos += 8;			/* skip sendTime */
+
+			if (r < pos + 1)
+			{
+				fprintf(stderr, _("%s: streaming header too small: %d\n"),
+						progname, r);
+				goto error;
+			}
+			replyRequested = copybuf[pos];
+
+			/* If the server requested an immediate reply, send one. */
+			if (replyRequested)
+			{
+				now = feGetCurrentTimestamp();
+				if (!sendFeedback(conn, logoff, now, false, false))
+					goto error;
+				last_status = now;
+			}
+			continue;
+		}
+		else if (copybuf[0] != 'w')
+		{
+			fprintf(stderr, _("%s: unrecognized streaming header: \"%c\"\n"),
+					progname, copybuf[0]);
+			goto error;
+		}
+
+
+		/*
+		 * Read the header of the XLogData message, enclosed in the CopyData
+		 * message. We only need the WAL location field (dataStart), the rest
+		 * of the header is ignored.
+		 */
+		hdr_len = 1;			/* msgtype 'w' */
+		hdr_len += 8;			/* dataStart */
+		hdr_len += 8;			/* walEnd */
+		hdr_len += 8;			/* sendTime */
+		if (r < hdr_len + 1)
+		{
+			fprintf(stderr, _("%s: streaming header too small: %d\n"),
+					progname, r);
+			goto error;
+		}
+
+		/* Extract WAL location for this block */
+		{
+			XLogRecPtr	temp = fe_recvint64(&copybuf[1]);
+
+			logoff = Max(temp, logoff);
+		}
+
+		if (outfd == -1 && strcmp(outfile, "-") == 0)
+		{
+			outfd = fileno(stdout);
+		}
+		else if (outfd == -1)
+		{
+			outfd = open(outfile, O_CREAT | O_APPEND | O_WRONLY | PG_BINARY,
+						 S_IRUSR | S_IWUSR);
+			if (outfd == -1)
+			{
+				fprintf(stderr,
+						_("%s: could not open log file \"%s\": %s\n"),
+						progname, outfile, strerror(errno));
+				goto error;
+			}
+		}
+
+		bytes_left = r - hdr_len;
+		bytes_written = 0;
+
+
+		while (bytes_left)
+		{
+			int			ret;
+
+			ret = write(outfd,
+						copybuf + hdr_len + bytes_written,
+						bytes_left);
+
+			if (ret < 0)
+			{
+				fprintf(stderr,
+				  _("%s: could not write %u bytes to log file \"%s\": %s\n"),
+						progname, bytes_left, outfile,
+						strerror(errno));
+				goto error;
+			}
+
+			/* Write was successful, advance our position */
+			bytes_written += ret;
+			bytes_left -= ret;
+		}
+
+		if (write(outfd, "\n", 1) != 1)
+		{
+			fprintf(stderr,
+				  _("%s: could not write %u bytes to log file \"%s\": %s\n"),
+					progname, 1, outfile,
+					strerror(errno));
+			goto error;
+		}
+	}
+
+	res = PQgetResult(conn);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr,
+				_("%s: unexpected termination of replication stream: %s"),
+				progname, PQresultErrorMessage(res));
+		goto error;
+	}
+	PQclear(res);
+
+	if (copybuf != NULL)
+		PQfreemem(copybuf);
+
+	if (outfd != -1 && close(outfd) != 0)
+		fprintf(stderr, _("%s: could not close file \"%s\": %s\n"),
+				progname, outfile, strerror(errno));
+	outfd = -1;
+error:
+	PQfinish(conn);
+	conn = NULL;
+}
+
+/*
+ * When sigint is called, just tell the system to exit at the next possible
+ * moment.
+ */
+#ifndef WIN32
+
+static void
+sigint_handler(int signum)
+{
+	time_to_abort = true;
+}
+#endif
+
+int
+main(int argc, char **argv)
+{
+	PGresult   *res;
+	static struct option long_options[] = {
+/* general options */
+		{"file", required_argument, NULL, 'f'},
+		{"no-loop", no_argument, NULL, 'n'},
+		{"verbose", no_argument, NULL, 'v'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, '?'},
+/* connnection options */
+		{"database", required_argument, NULL, 'd'},
+		{"host", required_argument, NULL, 'h'},
+		{"port", required_argument, NULL, 'p'},
+		{"username", required_argument, NULL, 'U'},
+		{"no-password", no_argument, NULL, 'w'},
+		{"password", no_argument, NULL, 'W'},
+/* replication options */
+		{"option", required_argument, NULL, 'o'},
+		{"plugin", required_argument, NULL, 'P'},
+		{"status-interval", required_argument, NULL, 's'},
+		{"slot", required_argument, NULL, 'S'},
+		{"startpos", required_argument, NULL, 'I'},
+/* action */
+		{"init", no_argument, NULL, 1},
+		{"start", no_argument, NULL, 2},
+		{"stop", no_argument, NULL, 3},
+		{NULL, 0, NULL, 0}
+	};
+	int			c;
+	int			option_index;
+	uint32		hi,
+				lo;
+
+	progname = get_progname(argv[0]);
+	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_receivellog"));
+
+	if (argc > 1)
+	{
+		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+		{
+			usage();
+			exit(0);
+		}
+		else if (strcmp(argv[1], "-V") == 0 ||
+				 strcmp(argv[1], "--version") == 0)
+		{
+			puts("pg_receivellog (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
+	}
+
+	while ((c = getopt_long(argc, argv, "f:nvd:h:o:p:U:wWP:s:S:",
+							long_options, &option_index)) != -1)
+	{
+		switch (c)
+		{
+/* general options */
+			case 'f':
+				outfile = pg_strdup(optarg);
+				break;
+			case 'n':
+				noloop = 1;
+				break;
+			case 'v':
+				verbose++;
+				break;
+/* connnection options */
+			case 'd':
+				dbname = pg_strdup(optarg);
+				break;
+			case 'h':
+				dbhost = pg_strdup(optarg);
+				break;
+			case 'p':
+				if (atoi(optarg) <= 0)
+				{
+					fprintf(stderr, _("%s: invalid port number \"%s\"\n"),
+							progname, optarg);
+					exit(1);
+				}
+				dbport = pg_strdup(optarg);
+				break;
+			case 'U':
+				dbuser = pg_strdup(optarg);
+				break;
+			case 'w':
+				dbgetpassword = -1;
+				break;
+			case 'W':
+				dbgetpassword = 1;
+				break;
+/* replication options */
+			case 'o':
+				{
+					char *data = pg_strdup(optarg);
+					char *val = strchr(data, '=');
+
+					if (val != NULL)
+					{
+						/* remove =; separate data from val */
+						*val = '\0';
+						val++;
+					}
+
+					noptions += 1;
+					options = pg_realloc(options, sizeof(char*) * noptions * 2);
+
+					options[(noptions - 1) * 2] = data;
+					options[(noptions - 1) * 2 + 1] = val;
+				}
+
+				break;
+			case 'P':
+				plugin = pg_strdup(optarg);
+				break;
+			case 's':
+				standby_message_timeout = atoi(optarg) * 1000;
+				if (standby_message_timeout < 0)
+				{
+					fprintf(stderr, _("%s: invalid status interval \"%s\"\n"),
+							progname, optarg);
+					exit(1);
+				}
+				break;
+			case 'S':
+				slot = pg_strdup(optarg);
+				break;
+			case 'I':
+				if (sscanf(optarg, "%X/%X", &hi, &lo) != 2)
+				{
+					fprintf(stderr,
+							_("%s: could not parse start position \"%s\"\n"),
+							progname, optarg);
+					exit(1);
+				}
+				startpos = ((uint64) hi) << 32 | lo;
+				break;
+/* action */
+			case 1:
+				do_init_slot = true;
+				break;
+			case 2:
+				do_start_slot = true;
+				break;
+			case 3:
+				do_stop_slot = true;
+				break;
+
+			default:
+
+				/*
+				 * getopt_long already emitted a complaint
+				 */
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+						progname);
+				exit(1);
+		}
+	}
+
+	/*
+	 * Any non-option arguments?
+	 */
+	if (optind < argc)
+	{
+		fprintf(stderr,
+				_("%s: too many command-line arguments (first is \"%s\")\n"),
+				progname, argv[optind]);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	/*
+	 * Required arguments
+	 */
+	if (slot == NULL)
+	{
+		fprintf(stderr, _("%s: no slot specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (!do_stop_slot && outfile == NULL)
+	{
+		fprintf(stderr, _("%s: no target file specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (!do_stop_slot && dbname == NULL)
+	{
+		fprintf(stderr, _("%s: no database specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (!do_stop_slot && !do_init_slot && !do_start_slot)
+	{
+		fprintf(stderr, _("%s: at least one action needs to be specified\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (do_stop_slot && (do_init_slot || do_start_slot))
+	{
+		fprintf(stderr, _("%s: --stop cannot be combined with --init or --start\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if (startpos && (do_init_slot || do_stop_slot))
+	{
+		fprintf(stderr, _("%s: --startpos cannot be combined with --init or --stop\n"), progname);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+#ifndef WIN32
+	pqsignal(SIGINT, sigint_handler);
+#endif
+
+	/*
+	 * don't really need this but it actually helps to get more precise error
+	 * messages about authentication, required GUCs and such without starting
+	 * to loop around connection attempts lateron.
+	 */
+	{
+		conn = GetConnection();
+		if (!conn)
+			/* Error message already written in GetConnection() */
+			exit(1);
+
+		/*
+		 * Run IDENTIFY_SYSTEM so we can get the timeline and current xlog
+		 * position.
+		 */
+		res = PQexec(conn, "IDENTIFY_SYSTEM");
+		if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		{
+			fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
+					progname, "IDENTIFY_SYSTEM", PQerrorMessage(conn));
+			disconnect_and_exit(1);
+		}
+
+		if (PQntuples(res) != 1 || PQnfields(res) != 4)
+		{
+			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, 4);
+			disconnect_and_exit(1);
+		}
+		PQclear(res);
+	}
+
+
+	/*
+	 * stop a replication slot
+	 */
+	if (do_stop_slot)
+	{
+		char		query[256];
+
+		if (verbose)
+			fprintf(stderr,
+					_("%s: init replication slot \"%s\"\n"),
+					progname, slot);
+
+		snprintf(query, sizeof(query), "FREE_LOGICAL_REPLICATION \"%s\"",
+				 slot);
+		res = PQexec(conn, query);
+		if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
+					progname, query, PQerrorMessage(conn));
+			disconnect_and_exit(1);
+		}
+
+		if (PQntuples(res) != 0 || PQnfields(res) != 0)
+		{
+			fprintf(stderr,
+					_("%s: could not stop logical rep: got %d rows and %d fields, expected %d rows and %d fields\n"),
+					progname, PQntuples(res), PQnfields(res), 0, 0);
+			disconnect_and_exit(1);
+		}
+
+		PQclear(res);
+		disconnect_and_exit(0);
+	}
+
+	/*
+	 * init a replication slot
+	 */
+	if (do_init_slot)
+	{
+		char		query[256];
+
+		if (verbose)
+			fprintf(stderr,
+					_("%s: init replication slot \"%s\"\n"),
+					progname, slot);
+
+		snprintf(query, sizeof(query), "INIT_LOGICAL_REPLICATION \"%s\" \"%s\"",
+				 slot, plugin);
+
+		res = PQexec(conn, query);
+		if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		{
+			fprintf(stderr, _("%s: could not send replication command \"%s\": %s"),
+					progname, query, PQerrorMessage(conn));
+			disconnect_and_exit(1);
+		}
+
+		if (PQntuples(res) != 1 || PQnfields(res) != 4)
+		{
+			fprintf(stderr,
+					_("%s: could not init logical rep: got %d rows and %d fields, expected %d rows and %d fields\n"),
+					progname, PQntuples(res), PQnfields(res), 1, 4);
+			disconnect_and_exit(1);
+		}
+
+		if (sscanf(PQgetvalue(res, 0, 1), "%X/%X", &hi, &lo) != 2)
+		{
+			fprintf(stderr,
+					_("%s: could not parse log location \"%s\"\n"),
+					progname, PQgetvalue(res, 0, 1));
+			disconnect_and_exit(1);
+		}
+		startpos = ((uint64) hi) << 32 | lo;
+
+		slot = strdup(PQgetvalue(res, 0, 0));
+		PQclear(res);
+	}
+
+
+	if (!do_start_slot)
+		disconnect_and_exit(0);
+
+	while (true)
+	{
+		StreamLog();
+		if (time_to_abort)
+		{
+			/*
+			 * We've been Ctrl-C'ed. That's not an error, so exit without an
+			 * errorcode.
+			 */
+			disconnect_and_exit(0);
+		}
+		else if (noloop)
+		{
+			fprintf(stderr, _("%s: disconnected.\n"), progname);
+			exit(1);
+		}
+		else
+		{
+			fprintf(stderr,
+			/* translator: check source for value for %d */
+					_("%s: disconnected. Waiting %d seconds to try again.\n"),
+					progname, RECONNECT_SLEEP_TIME);
+			pg_usleep(RECONNECT_SLEEP_TIME * 1000000);
+		}
+	}
+}
diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c
index 22a5340..f027e1e 100644
--- a/src/bin/pg_basebackup/receivelog.c
+++ b/src/bin/pg_basebackup/receivelog.c
@@ -11,21 +11,18 @@
  *		  src/bin/pg_basebackup/receivelog.c
  *-------------------------------------------------------------------------
  */
+
 #include "postgres_fe.h"
 
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <unistd.h>
-/* for ntohl/htonl */
-#include <netinet/in.h>
-#include <arpa/inet.h>
+/* local includes */
+#include "receivelog.h"
+#include "streamutil.h"
 
 #include "libpq-fe.h"
 #include "access/xlog_internal.h"
 
-#include "receivelog.h"
-#include "streamutil.h"
+#include <sys/stat.h>
+#include <unistd.h>
 
 
 /* fd and filename for currently open WAL file */
@@ -193,63 +190,6 @@ close_walfile(char *basedir, char *partial_suffix)
 
 
 /*
- * Local version of GetCurrentTimestamp(), since we are not linked with
- * backend code. The protocol always uses integer timestamps, regardless of
- * server setting.
- */
-static int64
-localGetCurrentTimestamp(void)
-{
-	int64		result;
-	struct timeval tp;
-
-	gettimeofday(&tp, NULL);
-
-	result = (int64) tp.tv_sec -
-		((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
-
-	result = (result * USECS_PER_SEC) + tp.tv_usec;
-
-	return result;
-}
-
-/*
- * Local version of TimestampDifference(), since we are not linked with
- * backend code.
- */
-static void
-localTimestampDifference(int64 start_time, int64 stop_time,
-						 long *secs, int *microsecs)
-{
-	int64		diff = stop_time - start_time;
-
-	if (diff <= 0)
-	{
-		*secs = 0;
-		*microsecs = 0;
-	}
-	else
-	{
-		*secs = (long) (diff / USECS_PER_SEC);
-		*microsecs = (int) (diff % USECS_PER_SEC);
-	}
-}
-
-/*
- * Local version of TimestampDifferenceExceeds(), since we are not
- * linked with backend code.
- */
-static bool
-localTimestampDifferenceExceeds(int64 start_time,
-								int64 stop_time,
-								int msec)
-{
-	int64		diff = stop_time - start_time;
-
-	return (diff >= msec * INT64CONST(1000));
-}
-
-/*
  * Check if a timeline history file exists.
  */
 static bool
@@ -369,47 +309,6 @@ writeTimeLineHistoryFile(char *basedir, TimeLineID tli, char *filename, char *co
 }
 
 /*
- * Converts an int64 to network byte order.
- */
-static void
-sendint64(int64 i, char *buf)
-{
-	uint32		n32;
-
-	/* High order half first, since we're doing MSB-first */
-	n32 = (uint32) (i >> 32);
-	n32 = htonl(n32);
-	memcpy(&buf[0], &n32, 4);
-
-	/* Now the low order half */
-	n32 = (uint32) i;
-	n32 = htonl(n32);
-	memcpy(&buf[4], &n32, 4);
-}
-
-/*
- * Converts an int64 from network byte order to native format.
- */
-static int64
-recvint64(char *buf)
-{
-	int64		result;
-	uint32		h32;
-	uint32		l32;
-
-	memcpy(&h32, buf, 4);
-	memcpy(&l32, buf + 4, 4);
-	h32 = ntohl(h32);
-	l32 = ntohl(l32);
-
-	result = h32;
-	result <<= 32;
-	result |= l32;
-
-	return result;
-}
-
-/*
  * Send a Standby Status Update message to server.
  */
 static bool
@@ -420,13 +319,13 @@ sendFeedback(PGconn *conn, XLogRecPtr blockpos, int64 now, bool replyRequested)
 
 	replybuf[len] = 'r';
 	len += 1;
-	sendint64(blockpos, &replybuf[len]);		/* write */
+	fe_sendint64(blockpos, &replybuf[len]);		/* write */
 	len += 8;
-	sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* flush */
+	fe_sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* flush */
 	len += 8;
-	sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* apply */
+	fe_sendint64(InvalidXLogRecPtr, &replybuf[len]);		/* apply */
 	len += 8;
-	sendint64(now, &replybuf[len]);		/* sendTime */
+	fe_sendint64(now, &replybuf[len]);		/* sendTime */
 	len += 8;
 	replybuf[len] = replyRequested ? 1 : 0;		/* replyRequested */
 	len += 1;
@@ -828,9 +727,9 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 		/*
 		 * Potentially send a status message to the master
 		 */
-		now = localGetCurrentTimestamp();
+		now = feGetCurrentTimestamp();
 		if (still_sending && standby_message_timeout > 0 &&
-			localTimestampDifferenceExceeds(last_status, now,
+			feTimestampDifferenceExceeds(last_status, now,
 											standby_message_timeout))
 		{
 			/* Time to send feedback! */
@@ -859,10 +758,10 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 				int			usecs;
 
 				targettime = last_status + (standby_message_timeout - 1) * ((int64) 1000);
-				localTimestampDifference(now,
-										 targettime,
-										 &secs,
-										 &usecs);
+				feTimestampDifference(now,
+									  targettime,
+									  &secs,
+									  &usecs);
 				if (secs <= 0)
 					timeout.tv_sec = 1; /* Always sleep at least 1 sec */
 				else
@@ -966,7 +865,7 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 			/* If the server requested an immediate reply, send one. */
 			if (replyRequested && still_sending)
 			{
-				now = localGetCurrentTimestamp();
+				now = feGetCurrentTimestamp();
 				if (!sendFeedback(conn, blockpos, now, false))
 					goto error;
 				last_status = now;
@@ -996,7 +895,7 @@ HandleCopyStream(PGconn *conn, XLogRecPtr startpos, uint32 timeline,
 						progname, r);
 				goto error;
 			}
-			blockpos = recvint64(&copybuf[1]);
+			blockpos = fe_recvint64(&copybuf[1]);
 
 			/* Extract WAL location for this block */
 			xlogoff = blockpos % XLOG_SEG_SIZE;
diff --git a/src/bin/pg_basebackup/receivelog.h b/src/bin/pg_basebackup/receivelog.h
index 7c983cd..f4789a5 100644
--- a/src/bin/pg_basebackup/receivelog.h
+++ b/src/bin/pg_basebackup/receivelog.h
@@ -1,3 +1,5 @@
+#include "libpq-fe.h"
+
 #include "access/xlogdefs.h"
 
 /*
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1dfb80f..c8d436d 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -11,17 +11,35 @@
  *-------------------------------------------------------------------------
  */
 
-#include "postgres_fe.h"
+/*
+ * We have to use postgres.h not postgres_fe.h here, because there's
+ * backend-only stuff in the datetime include files we need.  But we need a
+ * frontend-ish environment otherwise. Hence this ugly hack.
+ */
+#define FRONTEND 1
+#include "postgres.h"
+
 #include "streamutil.h"
 
+#include "common/fe_memutils.h"
+#include "utils/datetime.h"
+
 #include <stdio.h>
 #include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* for ntohl/htonl */
+#include <netinet/in.h>
+#include <arpa/inet.h>
 
 const char *progname;
 char	   *connection_string = NULL;
 char	   *dbhost = NULL;
 char	   *dbuser = NULL;
 char	   *dbport = NULL;
+char	   *dbname = NULL;
 int			dbgetpassword = 0;	/* 0=auto, -1=never, 1=always */
 static char *dbpassword = NULL;
 PGconn	   *conn = NULL;
@@ -86,10 +104,10 @@ GetConnection(void)
 	}
 
 	keywords[i] = "dbname";
-	values[i] = "replication";
+	values[i] = dbname == NULL ? "replication" : dbname;
 	i++;
 	keywords[i] = "replication";
-	values[i] = "true";
+	values[i] = dbname == NULL ? "true" : "database";
 	i++;
 	keywords[i] = "fallback_application_name";
 	values[i] = progname;
@@ -210,3 +228,102 @@ GetConnection(void)
 		return tmpconn;
 	}
 }
+
+
+/*
+ * Frontend version of GetCurrentTimestamp(), since we are not linked with
+ * backend code. The protocol always uses integer timestamps, regardless of
+ * server setting.
+ */
+int64
+feGetCurrentTimestamp(void)
+{
+	int64		result;
+	struct timeval tp;
+
+	gettimeofday(&tp, NULL);
+
+	result = (int64) tp.tv_sec -
+		((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
+
+	result = (result * USECS_PER_SEC) + tp.tv_usec;
+
+	return result;
+}
+
+/*
+ * Frontend version of TimestampDifference(), since we are not linked with
+ * backend code.
+ */
+void
+feTimestampDifference(int64 start_time, int64 stop_time,
+						 long *secs, int *microsecs)
+{
+	int64		diff = stop_time - start_time;
+
+	if (diff <= 0)
+	{
+		*secs = 0;
+		*microsecs = 0;
+	}
+	else
+	{
+		*secs = (long) (diff / USECS_PER_SEC);
+		*microsecs = (int) (diff % USECS_PER_SEC);
+	}
+}
+
+/*
+ * Frontend version of TimestampDifferenceExceeds(), since we are not
+ * linked with backend code.
+ */
+bool
+feTimestampDifferenceExceeds(int64 start_time,
+								int64 stop_time,
+								int msec)
+{
+	int64		diff = stop_time - start_time;
+
+	return (diff >= msec * INT64CONST(1000));
+}
+
+/*
+ * Converts an int64 to network byte order.
+ */
+void
+fe_sendint64(int64 i, char *buf)
+{
+	uint32		n32;
+
+	/* High order half first, since we're doing MSB-first */
+	n32 = (uint32) (i >> 32);
+	n32 = htonl(n32);
+	memcpy(&buf[0], &n32, 4);
+
+	/* Now the low order half */
+	n32 = (uint32) i;
+	n32 = htonl(n32);
+	memcpy(&buf[4], &n32, 4);
+}
+
+/*
+ * Converts an int64 from network byte order to native format.
+ */
+int64
+fe_recvint64(char *buf)
+{
+	int64		result;
+	uint32		h32;
+	uint32		l32;
+
+	memcpy(&h32, buf, 4);
+	memcpy(&l32, buf + 4, 4);
+	h32 = ntohl(h32);
+	l32 = ntohl(l32);
+
+	result = h32;
+	result <<= 32;
+	result |= l32;
+
+	return result;
+}
diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h
index 77d6b86..4286df8 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -5,6 +5,7 @@ extern char *connection_string;
 extern char *dbhost;
 extern char *dbuser;
 extern char *dbport;
+extern char *dbname;
 extern int	dbgetpassword;
 
 /* Connection kept global so we can disconnect easily */
@@ -17,3 +18,12 @@ extern PGconn *conn;
 	}
 
 extern PGconn *GetConnection(void);
+
+extern int64 feGetCurrentTimestamp(void);
+extern void feTimestampDifference(int64 start_time, int64 stop_time,
+									 long *secs, int *microsecs);
+
+extern bool feTimestampDifferenceExceeds(int64 start_time, int64 stop_time,
+											int msec);
+extern void fe_sendint64(int64 i, char *buf);
+extern int64 fe_recvint64(char *buf);
-- 
1.8.4.21.g992c386.dirty

0007-wal_decoding-test_logical_decoding-Add-extension-for.patchtext/x-patch; charset=us-asciiDownload
From 17bf92534dbf2710bd8424d7b1755d35e32a38d3 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 19 Aug 2013 13:24:31 +0200
Subject: [PATCH 7/8] wal_decoding: test_logical_decoding: Add extension for
 easier testing of logical decoding

This extension provides three functions for manipulating replication slots:
* init_logical_replication - initiate a replication slot and wait for consistent state
* start_logical_replication - return all changes since the last call up to now, without blocking
* free_logical_replication - free the logical slot again

Those are pretty direct synonyms for the replication connection commands.

Due to questions about how to integrate logical replication tests this module
also contains the current tests of logical replication itself.

Author: Abhijit Menon-Sen
---
 contrib/Makefile                                   |   1 +
 contrib/test_logical_decoding/Makefile             |  33 ++
 contrib/test_logical_decoding/expected/ddl.out     | 625 +++++++++++++++++++++
 contrib/test_logical_decoding/expected/rewrite.out |  70 +++
 contrib/test_logical_decoding/logical.conf         |   2 +
 contrib/test_logical_decoding/sql/ddl.sql          | 316 +++++++++++
 contrib/test_logical_decoding/sql/rewrite.sql      |  29 +
 .../test_logical_decoding--1.0.sql                 |   6 +
 .../test_logical_decoding/test_logical_decoding.c  | 238 ++++++++
 .../test_logical_decoding.control                  |   5 +
 10 files changed, 1325 insertions(+)
 create mode 100644 contrib/test_logical_decoding/Makefile
 create mode 100644 contrib/test_logical_decoding/expected/ddl.out
 create mode 100644 contrib/test_logical_decoding/expected/rewrite.out
 create mode 100644 contrib/test_logical_decoding/logical.conf
 create mode 100644 contrib/test_logical_decoding/sql/ddl.sql
 create mode 100644 contrib/test_logical_decoding/sql/rewrite.sql
 create mode 100644 contrib/test_logical_decoding/test_logical_decoding--1.0.sql
 create mode 100644 contrib/test_logical_decoding/test_logical_decoding.c
 create mode 100644 contrib/test_logical_decoding/test_logical_decoding.control

diff --git a/contrib/Makefile b/contrib/Makefile
index 6d2fe32..41cb892 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -51,6 +51,7 @@ SUBDIRS = \
 		tcn		\
 		test_parser	\
 		test_decoding	\
+		test_logical_decoding \
 		tsearch2	\
 		unaccent	\
 		vacuumlo	\
diff --git a/contrib/test_logical_decoding/Makefile b/contrib/test_logical_decoding/Makefile
new file mode 100644
index 0000000..f1990d3
--- /dev/null
+++ b/contrib/test_logical_decoding/Makefile
@@ -0,0 +1,33 @@
+MODULE_big = test_logical_decoding
+OBJS = test_logical_decoding.o
+
+EXTENSION = test_logical_decoding
+DATA = test_logical_decoding--1.0.sql
+
+# Note: because we don't tell the Makefile there are any regression tests,
+# we have to clean those result files explicitly
+EXTRA_CLEAN = -r $(pg_regress_clean_files)
+
+subdir = contrib/test_logical_decoding
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+
+# Disabled because these tests require "wal_level=logical", which
+# typical installcheck users do not have (e.g. buildfarm clients).
+installcheck:;
+
+submake-regress:
+	$(MAKE) -C $(top_builddir)/src/test/regress
+
+submake-test_decoding:
+	$(MAKE) -C $(top_builddir)/contrib/test_decoding
+
+check: all | submake-regress submake-test_decoding
+	$(pg_regress_check) --temp-config $(top_srcdir)/contrib/test_logical_decoding/logical.conf \
+	    --temp-install=./tmp_check \
+	    --extra-install=contrib/test_decoding \
+	    --extra-install=contrib/test_logical_decoding \
+	    ddl rewrite
+
+PHONY: submake-test_decoding submake-regress
diff --git a/contrib/test_logical_decoding/expected/ddl.out b/contrib/test_logical_decoding/expected/ddl.out
new file mode 100644
index 0000000..c161a43
--- /dev/null
+++ b/contrib/test_logical_decoding/expected/ddl.out
@@ -0,0 +1,625 @@
+CREATE EXTENSION test_logical_decoding;
+-- predictability
+SET synchronous_commit = on;
+-- faster startup
+CHECKPOINT;
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+-- fail because of an already existing slot
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ERROR:  There already is a logical slot named "regression_slot"
+-- succeed once
+SELECT stop_logical_replication('regression_slot');
+ stop_logical_replication 
+--------------------------
+                        0
+(1 row)
+
+-- fail
+SELECT stop_logical_replication('regression_slot');
+ERROR:  couldn't find logical slot "regression_slot"
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+/* check whether status function reports us, only reproduceable columns */
+SELECT slot_name, plugin, active,
+    xmin::xid IS NOT NULL,
+    pg_xlog_location_diff(restart_decoding_lsn, '0/01000000') > 0
+FROM pg_stat_logical_decoding;
+    slot_name    |    plugin     | active | ?column? | ?column? 
+-----------------+---------------+--------+----------+----------
+ regression_slot | test_decoding | f      | t        | t
+(1 row)
+
+/*
+ * Check that changes are handled correctly when interleaved with ddl
+ */
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+COMMIT;
+ALTER TABLE replication_example ADD COLUMN bar int;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4);
+BEGIN;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL);
+COMMIT;
+ALTER TABLE replication_example DROP COLUMN bar;
+INSERT INTO replication_example(somedata, text) VALUES (3, 1);
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (3, 2);
+INSERT INTO replication_example(somedata, text) VALUES (3, 3);
+COMMIT;
+ALTER TABLE replication_example RENAME COLUMN text TO somenum;
+INSERT INTO replication_example(somedata, somenum) VALUES (4, 1);
+-- collect all changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                               data                                                
+---------------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:1 somedata[int4]:1 text[varchar]:1
+ table "replication_example": INSERT: id[int4]:2 somedata[int4]:1 text[varchar]:2
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:3 somedata[int4]:2 text[varchar]:1 bar[int4]:4
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:4 somedata[int4]:2 text[varchar]:2 bar[int4]:4
+ table "replication_example": INSERT: id[int4]:5 somedata[int4]:2 text[varchar]:3 bar[int4]:4
+ table "replication_example": INSERT: id[int4]:6 somedata[int4]:2 text[varchar]:4 bar[int4]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:7 somedata[int4]:3 text[varchar]:1
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:8 somedata[int4]:3 text[varchar]:2
+ table "replication_example": INSERT: id[int4]:9 somedata[int4]:3 text[varchar]:3
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:10 somedata[int4]:4 somenum[varchar]:1
+ COMMIT
+(30 rows)
+
+ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING (somenum::int4);
+-- throw away changes, they contain oids
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ count 
+-------
+    12
+(1 row)
+
+INSERT INTO replication_example(somedata, somenum) VALUES (5, 1);
+BEGIN;
+INSERT INTO replication_example(somedata, somenum) VALUES (6, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod1 int;
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 2, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod2 int;
+INSERT INTO replication_example(somedata, somenum, zaphod2) VALUES (6, 3, 1);
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 4, 2);
+COMMIT;
+/*
+ * check whether the correct indexes are chosen for deletions
+ */
+CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
+INSERT INTO tr_unique(data) VALUES(10);
+--show deletion with unique index
+DELETE FROM tr_unique;
+ALTER TABLE tr_unique RENAME TO tr_pkey;
+-- show changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                          data                                                          
+------------------------------------------------------------------------------------------------------------------------
+ BEGIN
+ table "replication_example": INSERT: id[int4]:11 somedata[int4]:5 somenum[int4]:1
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:12 somedata[int4]:6 somenum[int4]:1
+ table "replication_example": INSERT: id[int4]:13 somedata[int4]:6 somenum[int4]:2 zaphod1[int4]:1
+ table "replication_example": INSERT: id[int4]:14 somedata[int4]:6 somenum[int4]:3 zaphod1[int4]:(null) zaphod2[int4]:1
+ table "replication_example": INSERT: id[int4]:15 somedata[int4]:6 somenum[int4]:4 zaphod1[int4]:2 zaphod2[int4]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "tr_unique": INSERT: id2[int4]:1 data[int4]:10
+ COMMIT
+ BEGIN
+ table "tr_unique": DELETE: id2[int4]:1
+ COMMIT
+ BEGIN
+ COMMIT
+(19 rows)
+
+-- hide changes bc of oid visible in full table rewrites
+ALTER TABLE tr_pkey ADD COLUMN id serial primary key;
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ count 
+-------
+     2
+(1 row)
+
+INSERT INTO tr_pkey(data) VALUES(1);
+--show deletion with primary key
+DELETE FROM tr_pkey;
+/* display results */
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                             data                             
+--------------------------------------------------------------
+ BEGIN
+ table "tr_pkey": INSERT: id2[int4]:2 data[int4]:1 id[int4]:1
+ COMMIT
+ BEGIN
+ table "tr_pkey": DELETE: id[int4]:1
+ COMMIT
+(6 rows)
+
+/*
+ * check that disk spooling works
+ */
+BEGIN;
+CREATE TABLE tr_etoomuch (id serial primary key, data int);
+INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i);
+DELETE FROM tr_etoomuch WHERE id < 5000;
+UPDATE tr_etoomuch SET data = - data WHERE id > 5000;
+COMMIT;
+/* display results, but hide most of the output */
+SELECT count(*), min(data), max(data)
+FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1')
+GROUP BY substring(data, 1, 24)
+ORDER BY 1;
+ count |                              min                              |                             max                             
+-------+---------------------------------------------------------------+-------------------------------------------------------------
+     1 | COMMIT                                                        | COMMIT
+     1 | BEGIN                                                         | BEGIN
+  4999 | table "tr_etoomuch": DELETE: id[int4]:1                       | table "tr_etoomuch": DELETE: id[int4]:999
+  5234 | table "tr_etoomuch": UPDATE: id[int4]:10000 data[int4]:-10000 | table "tr_etoomuch": UPDATE: id[int4]:9999 data[int4]:-9999
+ 10234 | table "tr_etoomuch": INSERT: id[int4]:10000 data[int4]:10000  | table "tr_etoomuch": INSERT: id[int4]:9 data[int4]:9
+(5 rows)
+
+/*
+ * check whether we subtransactions correctly in relation with each other
+ */
+CREATE TABLE tr_sub (id serial primary key, path text);
+-- toplevel, subtxn, toplevel, subtxn, subtxn
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('1-top-#1');
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#2');
+RELEASE SAVEPOINT a;
+SAVEPOINT b;
+SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#2');
+RELEASE SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-#1');
+RELEASE SAVEPOINT b;
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                            data                            
+------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "tr_sub": INSERT: id[int4]:1 path[text]:1-top-#1
+ table "tr_sub": INSERT: id[int4]:2 path[text]:1-top-1-#1
+ table "tr_sub": INSERT: id[int4]:3 path[text]:1-top-1-#2
+ table "tr_sub": INSERT: id[int4]:4 path[text]:1-top-2-1-#1
+ table "tr_sub": INSERT: id[int4]:5 path[text]:1-top-2-1-#2
+ table "tr_sub": INSERT: id[int4]:6 path[text]:1-top-2-#1
+ COMMIT
+(10 rows)
+
+-- check that we handle xlog assignments correctly
+BEGIN;
+-- nest 80 subtxns
+SAVEPOINT subtop;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+-- assign xid by inserting
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#1');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#2');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#3');
+RELEASE SAVEPOINT subtop;
+INSERT INTO tr_sub(path) VALUES ('2-top-#1');
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                             data                             
+--------------------------------------------------------------
+ BEGIN
+ table "tr_sub": INSERT: id[int4]:7 path[text]:2-top-1...--#1
+ table "tr_sub": INSERT: id[int4]:8 path[text]:2-top-1...--#2
+ table "tr_sub": INSERT: id[int4]:9 path[text]:2-top-1...--#3
+ table "tr_sub": INSERT: id[int4]:10 path[text]:2-top-#1
+ COMMIT
+(6 rows)
+
+-- make sure rollbacked subtransactions aren't decoded
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#1');
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-1-#1');
+SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-2-#1');
+ROLLBACK TO SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#2');
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                            data                             
+-------------------------------------------------------------
+ BEGIN
+ table "tr_sub": INSERT: id[int4]:11 path[text]:3-top-2-#1
+ table "tr_sub": INSERT: id[int4]:12 path[text]:3-top-2-1-#1
+ table "tr_sub": INSERT: id[int4]:14 path[text]:3-top-2-#2
+ COMMIT
+(5 rows)
+
+-- test whether a known, but not yet logged toplevel xact, followed by a
+-- subxact commit is handled correctly
+BEGIN;
+SELECT txid_current() != 0; -- so no fixed xid apears in the outfile
+ ?column? 
+----------
+ t
+(1 row)
+
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('4-top-1-#1');
+RELEASE SAVEPOINT a;
+COMMIT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ data 
+------
+(0 rows)
+
+/*
+ * Check whether treating a table as a catalog table works somewhat
+ */
+CREATE TABLE replication_metadata (
+    id serial primary key,
+    relation name NOT NULL,
+    options text[]
+)
+WITH (treat_as_catalog_table = true)
+;
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+Options: treat_as_catalog_table=true
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('foo', ARRAY['a', 'b']);
+ALTER TABLE replication_metadata RESET (treat_as_catalog_table);
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('bar', ARRAY['a', 'b']);
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = true);
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+Options: treat_as_catalog_table=true
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('blub', NULL);
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = false);
+\d+ replication_metadata
+                                              Table "public.replication_metadata"
+  Column  |  Type   |                             Modifiers                             | Storage  | Stats target | Description 
+----------+---------+-------------------------------------------------------------------+----------+--------------+-------------
+ id       | integer | not null default nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
+ relation | name    | not null                                                          | plain    |              | 
+ options  | text[]  |                                                                   | extended |              | 
+Indexes:
+    "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Has OIDs: no
+Options: treat_as_catalog_table=false
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('zaphod', NULL);
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                             data                                             
+----------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:1 relation[name]:foo options[_text]:{a,b}
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:2 relation[name]:bar options[_text]:{a,b}
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:3 relation[name]:blub options[_text]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_metadata": INSERT: id[int4]:4 relation[name]:zaphod options[_text]:(null)
+ COMMIT
+(20 rows)
+
+/*
+ * check whether we handle updates/deletes correct with & without a pkey
+ */
+/* we should handle the case without a key at all more gracefully */
+CREATE TABLE table_without_key(id serial, data int);
+INSERT INTO table_without_key(data) VALUES(1),(2);
+DELETE FROM table_without_key WHERE data = 1;
+UPDATE table_without_key SET data = 3 WHERE data = 2;
+UPDATE table_without_key SET id = -id;
+UPDATE table_without_key SET id = -id;
+DELETE FROM table_without_key WHERE data = 3;
+CREATE TABLE table_with_pkey(id serial primary key, data int);
+INSERT INTO table_with_pkey(data) VALUES(1), (2);
+DELETE FROM table_with_pkey WHERE data = 1;
+UPDATE table_with_pkey SET data = 3 WHERE data = 2;
+UPDATE table_with_pkey SET id = -id;
+UPDATE table_with_pkey SET id = -id;
+DELETE FROM table_with_pkey WHERE data = 3;
+CREATE TABLE table_with_unique(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id DROP NOT NULL;
+INSERT INTO table_with_unique(data) VALUES(1), (2);
+DELETE FROM table_with_unique WHERE data = 1;
+UPDATE table_with_unique SET data = 3 WHERE data = 2;
+UPDATE table_with_unique SET id = -id;
+UPDATE table_with_unique SET id = -id;
+DELETE FROM table_with_unique WHERE data = 3;
+CREATE TABLE table_with_unique_not_null(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id SET NOT NULL; --already set
+INSERT INTO table_with_unique_not_null(data) VALUES(1), (2);
+DELETE FROM table_with_unique_not_null WHERE data = 1;
+UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2;
+UPDATE table_with_unique_not_null SET id = -id;
+UPDATE table_with_unique_not_null SET id = -id;
+DELETE FROM table_with_unique_not_null WHERE data = 3;
+CREATE TABLE table_with_oid(id serial, data int) WITH oids;
+CREATE UNIQUE INDEX table_with_oid_oid ON table_with_oid(oid);
+INSERT INTO table_with_oid(data) VALUES(1), (2);
+DELETE FROM table_with_oid WHERE data = 1;
+UPDATE table_with_oid SET data = 3 WHERE data = 2;
+DELETE FROM table_with_oid WHERE data = 3;
+UPDATE table_with_oid SET id = -id;
+UPDATE table_with_oid SET id = -id;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                 data                                                 
+------------------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_without_key": INSERT: id[int4]:1 data[int4]:1
+ table "table_without_key": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_without_key": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ table "table_without_key": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_without_key": UPDATE: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_without_key": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_without_key": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_pkey": INSERT: id[int4]:1 data[int4]:1
+ table "table_with_pkey": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_pkey": DELETE: id[int4]:1
+ COMMIT
+ BEGIN
+ table "table_with_pkey": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_pkey": UPDATE: old-pkey: id[int4]:2 new-tuple: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_pkey": UPDATE: old-pkey: id[int4]:-2 new-tuple: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_pkey": DELETE: id[int4]:2
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_unique": INSERT: id[int4]:1 data[int4]:1
+ table "table_with_unique": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_unique": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ table "table_with_unique": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique": UPDATE: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique": DELETE: (no-tuple-data)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": INSERT: id[int4]:1 data[int4]:1
+ table "table_with_unique_not_null": INSERT: id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": DELETE: id[int4]:1
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": UPDATE: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": UPDATE: old-pkey: id[int4]:2 new-tuple: id[int4]:-2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": UPDATE: old-pkey: id[int4]:-2 new-tuple: id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_unique_not_null": DELETE: id[int4]:2
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "table_with_oid": INSERT: oid[oid]:16484 id[int4]:1 data[int4]:1
+ table "table_with_oid": INSERT: oid[oid]:16485 id[int4]:2 data[int4]:2
+ COMMIT
+ BEGIN
+ table "table_with_oid": DELETE: oid[oid]:16484
+ COMMIT
+ BEGIN
+ table "table_with_oid": UPDATE: oid[oid]:16485 id[int4]:2 data[int4]:3
+ COMMIT
+ BEGIN
+ table "table_with_oid": DELETE: oid[oid]:16485
+ COMMIT
+(105 rows)
+
+-- check toast support
+SELECT setseed(0);
+ setseed 
+---------
+ 
+(1 row)
+
+CREATE TABLE toasttable(
+       id serial primary key,
+       toasted_col1 text,
+       rand1 float8 DEFAULT random(),
+       toasted_col2 text,
+       rand2 float8 DEFAULT random()
+       );
+-- uncompressed external toast data
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+-- compressed external toast data
+INSERT INTO toasttable(toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i);
+-- update of existing column
+UPDATE toasttable
+    SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "toasttable": INSERT: id[int4]:1 toasted_col1[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand1[float8]:0.840187716763467 toasted_col2[text]:(null) rand2[float8]:0.394382926635444
+ COMMIT
+ BEGIN
+ table "toasttable": INSERT: id[int4]:2 toasted_col1[text]:(null) rand1[float8]:0.783099223393947 toasted_col2[text]:0001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500 rand2[float8]:0.798440033104271
+ COMMIT
+ BEGIN
+ table "toasttable": UPDATE: id[int4]:1 toasted_col1[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand1[float8]:0.840187716763467 toasted_col2[text]:(null) rand2[float8]:0.394382926635444
+ COMMIT
+(11 rows)
+
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+-- update of second column, first column unchanged
+UPDATE toasttable
+    SET toasted_col2 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+-- make sure we decode correctly even if the toast table is gone
+DROP TABLE toasttable;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ BEGIN
+ table "toasttable": INSERT: id[int4]:3 toasted_col1[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand1[float8]:0.911647357512265 toasted_col2[text]:(null) rand2[float8]:0.197551369201392
+ COMMIT
+ BEGIN
+ table "toasttable": UPDATE: id[int4]:1 toasted_col1[text]:(unchanged-toast-datum) rand1[float8]:0.840187716763467 toasted_col2[text]:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000 rand2[float8]:0.394382926635444
+ COMMIT
+ BEGIN
+ COMMIT
+(8 rows)
+
+-- done, free logical replication slot
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+ data 
+------
+(0 rows)
+
+SELECT stop_logical_replication('regression_slot');
+ stop_logical_replication 
+--------------------------
+                        0
+(1 row)
+
+/* check whether we aren't visible anymore now */
+SELECT * FROM pg_stat_logical_decoding;
+ slot_name | plugin | database | active | xmin | restart_decoding_lsn 
+-----------+--------+----------+--------+------+----------------------
+(0 rows)
+
diff --git a/contrib/test_logical_decoding/expected/rewrite.out b/contrib/test_logical_decoding/expected/rewrite.out
new file mode 100644
index 0000000..392e465
--- /dev/null
+++ b/contrib/test_logical_decoding/expected/rewrite.out
@@ -0,0 +1,70 @@
+CREATE EXTENSION test_logical_decoding;
+ERROR:  extension "test_logical_decoding" already exists
+-- predictability
+SET synchronous_commit = on;
+DROP TABLE IF EXISTS replication_example;
+-- faster startup
+CHECKPOINT;
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+INSERT INTO replication_example(somedata) VALUES (1);
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                         data                                          
+---------------------------------------------------------------------------------------
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:1 somedata[int4]:1 text[varchar]:(null)
+ COMMIT
+(5 rows)
+
+INSERT INTO replication_example(somedata) VALUES (2);
+VACUUM FULL pg_am;
+VACUUM FULL pg_amop;
+VACUUM FULL pg_proc;
+VACUUM FULL pg_opclass;
+VACUUM FULL pg_class;
+VACUUM FULL pg_type;
+VACUUM FULL pg_index;
+VACUUM FULL pg_database;
+INSERT INTO replication_example(somedata) VALUES (3);
+-- make old files go away
+CHECKPOINT;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+                                         data                                          
+---------------------------------------------------------------------------------------
+ BEGIN
+ table "replication_example": INSERT: id[int4]:2 somedata[int4]:2 text[varchar]:(null)
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ COMMIT
+ BEGIN
+ table "replication_example": INSERT: id[int4]:3 somedata[int4]:3 text[varchar]:(null)
+ COMMIT
+(22 rows)
+
+SELECT stop_logical_replication('regression_slot');
+ stop_logical_replication 
+--------------------------
+                        0
+(1 row)
+
diff --git a/contrib/test_logical_decoding/logical.conf b/contrib/test_logical_decoding/logical.conf
new file mode 100644
index 0000000..a7c6c86
--- /dev/null
+++ b/contrib/test_logical_decoding/logical.conf
@@ -0,0 +1,2 @@
+wal_level = logical
+max_logical_slots = 4
diff --git a/contrib/test_logical_decoding/sql/ddl.sql b/contrib/test_logical_decoding/sql/ddl.sql
new file mode 100644
index 0000000..b1eee39
--- /dev/null
+++ b/contrib/test_logical_decoding/sql/ddl.sql
@@ -0,0 +1,316 @@
+CREATE EXTENSION test_logical_decoding;
+-- predictability
+SET synchronous_commit = on;
+
+-- faster startup
+CHECKPOINT;
+
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+-- fail because of an already existing slot
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+-- succeed once
+SELECT stop_logical_replication('regression_slot');
+-- fail
+SELECT stop_logical_replication('regression_slot');
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+
+/* check whether status function reports us, only reproduceable columns */
+SELECT slot_name, plugin, active,
+    xmin::xid IS NOT NULL,
+    pg_xlog_location_diff(restart_decoding_lsn, '0/01000000') > 0
+FROM pg_stat_logical_decoding;
+
+/*
+ * Check that changes are handled correctly when interleaved with ddl
+ */
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+COMMIT;
+
+ALTER TABLE replication_example ADD COLUMN bar int;
+
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL);
+COMMIT;
+
+ALTER TABLE replication_example DROP COLUMN bar;
+INSERT INTO replication_example(somedata, text) VALUES (3, 1);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (3, 2);
+INSERT INTO replication_example(somedata, text) VALUES (3, 3);
+COMMIT;
+
+ALTER TABLE replication_example RENAME COLUMN text TO somenum;
+
+INSERT INTO replication_example(somedata, somenum) VALUES (4, 1);
+
+-- collect all changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING (somenum::int4);
+-- throw away changes, they contain oids
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO replication_example(somedata, somenum) VALUES (5, 1);
+
+BEGIN;
+INSERT INTO replication_example(somedata, somenum) VALUES (6, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod1 int;
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 2, 1);
+ALTER TABLE replication_example ADD COLUMN zaphod2 int;
+INSERT INTO replication_example(somedata, somenum, zaphod2) VALUES (6, 3, 1);
+INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 4, 2);
+COMMIT;
+
+/*
+ * check whether the correct indexes are chosen for deletions
+ */
+
+CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
+INSERT INTO tr_unique(data) VALUES(10);
+--show deletion with unique index
+DELETE FROM tr_unique;
+
+ALTER TABLE tr_unique RENAME TO tr_pkey;
+
+-- show changes
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- hide changes bc of oid visible in full table rewrites
+ALTER TABLE tr_pkey ADD COLUMN id serial primary key;
+SELECT count(data) FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO tr_pkey(data) VALUES(1);
+--show deletion with primary key
+DELETE FROM tr_pkey;
+
+/* display results */
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+/*
+ * check that disk spooling works
+ */
+BEGIN;
+CREATE TABLE tr_etoomuch (id serial primary key, data int);
+INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i);
+DELETE FROM tr_etoomuch WHERE id < 5000;
+UPDATE tr_etoomuch SET data = - data WHERE id > 5000;
+COMMIT;
+
+/* display results, but hide most of the output */
+SELECT count(*), min(data), max(data)
+FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1')
+GROUP BY substring(data, 1, 24)
+ORDER BY 1;
+
+/*
+ * check whether we subtransactions correctly in relation with each other
+ */
+CREATE TABLE tr_sub (id serial primary key, path text);
+
+-- toplevel, subtxn, toplevel, subtxn, subtxn
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('1-top-#1');
+
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-1-#2');
+RELEASE SAVEPOINT a;
+
+SAVEPOINT b;
+SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#1');
+INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#2');
+RELEASE SAVEPOINT c;
+INSERT INTO tr_sub(path) VALUES ('1-top-2-#1');
+RELEASE SAVEPOINT b;
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- check that we handle xlog assignments correctly
+BEGIN;
+-- nest 80 subtxns
+SAVEPOINT subtop;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;
+-- assign xid by inserting
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#1');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#2');
+INSERT INTO tr_sub(path) VALUES ('2-top-1...--#3');
+RELEASE SAVEPOINT subtop;
+INSERT INTO tr_sub(path) VALUES ('2-top-#1');
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- make sure rollbacked subtransactions aren't decoded
+BEGIN;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#1');
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-1-#1');
+SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-2-#1');
+ROLLBACK TO SAVEPOINT b;
+INSERT INTO tr_sub(path) VALUES ('3-top-2-#2');
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- test whether a known, but not yet logged toplevel xact, followed by a
+-- subxact commit is handled correctly
+BEGIN;
+SELECT txid_current() != 0; -- so no fixed xid apears in the outfile
+SAVEPOINT a;
+INSERT INTO tr_sub(path) VALUES ('4-top-1-#1');
+RELEASE SAVEPOINT a;
+COMMIT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+
+/*
+ * Check whether treating a table as a catalog table works somewhat
+ */
+CREATE TABLE replication_metadata (
+    id serial primary key,
+    relation name NOT NULL,
+    options text[]
+)
+WITH (treat_as_catalog_table = true)
+;
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('foo', ARRAY['a', 'b']);
+
+ALTER TABLE replication_metadata RESET (treat_as_catalog_table);
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('bar', ARRAY['a', 'b']);
+
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = true);
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('blub', NULL);
+
+ALTER TABLE replication_metadata SET (treat_as_catalog_table = false);
+\d+ replication_metadata
+
+INSERT INTO replication_metadata(relation, options)
+VALUES ('zaphod', NULL);
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+/*
+ * check whether we handle updates/deletes correct with & without a pkey
+ */
+
+/* we should handle the case without a key at all more gracefully */
+CREATE TABLE table_without_key(id serial, data int);
+INSERT INTO table_without_key(data) VALUES(1),(2);
+DELETE FROM table_without_key WHERE data = 1;
+UPDATE table_without_key SET data = 3 WHERE data = 2;
+UPDATE table_without_key SET id = -id;
+UPDATE table_without_key SET id = -id;
+DELETE FROM table_without_key WHERE data = 3;
+
+CREATE TABLE table_with_pkey(id serial primary key, data int);
+INSERT INTO table_with_pkey(data) VALUES(1), (2);
+DELETE FROM table_with_pkey WHERE data = 1;
+UPDATE table_with_pkey SET data = 3 WHERE data = 2;
+UPDATE table_with_pkey SET id = -id;
+UPDATE table_with_pkey SET id = -id;
+DELETE FROM table_with_pkey WHERE data = 3;
+
+CREATE TABLE table_with_unique(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id DROP NOT NULL;
+INSERT INTO table_with_unique(data) VALUES(1), (2);
+DELETE FROM table_with_unique WHERE data = 1;
+UPDATE table_with_unique SET data = 3 WHERE data = 2;
+UPDATE table_with_unique SET id = -id;
+UPDATE table_with_unique SET id = -id;
+DELETE FROM table_with_unique WHERE data = 3;
+
+CREATE TABLE table_with_unique_not_null(id serial unique, data int);
+ALTER TABLE table_with_unique ALTER COLUMN id SET NOT NULL; --already set
+INSERT INTO table_with_unique_not_null(data) VALUES(1), (2);
+DELETE FROM table_with_unique_not_null WHERE data = 1;
+UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2;
+UPDATE table_with_unique_not_null SET id = -id;
+UPDATE table_with_unique_not_null SET id = -id;
+DELETE FROM table_with_unique_not_null WHERE data = 3;
+
+CREATE TABLE table_with_oid(id serial, data int) WITH oids;
+CREATE UNIQUE INDEX table_with_oid_oid ON table_with_oid(oid);
+INSERT INTO table_with_oid(data) VALUES(1), (2);
+DELETE FROM table_with_oid WHERE data = 1;
+UPDATE table_with_oid SET data = 3 WHERE data = 2;
+DELETE FROM table_with_oid WHERE data = 3;
+UPDATE table_with_oid SET id = -id;
+UPDATE table_with_oid SET id = -id;
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- check toast support
+SELECT setseed(0);
+CREATE TABLE toasttable(
+       id serial primary key,
+       toasted_col1 text,
+       rand1 float8 DEFAULT random(),
+       toasted_col2 text,
+       rand2 float8 DEFAULT random()
+       );
+
+-- uncompressed external toast data
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+
+-- compressed external toast data
+INSERT INTO toasttable(toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i);
+
+-- update of existing column
+UPDATE toasttable
+    SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i);
+
+-- update of second column, first column unchanged
+UPDATE toasttable
+    SET toasted_col2 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i))
+WHERE id = 1;
+
+-- make sure we decode correctly even if the toast table is gone
+DROP TABLE toasttable;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+-- done, free logical replication slot
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+SELECT stop_logical_replication('regression_slot');
+
+/* check whether we aren't visible anymore now */
+SELECT * FROM pg_stat_logical_decoding;
diff --git a/contrib/test_logical_decoding/sql/rewrite.sql b/contrib/test_logical_decoding/sql/rewrite.sql
new file mode 100644
index 0000000..2400fe3
--- /dev/null
+++ b/contrib/test_logical_decoding/sql/rewrite.sql
@@ -0,0 +1,29 @@
+CREATE EXTENSION test_logical_decoding;
+-- predictability
+SET synchronous_commit = on;
+
+DROP TABLE IF EXISTS replication_example;
+
+-- faster startup
+CHECKPOINT;
+SELECT 'init' FROM init_logical_replication('regression_slot', 'test_decoding');
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+INSERT INTO replication_example(somedata) VALUES (1);
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+
+INSERT INTO replication_example(somedata) VALUES (2);
+VACUUM FULL pg_am;
+VACUUM FULL pg_amop;
+VACUUM FULL pg_proc;
+VACUUM FULL pg_opclass;
+VACUUM FULL pg_class;
+VACUUM FULL pg_type;
+VACUUM FULL pg_index;
+VACUUM FULL pg_database;
+INSERT INTO replication_example(somedata) VALUES (3);
+
+-- make old files go away
+CHECKPOINT;
+
+SELECT data FROM start_logical_replication('regression_slot', 'now', 'hide-xids', '1');
+SELECT stop_logical_replication('regression_slot');
diff --git a/contrib/test_logical_decoding/test_logical_decoding--1.0.sql b/contrib/test_logical_decoding/test_logical_decoding--1.0.sql
new file mode 100644
index 0000000..b6e048c
--- /dev/null
+++ b/contrib/test_logical_decoding/test_logical_decoding--1.0.sql
@@ -0,0 +1,6 @@
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_logical_decoding" to load this file. \quit
+
+CREATE FUNCTION start_logical_replication (slotname name, pos text, VARIADIC options text[] DEFAULT '{}', OUT location text, OUT xid bigint, OUT data text) RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'start_logical_replication'
+LANGUAGE C IMMUTABLE STRICT;
diff --git a/contrib/test_logical_decoding/test_logical_decoding.c b/contrib/test_logical_decoding/test_logical_decoding.c
new file mode 100644
index 0000000..26ecdfa
--- /dev/null
+++ b/contrib/test_logical_decoding/test_logical_decoding.c
@@ -0,0 +1,238 @@
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalfuncs.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "storage/fd.h"
+#include "miscadmin.h"
+#include "funcapi.h"
+
+PG_MODULE_MAGIC;
+
+Datum		start_logical_replication(PG_FUNCTION_ARGS);
+
+static Tuplestorestate *tupstore = NULL;
+static TupleDesc tupdesc;
+
+static void
+LogicalOutputPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	resetStringInfo(ctx->out);
+}
+
+static void
+LogicalOutputWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
+{
+	Datum		values[3];
+	bool		nulls[3];
+	char		buf[60];
+
+	sprintf(buf, "%X/%X", (uint32) (lsn >> 32), (uint32) lsn);
+
+	memset(nulls, 0, sizeof(nulls));
+	values[0] = CStringGetTextDatum(buf);
+	values[1] = Int64GetDatum(xid);
+	values[2] = CStringGetTextDatum(ctx->out->data);
+
+	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+}
+
+PG_FUNCTION_INFO_V1(start_logical_replication);
+
+Datum
+start_logical_replication(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	XLogRecPtr	now;
+	XLogRecPtr	startptr;
+	XLogRecPtr	rp;
+
+	LogicalDecodingContext *ctx;
+
+	ResourceOwner old_resowner = CurrentResourceOwner;
+	ArrayType  *arr;
+	Size		ndim;
+	List	   *options = NIL;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	arr = PG_GETARG_ARRAYTYPE_P(2);
+	ndim = ARR_NDIM(arr);
+
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	if (ndim > 1)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("start_logical_replication only accept one dimension of arguments")));
+	}
+	else if (array_contains_nulls(arr))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			  errmsg("start_logical_replication expects NOT NULL options")));
+	}
+	else if (ndim == 1)
+	{
+		int			nelems;
+		Datum	   *datum_opts;
+		int			i;
+
+		Assert(ARR_ELEMTYPE(arr) == TEXTOID);
+
+		deconstruct_array(arr, TEXTOID, -1, false, 'i',
+						  &datum_opts, NULL, &nelems);
+
+		if (nelems % 2 != 0)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("options need to be specified pairwise")));
+		}
+
+		for (i = 0; i < nelems; i += 2)
+		{
+			char	   *name = VARDATA(DatumGetTextP(datum_opts[i]));
+			char	   *opt = VARDATA(DatumGetTextP(datum_opts[i + 1]));
+
+			options = lappend(options, makeDefElem(name, (Node *) makeString(opt)));
+		}
+	}
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * XXX: It's impolite to ignore our argument and keep decoding until the
+	 * current position.
+	 */
+	now = GetFlushRecPtr();
+
+	/*
+	 * We need to create a normal_snapshot_reader, but adjust it to use our
+	 * page_read callback, and also make its reorder buffer use our callback
+	 * wrappers that don't depend on walsender.
+	 */
+
+	CheckLogicalReplicationRequirements();
+	LogicalDecodingReAcquireSlot(NameStr(*name));
+
+	ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, false,
+									   MyLogicalDecodingSlot->confirmed_flush,
+									   options,
+									   logical_read_local_xlog_page,
+									   LogicalOutputPrepareWrite,
+									   LogicalOutputWrite);
+
+	startptr = MyLogicalDecodingSlot->restart_decoding;
+
+	elog(DEBUG1, "Starting logical replication from %X/%X to %X/%X",
+		 (uint32) (MyLogicalDecodingSlot->restart_decoding >> 32),
+		 (uint32) MyLogicalDecodingSlot->restart_decoding,
+		 (uint32) (now >> 32), (uint32) now);
+
+	CurrentResourceOwner = ResourceOwnerCreate(CurrentResourceOwner, "logical decoding");
+
+	/* invalidate non-timetravel entries */
+	InvalidateSystemCaches();
+
+	PG_TRY();
+	{
+
+		while ((startptr != InvalidXLogRecPtr && startptr < now) ||
+			   (ctx->reader->EndRecPtr && ctx->reader->EndRecPtr < now))
+		{
+			XLogRecord *record;
+			char	   *errm = NULL;
+
+			record = XLogReadRecord(ctx->reader, startptr, &errm);
+			if (errm)
+				elog(ERROR, "%s", errm);
+
+			startptr = InvalidXLogRecPtr;
+
+			if (record != NULL)
+			{
+				XLogRecordBuffer buf;
+
+				buf.origptr = ctx->reader->ReadRecPtr;
+				buf.endptr = ctx->reader->EndRecPtr;
+				buf.record = *record;
+				buf.record_data = XLogRecGetData(record);
+
+				/*
+				 * The {begin_txn,change,commit_txn}_wrapper callbacks above
+				 * will store the description into our tuplestore.
+				 */
+				DecodeRecordIntoReorderBuffer(ctx, &buf);
+			}
+		}
+	}
+	PG_CATCH();
+	{
+		LogicalDecodingReleaseSlot();
+
+		/*
+		 * clear timetravel entries: XXX allowed in aborted TXN?
+		 */
+		InvalidateSystemCaches();
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	rp = ctx->reader->EndRecPtr;
+	if (rp >= now)
+	{
+		elog(DEBUG1, "Reached endpoint (wanted: %X/%X, got: %X/%X)",
+			 (uint32) (now >> 32), (uint32) now,
+			 (uint32) (rp >> 32), (uint32) rp);
+	}
+
+	tuplestore_donestoring(tupstore);
+
+	CurrentResourceOwner = old_resowner;
+
+	/*
+	 * Next time, start where we left off. (Hunting things, the family
+	 * business..)
+	 */
+	MyLogicalDecodingSlot->confirmed_flush = ctx->reader->EndRecPtr;
+
+	LogicalDecodingReleaseSlot();
+
+	return (Datum) 0;
+}
diff --git a/contrib/test_logical_decoding/test_logical_decoding.control b/contrib/test_logical_decoding/test_logical_decoding.control
new file mode 100644
index 0000000..0dce19f
--- /dev/null
+++ b/contrib/test_logical_decoding/test_logical_decoding.control
@@ -0,0 +1,5 @@
+# test_logical_decoding extension
+comment = 'test logical decoding'
+default_version = '1.0'
+module_pathname = '$libdir/test_logical_decoding'
+relocatable = true
-- 
1.8.4.21.g992c386.dirty

0008-wal_decoding-design-document-v2.4-and-snapshot-build.patchtext/x-patch; charset=us-asciiDownload
>From 0c4c3957bd497a8384c69201d2ea1be5c7c67a8a Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 19 Aug 2013 13:24:31 +0200
Subject: [PATCH 8/8] wal_decoding: design document v2.4 and snapshot building
 design doc v0.5

---
 src/backend/replication/logical/DESIGN.txt         | 593 +++++++++++++++++++++
 src/backend/replication/logical/Makefile           |   6 +
 .../replication/logical/README.SNAPBUILD.txt       | 241 +++++++++
 3 files changed, 840 insertions(+)
 create mode 100644 src/backend/replication/logical/DESIGN.txt
 create mode 100644 src/backend/replication/logical/README.SNAPBUILD.txt

diff --git a/src/backend/replication/logical/DESIGN.txt b/src/backend/replication/logical/DESIGN.txt
new file mode 100644
index 0000000..d76fdb4
--- /dev/null
+++ b/src/backend/replication/logical/DESIGN.txt
@@ -0,0 +1,593 @@
+//-*- mode: adoc -*-
+= High Level Design for Logical Replication in Postgres =
+:copyright: PostgreSQL Global Development Group 2012
+:author: Andres Freund, 2ndQuadrant Ltd.
+:email: andres@2ndQuadrant.com
+
+== Introduction ==
+
+This document aims to first explain why we think postgres needs another
+replication solution and what that solution needs to offer in our opinion. Then
+it sketches out our proposed implementation.
+
+In contrast to an earlier version of the design document which talked about the
+implementation of four parts of replication solutions:
+
+1. Source data generation
+1. Transportation of that data
+1. Applying the changes
+1. Conflict resolution
+
+this version only plans to talk about the first part in detail as it is an
+independent and complex part usable for a wide range of use cases which we want
+to get included into postgres in a first step.
+
+=== Previous discussions ===
+
+There are two rather large threads discussing several parts of the initial
+prototype and proposed architecture:
+
+- http://archives.postgresql.org/message-id/201206131327.24092.andres@2ndquadrant.com[Logical Replication/BDR prototype and architecture]
+- http://archives.postgresql.org/message-id/201206211341.25322.andres@2ndquadrant.com[Catalog/Metadata consistency during changeset extraction from WAL]
+
+Those discussions lead to some fundamental design changes which are presented in this document.
+
+=== Changes from v1 ===
+* At least a partial decoding step required/possible on the source system
+* No intermediate ("schema only") instances required
+* DDL handling, without event triggers
+* A very simple text conversion is provided for debugging/demo purposes
+* Smaller scope
+
+== Existing approaches to replication in Postgres ==
+
+If any currently used approach to replication can be made to support every
+use-case/feature we need, it likely is not a good idea to implement something
+different. Currently three basic approaches are in use in/around postgres
+today:
+
+. Trigger based
+. Recovery based/Physical footnote:[Often referred to by terms like Hot Standby, Streaming Replication, Point In Time Recovery]
+. Statement based
+
+Statement based replication has obvious and known problems with consistency and
+correctness making it hard to use in the general case so we will not further
+discuss it here.
+
+Lets have a look at the advantages/disadvantages of the other approaches:
+
+=== Trigger based Replication ===
+
+This variant has a multitude of significant advantages:
+
+* implementable in userspace
+* easy to customize
+* just about everything can be made configurable
+* cross version support
+* cross architecture support
+* can feed into systems other than postgres
+* no overhead from writes to non-replicated tables
+* writable standbys
+* mature solutions
+* multimaster implementations possible & existing
+
+But also a number of disadvantages, some of them very hard to solve:
+
+* essentially duplicates the amount of writes (or even more!)
+* synchronous replication hard or impossible to implement
+* noticeable CPU overhead
+** trigger functions
+** text conversion of data
+* complex parts implemented in several solutions
+* not in core
+
+Especially the higher amount of writes might seem easy to solve at a first
+glance but a solution not using a normal transactional table for its log/queue
+has to solve a lot of problems. The major ones are:
+
+* crash safety, restartability & spilling to disk
+* consistency with the commit status of transactions
+* only a minimal amount of synchronous work should be done inside individual
+transactions
+
+In our opinion those problems are restricting progress/wider distribution of
+these class of solutions. It is our aim though that existing solutions in this
+space - most prominently slony and londiste - can benefit from the work we are
+doing & planning to do by incorporating at least parts of the changeset
+generation infrastructure.
+
+=== Recovery based Replication ===
+
+This type of solution, being built into postgres and of increasing popularity,
+has and will have its use cases and we do not aim to replace but to complement
+it. We plan to reuse some of the infrastructure and to make it possible to mix
+both modes of replication
+
+Advantages:
+
+* builtin
+* built on existing infrastructure from crash recovery
+* efficient
+** minimal CPU, memory overhead on primary
+** low amount of additional writes
+* synchronous operation mode
+* low maintenance once setup
+* handles DDL
+
+Disadvantages:
+
+* standbys are read only
+* no cross version support
+* no cross architecture support
+* no replication into foreign systems
+* hard to customize
+* not configurable on the level of database, tables, ...
+
+== Goals ==
+
+As seen in the previous short survey of the two major interesting classes of
+replication solution there is a significant gap between those. Our aim is to
+make it smaller.
+
+We aim for:
+
+* in core
+* low CPU overhead
+* low storage overhead
+* asynchronous, optionally synchronous operation modes
+* robust
+* modular
+* basis for other technologies (sharding, replication into other DBMS's, ...)
+* basis for at least one multi-master solution
+* make the implementation as unintrusive as possible, but not more
+
+== New Architecture ==
+
+=== Overview ===
+
+Our proposal is to reuse the basic principle of WAL based replication, namely
+reusing data that already needs to be written for another purpose, and extend
+it to allow most, but not all, the flexibility of trigger based solutions.
+We want to do that by decoding the WAL back into a non-physical form.
+
+To get the flexibility we and others want we propose that the last step of
+changeset generation, transforming it into a format that can be used by the
+replication consumer, is done in an extensible manner. In the schema the part
+that does that is described as 'Output Plugin'. To keep the amount of
+duplication between different plugins as low as possible the plugin should only
+do a a very limited amount of work.
+
+The following paragraphs contain reasoning for the individual design decisions
+made and their highlevel design.
+
+=== Schematics ===
+
+The basic proposed architecture for changeset extraction is presented in the
+following diagram. The first part should look familiar to anyone knowing
+postgres' architecture. The second is where most of the new magic happens.
+
+[[basic-schema]]
+.Architecture Schema
+["ditaa"]
+------------------------------------------------------------------------------
+        Traditional Stuff
+
+ +---------+---------+---------+---------+----+
+ | Backend | Backend | Backend | Autovac | ...|
+ +----+----+---+-----+----+----+----+----+-+--+
+      |        |          |         |      |
+      +------+ | +--------+         |      |
+    +-+      | | | +----------------+      |
+    |        | | | |                       |
+    |        v v v v                       |
+    |     +------------+                   |
+    |     | WAL writer |<------------------+
+    |     +------------+
+    |       | | | | |
+    v       v v v v v       +-------------------+
++--------+ +---------+   +->| Startup/Recovery  |
+|{s}     | |{s}      |   |  +-------------------+
+|Catalog | |   WAL   |---+->| SR/Hot Standby    |
+|        | |         |   |  +-------------------+
++--------+ +---------+   +->| Point in Time     |
+    ^          |            +-------------------+
+ ---|----------|--------------------------------
+    |       New Stuff
++---+          |
+|              v            Running separately
+| +----------------+  +=-------------------------+
+| | Walsender  |   |  |                          |
+| |            v   |  |    +-------------------+ |
+| +-------------+  |  | +->| Logical Rep.      | |
+| |     WAL     |  |  | |  +-------------------+ |
++-|  decoding   |  |  | +->| Multimaster       | |
+| +------+------/  |  | |  +-------------------+ |
+| |            |   |  | +->| Slony             | |
+| |            v   |  | |  +-------------------+ |
+| +-------------+  |  | +->| Auditing          | |
+| |     TX      |  |  | |  +-------------------+ |
++-| reassembly  |  |  | +->| Mysql/...         | |
+| +-------------/  |  | |  +-------------------+ |
+| |            |   |  | +->| Custom Solutions  | |
+| |            v   |  | |  +-------------------+ |
+| +-------------+  |  | +->| Debugging         | |
+| |   Output    |  |  | |  +-------------------+ |
++-|   Plugin    |--|--|-+->| Data Recovery     | |
+  +-------------/  |  |    +-------------------+ |
+  |                |  |                          |
+  +----------------+  +--------------------------|
+------------------------------------------------------------------------------
+
+=== WAL enrichement ===
+
+To be able to decode individual WAL records at the very minimal they need to
+contain enough information to reconstruct what has happened to which row. The
+action is already encoded in the WAL records header in most of the cases.
+
+As an example of missing data, the WAL record emitted when a row gets deleted,
+only contains its physical location. At the very least we need a way to
+identify the deleted row: in a relational database the minimal amount of data
+that does that should be the primary key footnote:[Yes, there are use cases
+where the whole row is needed, or where no primary key can be found].
+
+We propose that for now it is enough to extend the relevant WAL record with
+additional data when the newly introduced 'WAL_level = logical' is set.
+
+Previously it has been argued on the hackers mailing list that a generic 'WAL
+record annotation' mechanism might be a good thing. That mechanism would allow
+to attach arbitrary data to individual wal records making it easier to extend
+postgres to support something like what we propose.. While we don't oppose that
+idea we think it is largely orthogonal issue to this proposal as a whole
+because the format of a WAL records is version dependent by nature and the
+necessary changes for our easy way are small, so not much effort is lost.
+
+A full annotation capability is a complex endeavour on its own as the parts of
+the code generating the relevant WAL records has somewhat complex requirements
+and cannot easily be configured from the outside.
+
+Currently this is contained in the http://archives.postgresql.org/message-id/1347669575-14371-6-git-send-email-andres@2ndquadrant.com[Log enough data into the wal to reconstruct logical changes from it] patch.
+
+=== WAL parsing & decoding ===
+
+The main complexity when reading the WAL as stored on disk is that the format
+is somewhat complex and the existing parser is too deeply integrated in the
+recovery system to be directly reusable. Once a reusable parser exists decoding
+the binary data into individual WAL records is a small problem.
+
+Currently two competing proposals for this module exist, each having its own
+merits. In the grand scheme of this proposal it is irrelevant which one gets
+picked as long as the functionality gets integrated.
+
+The mailing list post
+http:http://archives.postgresql.org/message-id/1347669575-14371-3-git-send-email-andres@2ndquadrant.com[Add
+support for a generic wal reading facility dubbed XLogReader] contains both
+competing patches and discussion around which one is preferable.
+
+Once the WAL has been decoded into individual records two major issues exist:
+
+1. records from different transactions and even individual user level actions
+are intermingled
+1. the data attached to records cannot be interpreted on its own, it is only
+meaningful with a lot of required information (including table, columns, types
+and more)
+
+The solution to the first issue is described in the next section: <<tx-reassembly>>
+
+The second problem is probably the reason why no mature solution to reuse the
+WAL for logical changeset generation exists today. See the <<snapbuilder>>
+paragraph for some details.
+
+As decoding, Transaction reassembly and Snapshot building are interdependent
+they currently are implemented in the same patch:
+http://archives.postgresql.org/message-id/1347669575-14371-8-git-send-email-andres@2ndquadrant.com[Introduce
+wal decoding via catalog timetravel]
+
+That patch also includes a small demonstration that the approach works in the
+presence of DDL:
+
+[[example-of-decoding]]
+.Decoding example
+[NOTE]
+---------------------------
+/* just so we keep a sensible xmin horizon */
+ROLLBACK PREPARED 'f';
+BEGIN;
+CREATE TABLE keepalive();
+PREPARE TRANSACTION 'f';
+
+DROP TABLE IF EXISTS replication_example;
+
+SELECT pg_current_xlog_insert_location();
+CHECKPOINT;
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text
+varchar(120));
+begin;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+commit;
+
+
+ALTER TABLE replication_example ADD COLUMN bar int;
+
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4);
+INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL);
+COMMIT;
+
+/* slightly more complex schema change, still no table rewrite */
+ALTER TABLE replication_example DROP COLUMN bar;
+INSERT INTO replication_example(somedata, text) VALUES (3, 1);
+
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (3, 2);
+INSERT INTO replication_example(somedata, text) VALUES (3, 3);
+commit;
+
+ALTER TABLE replication_example RENAME COLUMN text TO somenum;
+
+INSERT INTO replication_example(somedata, somenum) VALUES (4, 1);
+
+/* complex schema change, changing types of existing column, rewriting the table */
+ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING
+(somenum::int4);
+
+INSERT INTO replication_example(somedata, somenum) VALUES (5, 1);
+
+SELECT pg_current_xlog_insert_location();
+
+/* now decode what has been written to the WAL during that time */
+
+SELECT decode_xlog('0/1893D78', '0/18BE398');
+
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:1 somedata[int4]:1 text[varchar]:1
+WARNING:  tuple is: id[int4]:2 somedata[int4]:1 text[varchar]:2
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:3 somedata[int4]:2 text[varchar]:1 bar[int4]:4
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:4 somedata[int4]:2 text[varchar]:2 bar[int4]:4
+WARNING:  tuple is: id[int4]:5 somedata[int4]:2 text[varchar]:3 bar[int4]:4
+WARNING:  tuple is: id[int4]:6 somedata[int4]:2 text[varchar]:4 bar[int4]:
+(null)
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:7 somedata[int4]:3 text[varchar]:1
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:8 somedata[int4]:3 text[varchar]:2
+WARNING:  tuple is: id[int4]:9 somedata[int4]:3 text[varchar]:3
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:10 somedata[int4]:4 somenum[varchar]:1
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  COMMIT
+WARNING:  BEGIN
+WARNING:  tuple is: id[int4]:11 somedata[int4]:5 somenum[int4]:1
+WARNING:  COMMIT
+
+---------------------------
+
+[[tx-reassembly]]
+=== TX reassembly ===
+
+In order to make usage of the decoded stream easy we want to present the user
+level code with a correctly ordered image of individual transactions at once
+because otherwise every user will have to reassemble transactions themselves.
+
+Transaction reassembly needs to solve several problems:
+
+1. changes inside a transaction can be interspersed with other transactions
+1. a top level transaction only knows which subtransactions belong to it when
+it reads the commit record
+1. individual user level actions can be smeared over multiple records (TOAST)
+
+Our proposed module solves 1) and 2) by building individual streams of records
+split by xid. While not fully implemented yet we plan to spill those individual
+xid streams to disk after a certain amount of memory is used. This can be
+implemented without any change in the external interface.
+
+As all the individual streams are already sorted by LSN by definition - we
+build them from the wal in a FIFO manner, and the position in the WAL is the
+definition of the LSN footnote:[the LSN is just the byte position int the WAL
+stream] - the individual changes can be merged efficiently by a k-way merge
+(without sorting!) by keeping the individual streams in a binary heap.
+
+To manipulate the binary heap a generic implementation is proposed. Several
+independent implementations of binary heaps already exist in the postgres code,
+but none of them is generic.  The patch is available at
+http://archives.postgresql.org/message-id/1347669575-14371-2-git-send-email-andres@2ndquadrant.com[Add
+minimal binary heap implementation].
+
+[NOTE]
+============
+The reassembly component was previously coined ApplyCache because it was
+proposed to run on replication consumers just before applying changes. This is
+not the case anymore.
+
+It is still called that way in the source of the patch recently submitted.
+============
+
+[[snapbuilder]]
+=== Snapshot building  ===
+
+To decode the contents of wal records describing data changes we need to decode
+and transform their contents. A single tuple is stored in a data structure
+called HeapTuple. As stored on disk that structure doesn't contain any
+information about the format of its contents.
+
+The basic problem is twofold:
+
+1. The wal records only contain the relfilenode not the relation oid of a table
+11. The relfilenode changes when an action performing a full table rewrite is performed
+1. To interpret a HeapTuple correctly the exact schema definition from back
+when the wal record was inserted into the wal stream needs to be available
+
+We chose to implement timetraveling access to the system catalog using
+postgres' MVCC nature & implementation because of the following advantages:
+
+* low amount of additional data in wal
+* genericity
+* similarity of implementation to Hot Standby, quite a bit of the infrastructure is reusable
+* all kinds of DDL can be handled in reliable manner
+* extensibility to user defined catalog like tables
+
+Timetravel access to the catalog means that we are able to look at the catalog
+just as it looked when changes were generated. That allows us to get the
+correct information about the contents of the aforementioned HeapTuple's so we
+can decode them reliably.
+
+Other solutions we thought about that fell through:
+* catalog only proxy instances that apply schema changes exactly to the point
+  were decoding using ``old fashioned'' wal replay
+* do the decoding on a 2nd machine, replicating all DDL exactly, rely on the catalog there
+* do not allow DDL at all
+* always add enough data into the WAL to allow decoding
+* build a fully versioned catalog
+
+The email thread available under
+http://archives.postgresql.org/message-id/201206211341.25322.andres@2ndquadrant.com[Catalog/Metadata
+consistency during changeset extraction from WAL] contains some details,
+advantages and disadvantages about the different possible implementations.
+
+How we build snapshots is somewhat intricate and complicated and seems to be
+out of scope for this document. We will provide a second document discussing
+the implementation in detail. Let's just assume it is possible from here on.
+
+[NOTE]
+Some details are already available in comments inside 'src/backend/replication/logical/snapbuild.{c,h}'.
+
+=== Output Plugin ===
+
+As already mentioned previously our aim is to make the implementation of output
+plugins as simple and non-redundant as possible as we expect several different
+ones with different use cases to emerge quickly. See <<basic-schema>> for a
+list of possible output plugins that we think might emerge.
+
+Although we for now only plan to tackle logical replication and based on that a
+multi-master implementation in the near future we definitely aim to provide all
+use-cases with something easily useable!
+
+To decode and translate local transaction an output plugin needs to be able to
+transform transactions as a whole so it can apply them as a meaningful
+transaction at the other side.
+
+What we do to provide that is, that very time we find a transaction commit and
+thus have completed reassembling the transaction we start to provide the
+individual changes to the output plugin. It currently only has to fill out 3
+callbacks:
+[options="header"]
+|=====================================================================================================================================
+|Callback |Passed Parameters                    |Called per TX  | Use
+|begin    |xid                                  |once           |Begin of a reassembled transaction
+|change   |xid, subxid, change, mvcc snapshot   |every change   |Gets passed every change so it can transform it to the target format
+|commit   |xid                                  |once           |End of a reassembled transaction
+|=====================================================================================================================================
+
+During each of those callback an appropriate timetraveling SnapshotNow snapshot
+is setup so the callbacks can perform all read-only catalog accesses they need,
+including using the sys/rel/catcache. For obvious reasons only read access is
+allowed.
+
+The snapshot guarantees that the result of lookups are be the same as they
+were/would have been when the change was originally created.
+
+Additionally they get passed a MVCC snapshot, to e.g. run sql queries on
+catalogs or similar.
+
+[IMPORTANT]
+============
+At the moment none of these snapshots can be used to access normal user
+tables. Adding additional tables to the allowed set is easy implementation
+wise, but every transaction changing such tables incurs a noticeably higher
+overhead.
+============
+
+For now transactions won't be decoded/output in parallel. There are ideas to
+improve on this, but we don't think the complexity is appropriate for the first
+release of this feature.
+
+This is an adoption barrier for databases where large amounts of data get
+loaded/written in one transaction.
+
+=== Setup of replication nodes ===
+
+When setting up a new standby/consumer of a primary some problem exist
+independent of the implementation of the consumer. The gist of the problem is
+that when making a base backup and starting to stream all changes since that
+point transactions that were running during all this cannot be included:
+
+* Transaction that have not committed before starting to dump a database are
+  invisible to the dumping process
+
+* Transactions that began before the point from which on the WAL is being
+  decoded are incomplete and cannot be replayed
+
+Our proposal for a solution to this is to detect points in the WAL stream where we can provide:
+
+. A snapshot exported similarly to pg_export_snapshot() footnote:[http://www.postgresql.org/docs/devel/static/functions-admin.html#FUNCTIONS-SNAPSHOT-SYNCHRONIZATION] that can be imported with +SET TRANSACTION SNAPSHOT+ footnote:[http://www.postgresql.org/docs/devel/static/sql-set-transaction.html]
+. A stream of changes that will include the complete data of all transactions seen as running by the snapshot generated in 1)
+
+See the diagram.
+
+[[setup-schema]]
+.Control flow during setup of a new node
+["ditaa",scaling="0.7"]
+------------------------------------------------------------------------------
++----------------+
+| Walsender  |   |                               +------------+
+|            v   |                               | Consumer   |
++-------------+  |<--IDENTIFY_SYSTEM-------------|            |
+|     WAL     |  |                               |            |
+|  decoding   |  |----....---------------------->|            |
++------+------/  |                               |            |
+|            |   |                               |            |
+|            v   |                               |            |
++-------------+  |<--INIT_LOGICAL $PLUGIN--------|            |
+|     TX      |  |                               |            |
+| reassembly  |  |---FOUND_STARTING %X/%X------->|            |
++-------------/  |                               |            |
+|            |   |---FOUND_CONSISTENT %X/%X----->|            |
+|            v   |---pg_dump snapshot----------->|            |
++-------------+  |---replication slot %P-------->|            |
+|   Output    |  |                               |            |
+|   Plugin    |  |    ^                          |            |
++-------------/  |    |                          |            |
+|                |    +-run pg_dump separately --|            |
+|                |                               |            |
+|                |<--STREAM_DATA-----------------|            |
+|                |                               |            |
+|                |---data ---------------------->|            |
+|                |                               |            |
+|                |                               |            |
+|                |  ---- SHUTDOWN -------------  |            |
+|                |                               |            |
+|                |                               |            |
+|                |<--RESTART_LOGICAL $PLUGIN %P--|            |
+|                |                               |            |
+|                |---data----------------------->|            |
+|                |                               |            |
+|                |                               |            |
++----------------+                               +------------+
+
+------------------------------------------------------------------------------
+
+=== Disadvantages of the approach ===
+
+* somewhat intricate code for snapshot timetravel
+* output plugins/walsenders need to work per database as they access the catalog
+* when sending to multiple standbys some work is done multiple times
+* decoding/applying multiple transactions in parallel is somewhat hard
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 310a45c..6fae278 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -17,3 +17,9 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 OBJS = decode.o logical.o logicalfuncs.o reorderbuffer.o snapbuild.o
 
 include $(top_srcdir)/src/backend/common.mk
+
+DESIGN.pdf: DESIGN.txt
+	a2x -v --fop -f pdf -D $(shell pwd) $<
+
+README.SNAPBUILD.pdf: README.SNAPBUILD.txt
+	a2x -v --fop -f pdf -D $(shell pwd) $<
diff --git a/src/backend/replication/logical/README.SNAPBUILD.txt b/src/backend/replication/logical/README.SNAPBUILD.txt
new file mode 100644
index 0000000..b6c7470
--- /dev/null
+++ b/src/backend/replication/logical/README.SNAPBUILD.txt
@@ -0,0 +1,241 @@
+= Snapshot Building =
+:author: Andres Freund, 2nQuadrant Ltd
+
+== Why do we need timetravel catalog access ==
+
+When doing WAL decoding (see DESIGN.txt for reasons to do so), we need to know
+how the catalog looked at the point a record was inserted into the WAL, because
+without that information we don't know much more about the record other than
+its length.  It's just an arbitrary bunch of bytes without further information.
+Unfortunately, due the possibility that the table definition might change we
+cannot just access a newer version of the catalog and assume the table
+definition continues to be the same.
+
+If only the type information were required, it might be enough to annotate the
+wal records with a bit more information (table oid, table name, column name,
+column type) --- but as we want to be able to convert the output to more useful
+formats such as text, we additionally need to be able to call output functions.
+Those need a normal environment including the usual caches and normal catalog
+access to lookup operators, functions and other types.
+
+Our solution to this is to add the capability to access the catalog such as it
+was at the time the record was inserted into the WAL. The locking used during
+WAL generation guarantees the catalog is/was in a consistent state at that
+point.  We call this 'time-travel catalog access'.
+
+Interesting cases include:
+
+- enums
+- composite types
+- extension types
+- non-C functions
+- relfilenode to table OID mapping
+
+Due to postgres' non-overwriting storage manager, regular modifications of a
+table's content are theoretically non-destructive. The problem is that there is
+no way to access an arbitrary point in time even if the data for it is there.
+
+This module adds the capability to do so in the very limited set of
+circumstances we need it in for WAL decoding. It does *not* provide a general
+time-travelling facility.
+
+A 'Snapshot' is the data structure used in postgres to describe which tuples
+are visible and which are not. We need to build a Snapshot which can be used to
+access the catalog the way it looked when the wal record was inserted.
+
+Restrictions:
+
+- Only works for catalog tables or tables explicitly marked as such.
+- Snapshot modifications are somewhat expensive
+- it cannot build initial visibility information for every point in time, it
+  needs a specific circumstances to start.
+
+== How are time-travel snapshots built ==
+
+'Hot Standby' added infrastructure to build snapshots from WAL during recovery in
+the 9.0 release. Most of that can be reused for our purposes.
+
+We cannot reuse all of the hot standby infrastructure because:
+
+- we are not in recovery
+- we need to look at interim states *inside* a transaction
+- we need the capability to have multiple different snapshots arround at the same time
+
+Normally the catalog is accessed using SnapshotNow which can legally be
+replaced by SnapshotMVCC that has been taken at the start of a scan. So catalog
+timetravel contains infrastructure to make SnapshotNow catalog access use
+appropriate MVCC snapshots. They aren't generated with GetSnapshotData()
+though, but reassembled from WAL contents.
+
+We collect our data in a normal struct SnapshotData, repurposing some fields
+creatively:
+
+- +Snapshot->xip+ contains all transaction we consider committed
+- +Snapshot->subxip+ contains all transactions belonging to our transaction,
+  including the toplevel one
+- +Snapshot->active_count+ is used as a refcount
+
+The meaning of +xip+ is inverted in comparison with non-timetravel snapshots in
+the sense that members of the array are the committed transactions, not the in
+progress ones. Because usually only a tiny percentage of comitted transactions
+will have modified the catalog between xmin and xmax this allows us to keep the
+array small in the usual cases. It also makes subtransaction handling easier
+since we neither need to query pg_subtrans (which we couldn't anyway since it's
+truncated at restart) nor have problems with suboverflowed snapshots.
+
+== Building of initial snapshot ==
+
+We can start building an initial snapshot as soon as we find either an
++XLOG_RUNNING_XACTS+ or an +XLOG_CHECKPOINT_SHUTDOWN+ record because they allow us
+to know how many transactions are running.
+
+We need to know which transactions were running when we start to build a
+snapshot/start decoding as we don't have enough information about them (they
+could have done catalog modifications before we started watching). Also, we
+wouldn't have the complete contents of those transactions, because we started
+reading after they began.  (The latter is also important when building
+snapshots that can be used to build a consistent initial clone.)
+
+There also is the problem that +XLOG_RUNNING_XACT+ records can be
+'suboverflowed' which means there were more running subtransactions than
+fitting into shared memory. In that case we use the same incremental building
+trick hot standby uses which is either
+
+1. wait till further +XLOG_RUNNING_XACT+ records have a running->oldestRunningXid
+after the initial xl_runnign_xacts->nextXid
+2. wait for a further +XLOG_RUNNING_XACT+ that is not overflowed or
+a +XLOG_CHECKPOINT_SHUTDOWN+
+
+When we start building a snapshot we are in the +SNAPBUILD_START+ state. As
+soon as we find any visibility information, even if incomplete, we change to
++SNAPBUILD_INITIAL_POINT+.
+
+When we have collected enough information to decode any transaction starting
+after that point in time we fall over to +SNAPBUILD_FULL_SNAPSHOT+. If those
+transactions commit before the next state is reached, we throw their complete
+contents away.
+
+As soon as all transactions that were running when we switched over to
++SNAPBUILD_FULL_SNAPSHOT+ commit, we change state to +SNAPBUILD_CONSISTENT+.
+Every transaction that commits from now on gets handed to the output plugin.
+When doing the switch to +SNAPBUILD_CONSISTENT+ we optionally export a snapshot
+which makes all transactions that committed up to this point visible.  This
+exported snapshot can be used to run pg_dump; replaying all changes emitted
+by the output plugin on a database restored from such a dump will result in
+a consistent clone.
+
+["ditaa",scaling="0.8"]
+---------------
+
+        +-------------------------+
+   +----|SNAPBUILD_START          |-------------+
+   |    +-------------------------+             |
+   |                 |                          |
+   |                 |                          |
+   |     running_xacts with running xacts       |
+   |                 |                          |
+   |                 |                          |
+   |                 v                          |
+   |    +-------------------------+             v
+   |    |SNAPBUILD_FULL_SNAPSHOT  |------------>|
+   |    +-------------------------+             |
+XLOG_RUNNING_XACTS   |                      saved snapshot
+  with zero xacts    |                 at running_xacts's lsn
+   |                 |                          |
+   |     all running toplevel TXNs finished     |
+   |                 |                          |
+   |                 v                          |
+   |    +-------------------------+             |
+   +--->|SNAPBUILD_CONSISTENT     |<------------+
+        +-------------------------+
+
+---------------
+
+== Snapshot Management ==
+
+Whenever a transaction is detected as having started during decoding in
++SNAPBUILD_FULL_SNAPSHOT+ state, we distribute the currently maintained
+snapshot to it (i.e. call ReorderBufferSetBaseSnapshot). This serves as its
+initial snapshot. Unless there are concurrent catalog changes that snapshot
+will be used for the decoding the entire transaction's changes.
+
+Whenever a transaction-with-catalog-changes commits, we iterate over all
+concurrently active transactions and add a new SnapshotNow to it
+(ReorderBufferAddSnapshot(current_lsn)). This is required because any row
+written from now that point on will have used the changed catalog contents.
+
+When decoding a transaction that made catalog changes itself we tell that
+transaction that (ReorderBufferAddNewCommandId(current_lsn)) which will cause
+the decoding to use the appropriate command id from that point on.
+
+SnapshotNow's need to be setup globally so the syscache and other pieces access
+it transparently. This is done using two new tqual.h functions:
+SetupDecodingSnapshots() and RevertFromDecodingSnapshots().
+
+== Catalog/User Table Detection ==
+
+Since we only want to store committed transactions that actually modified the
+catalog we need a way to detect that from WAL:
+
+Right now, we assume that every transaction that commits before we reach
++SNAPBUILD_CONSISTENT+ state has made catalog modifications since we can't rely
+on having seen the entire transaction before that. That's not harmful beside
+incurring some price in memory usage and runtime.
+
+After having reached consistency we recognize catalog modifying transactions
+via HEAP2_NEW_CID and HEAP_INPLACE that are logged by catalog modifying
+actions.
+
+== mixed DDL/DML transaction handling  ==
+
+When a transactions uses DDL and DML in the same transaction things get a bit
+more complicated because we need to handle CommandIds and ComboCids as we need
+to use the correct version of the catalog when decoding the individual tuples.
+
+For that we emit the new HEAP2_NEW_CID records which contain the physical tuple
+location, cmin and cmax when the catalog is modified. If we need to detect
+visibility of a catalog tuple that has been modified in our own transaction -
+which we can detect via xmin/xmax - we look in a hash table using the location
+as key to get correct cmin/cmax values.
+From those values we can also extract the commandid that generated the record.
+
+All this only needs to happen in the transaction performing the DDL.
+
+== Cache Handling ==
+
+As we allow usage of the normal {sys,cat,rel,..}cache we also need to integrate
+cache invalidation. For transactions that only do DDL thats easy as everything
+is already provided by HS. Everytime we read a commit record we apply the
+sinval messages contained therein.
+
+For transactions that contain DDL and DML cache invalidation needs to happen
+more frequently because we need to all tore down all caches that just got
+modified. To do that we simply apply all invalidation messages that got
+collected at the end of transaction and apply them everytime we've decoded
+single change. At some point this can get optimized by generating new local
+invalidation messages, but that seems too complicated for now.
+
+XXX: talk about syscache handling of relmapped relation.
+
+== xmin Horizon Handling ==
+
+Reusing MVCC for timetravel access has one obvious major problem: VACUUM. Rows
+we still need for decoding cannot be removed but at the same time we cannot
+keep data in the catalog indefinitely.
+
+For that we peg the xmin horizon that's used to decide which rows can be
+removed. We only need to prevent removal of those rows for catalog like
+relations, not for all user tables. For that reason a separate xmin horizon
+RecentGlobalDataXmin got introduced.
+
+Since we need to persist that knowledge across restarts we keep the xmin for a
+in the logical slots which are safed in a crashsafe manner. They are restored
+from disk into memory at server startup.
+
+== Restartable Decoding ==
+
+As we want to generate a consistent stream of changes we need to have the
+ability to start from a previously decoded location without waiting possibly
+very long to reach consistency. For that reason we dump the current visibility
+information to disk everytime we read an xl_running_xacts record.
+
-- 
1.8.4.21.g992c386.dirty

#8Fujii Masao
masao.fujii@gmail.com
In reply to: Andres Freund (#7)
Re: logical changeset generation v6

On Tue, Sep 17, 2013 at 11:31 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-09-17 09:45:28 -0400, Peter Eisentraut wrote:

On 9/15/13 11:30 AM, Andres Freund wrote:

On 2013-09-15 11:20:20 -0400, Peter Eisentraut wrote:

On Sat, 2013-09-14 at 22:49 +0200, Andres Freund wrote:

Attached you can find the newest version of the logical changeset
generation patchset.

You probably have bigger things to worry about, but please check the
results of cpluspluscheck, because some of the header files don't
include header files they depend on.

Hm. I tried to get that right, but it's been a while since I last
checked. I don't regularly use cpluspluscheck because it doesn't work in
VPATH builds... We really need to fix that.

I'll push a fix for that to the git tree, don't think that's worth a
resend in itself.

This patch set now fails to apply because of the commit "Rename various
"freeze multixact" variables".

And I am even partially guilty for that patch...

Rebased patches attached.

When I applied all the patches and do the compile, I got the following error:

gcc -O0 -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -g -I. -I../../../../src/include -D_GNU_SOURCE -c -o
snapbuild.o snapbuild.c
snapbuild.c:187: error: redefinition of typedef 'SnapBuild'
../../../../src/include/replication/snapbuild.h:45: note: previous
declaration of 'SnapBuild' was here
make[4]: *** [snapbuild.o] Error 1

When I applied only
0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch,
compiled the source, and set up the asynchronous replication, I got
the segmentation
fault.

LOG: server process (PID 12777) was terminated by signal 11:
Segmentation fault

Regards,

--
Fujii Masao

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Andres Freund
andres@2ndquadrant.com
In reply to: Fujii Masao (#8)
8 attachment(s)
Re: logical changeset generation v6

On 2013-09-19 14:08:36 +0900, Fujii Masao wrote:

When I applied all the patches and do the compile, I got the following error:

gcc -O0 -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -g -I. -I../../../../src/include -D_GNU_SOURCE -c -o
snapbuild.o snapbuild.c
snapbuild.c:187: error: redefinition of typedef 'SnapBuild'
../../../../src/include/replication/snapbuild.h:45: note: previous
declaration of 'SnapBuild' was here
make[4]: *** [snapbuild.o] Error 1

Hm. Somebody had reported that previously and I tried to fix it but
obviously I failed. Unfortunately I don't see that warning in any of the
gcc versions I have tried locally.

Hopefully fixed.

When I applied only
0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch,
compiled the source, and set up the asynchronous replication, I got
the segmentation
fault.

Fixed, I mismerged something, sorry for that.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Add-information-about-a-tables-primary-.patch.gzapplication/x-patch-gzipDownload
�d�:R0003-wal_decoding-Add-information-about-a-tables-primary-.patch�WmS����'�Pl��^c���I����)��{�d2�,�����]i�����v�L���x�]I���y9+�9�G�D��/�:��D�h(5���d4�?<���~�
]�%�����{�$:�czcT�-��4�^
��ZQ��u+}�N���lSr�y�{�>%��~�����z�]��_�t�������?�~w���Ev���Uj����f�s�R�%�y�H��:,�t.�%��eD.'��R:�Eg�8�Q�S�������dn�H�%7�t�*�'$�(�@��.�4�o����*m\:YB�H�Y976�=8#2r�t����(4���?(�T�5Pf0T�-*\�J��.}�,�,��<U��Y�IZXG���w�)Z�fmJ�s�R��
�Lg!
�B%H���������5�r����9���������*���:������Fw"+���?hw����5[d�V��?w��Z����f�%�f:[L��R�vA�,��8H�^dx����V�a�qp~�z7Xm���R�w�]����g�Y������b>�MiY��ZF�R�-������k����y�)X@0�d����r�b�?�X�=&!��/���q��{�E���j/s��ak#V�M6���d=�A�oe�h	�RVS���mu:��l!�c!o��n���v��3�-t�_bI�A����o�/52+����S<���?)Y�QX�,Sm&p���l�{�6������V�t2�Ng
kE�+L��(D~<�r(TJ�T��7�[_%+�M_'��k����6����^�z!
/��WR��3Y@�k?j��.�n�\���o�����e�N�Ny��h ax'��+���
.n����������:*U�+�Q�'���|�������������T��~@@�	�������%�?|[b���$G��}w�O���?r^��e�5_���Y�b
@������	g�!�"���>.�|%s����A���pB�7_x���?lx�#����W����bQ���L�f.=Z��l;�*O�'��+�P���8��
� t�L��[�m���)F8�^Q�{���m�ij�J������<%Pb�����'fz����z�\�"���r>F�x�����nR������������pu�6W�g@�
�k2���-���bz����(b��c���G-����k&��=c��?Ux*�_d�b�����k����z�_�T�	��������Y��\�!+��Oa]�������5������L���y��^s��
��c����7q��>O/�$����J��;�O��}�o���7�~��4�PK9�F��."�/3������`n������/�m������5��sL�M�:��a?JV�����?br��P�Y[���7eM�#9��_���z0[OT�z�����������'e���;�A���@�g+��b�b����B��W�:���
����[bn5
7P�J�9����X!z`�J�z���d��a�y4$t�d�[��!r����j�6�}.o[�bw�A�r�+��2�o��:49�u\�B���n�m����I����?�S�9�#y$Fq�����`����F���l����$(����d�5�G�'[,j\�t��w�J��5�*����Ym�����O6��`�@j�Gp+F ��v��vV��H�l��|f�'Y��`l��62/0-�$y[�}�X�@�w�oy��z�����Myr��:��w�����\��ow����W-��c���f=f�
����2"A����������t?���_��b9����-��-h�.r�C��y���:��+v\V�[��)��g$K�`�
��D�6�~��EI<�q?��GG}�?�*-�2��-@���
0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
�e�:R0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch�\is�F��L���k�!%�����ldI�Y��������P 0$��CG���}�gH��?��5��tOOO���6�g��:����j����~���m�][�F��w��G=���e���r.���:�����o��X�xv C�6��g����g�3��6m�S�����N��h1�N����q�u�w �[�W��G�I+:�^�O�����g�`��--�v����{Q���%�]$���1�eF��O�"rf2
�{�V���
'!
E(�e�h
����%~��2���~3�t�~�XSH3�l��,���"��B1&�"��c&�1�!�p�����&2$��g�G����[1��a"����� ���HD���1h������.�G�o�1('4��Ha�I�sw��M�q�Z�����hs�c2�S:�S3�T\g�����hGb��6N4{��uG�uV�89��$������fx�C���������b+��9�%F���X6=��0�h*���G��q�x�����O�4@��?4W���'���z���������Fv���q]���5t�HzQ�~C���x�@B*�zb��^��@�](�LA�.��`��hNQ�d��O�8���V���`W�p$����f��`�7��J+aV-D�P�g��BK+3���	�hA(%(|��}����"^�HN�t,�H�BfM$�*��-,��$�E`K������p�P.qu�{�ul3���}5����ds�T�|0
��G&f���Iy2�C���l0�y�����L���|��20[h�v��Y��?�#�h`�����%�K^G�C6;t�J��>	g�:�	>?L�'�~M(��i�Ll���{�qC���jn��#����z-I��������U?��'�Ny��$�H�7���^:ww�8�g�a���6��q���~�d4u~s"qk�vn�W�@1�+���c2�[gC�q&��zaA�-$����oK���bt���������(pF�Z��8�i`:�g�'����=t�HZ�����0�v)@���E��K�{��ti	��i�����[b��r*�9���,MJ�����+���A�I�������:0�G�Z�%����	bgh���ay��h��)�#������R��l7z��S8NNb�\)nB�o,��H{���B�R�%�D���yR^�RBM���4|B����a3��-���L����O��qKZ4!����HWP�'�5������V�8MBZ��<�k��T��4j�����9f>�p��A3�s����cyq#%e)5�)p�5-�[%C����r���c����=��S��k��b1�=��#$����f�9��/��G�[��<���a>{�m�)�lO��O��<(Z^�4�Efl�y������:�����R�P����#5�%r�����]gn�?g����$ c���j����Y�0����}�b`�I�E(2�)�	����"SdI���CINl���H��$�Il�m�����w��}o�D����3e{����c;FV-�P�:����TID�9@�Hio�v�H���Me)�(�. ]d���V�(�x�*��"sfp&��
�w��D��x�,to��J���*�R9�����%R���U�� ��e�Q6r��	%V�<u5�~�r)�������B��^I��A%���D�-,i6�\XjG��:�$
.�Rt8�t�,�J��A���T\C�y3�B�����9�n�#�����������k�Ms
L����RsH�h����:���~�5$����[wk� nB��N��Vb�	i����A�L��5d+�U�HM+����
���*��7�f��I�)�|at���n����T�����]�8�V$ab^N�����x�>F�Oh��E���\�r6]'��F����
B��.4�
q�j
�e�{��v�!����t�j��W���I��J`��~��1j�&��q
���F���b	��V_I�.�Q���J��"]��_E����6��U����������a�k�a�*�3�holw[f�)�r�T�QMm�1��X�����bg��@��k�'�[�����f�8��H�j��~����W/�^�+���JG�-�9r���z:�������lF7Q��*��8��T*ON)2��������������������GcJ����X.���bl�Q<�&����3����L���}!��3��P��_
^M��X����>���>�5I���f�F�B��30��EC��*t�\6��a4A/u��C���md��W	i(�8p�'c�������Q����ug�!^4��v���s�vx��|�X�5Kl�>u��?W�on�7WW�/V�P���Y�������x-�fJeu����Z#M5�K/����W�zj��\�+�.�;�����G�f�<����zg��J��w#�:�k�f���.�}�t(�7�������q
�RP��������V*���K���w��)�j�W���B������FZ�Q@�i��orCc�7k��QW��<�@m������^�ft�f�$���N��V��t�	�1�%] �v9����c?,�v��l@��X���}��n�5;2|5�v?��_��R+�&�?���f��#��>\%��{��N>��K�&��lA�!),�XVS�>t.����B�l���:��\��$V��F���r"��4hYg�#�i�������&x�1<����F�K�
��<�4D��i�F��P��f,��B&ZN�]��M�P���J3��\ �8�0P��h:Q-�]��sc��tq��1V$��C��������<��V1�`����x�7�Rh}%�"{[jh���
s6����(�hpA��cR����-Vy��������F�E:'a&2�c[c/�L�L���z5������l�`G}(�����-qM���6��t1�.y����%AAS�1�7�g5Mf�>	9��,���r"ff��;��i�xu�g���8|J9�F����F@���3��29Q���|n�h.K^_���A?<������M�g����I#��6.@a��$2+�^N��_t���B+�8��8�y����
�M��9�Az]��m|8j����Pl3Bd
kj(f�>�y��}Q�-1l�4�D�u��F+�Cx�����XQXb�$'������qj]#�a��^��
�wb���VF���i�!;?)v�D�w2��|M�����d�`���?N�c(t�s�J.�4��^u���l+�	SGMS����lzZ�������b6��o���S����=���`DWqT�a�/X&a���J���r��Q���T��.L,��A����R�o����@��z'��2W������R��5v��v��V�v����Rk�1.C��52,�#����>�r]�v������(�����?��N9~�~f5�>3�k�z�BM��G���*SV9���U���������������<W����]��aH���������M([���4������E��#����?��8v]���6�M_��;�=m3#�� K	��=���}���B�������._����������d:���=����zS�h�7�9��v��l�3S��e�"�Xm��Y���aU�����w������t]=(���f���0P*u��a"�Zbz����W'��/g�J�7��;d���f	l�h%�,����$,�%��������w���fpn��On����hi� N�64�L�Q��l���=#yz&I��n�-Z��Z�5Kl��K����;�F�������=�_�c5~�t����>.|���u��Wc�=��i�__�p��/>\6����b���qJ.
�F�6� ����|���eM�t>kd@����A�������Xy���F��
�����g���Yl�K�Y
u��,'?�^TJR�+
QD�00dX�c�T�aE
;�r��pZ����S|g��;��/R�'��&�l���@-j$�eL'@�1�����Q���t�I��?P��}/)�!^����hE1gi�uU;� �,ob(��u�1*IC��jJ�'j�ZK�e�_~��_�yN�J�l���M~�W��d@����1��zp���Js;7A�$K02���j�2v�P�C'P�6'H_�+D��p�C��`~�3��/0!�t{����B����L;a��H�J�J��<�sk�0��5(����;9@�c_�nN�~-��.��4g�F��Kw������s����V���weW}=Y���	Zqv5�%���\h���'o(�	��������;���z�2�F��R?bPO�,XG.����R%�J%D�Hy;7-�.U��o�&{^���f�I�ZN��PK
�/En������r����%]5Gc�]~�)`�CF������������Z������v]�Ee����-�Bv*eh��`��
1]��;��/�a?����*����
�+7�����������{�����j"��6���[S������yETc��g"f��t���SZf5�Fw~�:�P}��Z_�N#�������
A������x�zm�xvz��F��]�k��:�8�
�;?�f��?_����<S�W��.(v���EfH�@���$�+����U���z�f��<U5�v��U�����ENK^�3>@�%����Z���R�o�n�/���Q���v�jt����,���%Q���PP���(��w�m?
��N��{�
�pjP������M��>�8���i-7�.��E�CW�C�&�8��kT�*;��D�F�\C�K%{8|t��j�o��lE%�k���hIp�Rj�%��[�&G�� V��{{G��;������2X�1�X.z��o
������_�X@B'�����<j���_U1�mC��R�������]*�93����|J���C��9�H��Gr���^���V�a��k8n��.�(I�^���T�;n��x�J�$[�RQ���-����X��MU�h��|��R+���V�F|��JssS��1�#%�#�~+��P�4���]�7R�Tj�>����x>�CS������i�����l2��>�*�N����|������:���������7����S��mmq.����]C�t;��P�����2�L)|��wg]�[��suhUM���R�r�V����B��(�*%��)�R����AG/����ya�T)+�k X��S���-�a�����noa0_[�L�@�DfRW����rm]�6�G�A
�)v���$�%|if�)S^I=3��W�t������x�����kp��I	*���V=�X���*Y�:=_�)�
�%���v\P�����K*�9�hR7.%�W3W*�[��W�3�������������|Q��Y�����5U�T�����.�~�E������v+�5>�.�a���;����iF����*�T�{W���U���+�D��q�F`�����:��T�����9�S�\�[��B/{fD��q�~��~9f�[B������m!�|7�����{ge���H���-�c{�w;��E�JK1�
��R��_:j�3����&�C.�H��@������\7���.~@���C��c�����b65�s�7`f��`
N���,n*�F��g��>�	���f�k��R����r)����yv��8o�7���B����(�B�bV���J�5�JU�����Y��dx����6����A���?oh#K�����S���-�������-��{����H)���RJ���gc=K�I-��}g��;��2O�5N��8�����<��~}
����3�H����GO�n=�v���@�h�Z2&JVzq?N���P�i
��z}#M��0@s
���t�.���_t���D1c�dq�+�#i�d�2�:�:3�����~����
k���A��A��.�T�����_9�
G$z�WU�Y�e���k�R�0��
BX��
M<a_�GO��`�$���������;3l��h�*����a���E�2��=,��������j�Z�s��������q�89>=�4NO�O�l�AN��Q�����i���<m�I�X{�l�Im{����BK��a+rE��V�W����Mk�C����L����u�7�"��X����AV]E�����Z��3U]�0�4��-	�/�ks��-���"�>S_��D�.b���-��t$f������D�G�� ��G��m������Qd~�c_�
���A
�����X�?���v��v�I��h)��c�c�
��y�����`+�)����c�@��'Haj�#�b9B���V���2X���t�^��/Z�c	`�\��c��t�����n7
&�V�+�-�+��b���e�4kf����3��(��~�w�����;tn����%�f/!)�~��u�����mPg<<��O^��i����L��n����x�I�/��kQ��R_2.aS�#����N$�����r�[a]z�}w��.l�&a�����P�3�f�?�8�
�h����V��ksuy�����3R �[��1boJ�����@�����b���]�W�	t��O�7{��-��4>�^ �)����>��I$v�@��s��z�S]�����:M���������~��Y�7�����u����uw�t��c[�1�|�r@(@��/�>���r-�KS�'+��
�������'�U���<�Lc���/0;y��r���Gruv�c��X���������`8}��-�{:�Z�
�_��D$��I�#��u���!O�h*B��q����T�x����V���]_3=����l�f�AE�B��c8���fs��va��4�����	����e�wg�d����X���q�4�������C����}Q]�
e���QkY���rR'^����N�%h�/���=y�|[H�hS�� ��I�J���O*��C�4ZK�}���@d����0~�Z\��;���H��xb�6��%�{��K0�������&�^|�\o�?���K(�y����%����q���^n��u��������"����70���m���F=r��)���������2%"d��[�'��������m��-H�|��A��.��|Cw������|��mj�}�r��"��i|�	�Qx�hUU&���|�M����fm�d����h�>s���3�p�K�X��d�O�+����=�2��_�t�#���r����P�tW��Y�AG�jr^�O�~�~ ����{ �=.���/gt����=�1��B1c8��o�)�T�=�+3�6f5����S�^/��:�8Y������R=hy����)�!�S�
C��:��X���������k�����}�9m#���Mwa���F&��;��4\���U"�Z �1��rG�]������7�b8P/*�xq����-3��m�������:
��Z0���^�47(���#�H+����Q)�������/w�{�zr�������{q��y��b���z���Y�$a\
}�)
�-c�LFw��
�Yu�?H3
��0�eF��u-I9|>��<�b?:��9\��������m�T��wh{R��Y��
N��N��.gO
�zjC��HmO�L?����O��<����������1/���+�X,�|��;�xA�*��]
G��ot������kRVWG�	������tF���u�]��P�E]|��j��rW� .j���x 0�����t��V�u 0&�7��������8X��:7���c9������E�����s�N��	�-�w���<l��q�i����������b�k�;F�L-,k���y8[�xn��g#����)��:�CEx��\E�s�<��3�qL�_kFE�M��z>�PM�Y�����{j��#��nL�%]�`�aA�v��'��Oxr���B!�\�z� .�8ja28d+�^|��Pl�s�J�������@mF0��PE![�����;n�����K��,!��|������`���P�]���8���sG3 V��EFn������g����O����{�3eb��Up4�I�4��b�)���b8-n�s��CW��A��@����f-ah���
5�A�3�0N��t�\�8�-R��M��1>��o����[��6��n4�<����4��"kU�:%��ZG!���hs��X�4�u��
|����}�f�'0����i�����/�JB��?�vCE(D9��������(n��H`a;��u���j9��P	nX0�@�����>L��7^��px=�J%r��G&p�U��?���E+{�f�Aj7���R��&��������g��a*�r����kTo���#����l��V�����kA�)�J��Yq���A����V&ng�] U��X���{��_�bo�����1��=h����0jCjT��sP�� P����v����������"�������@��Z����A��������������%�E<~R����MVsvW����d���T�BBj#�y0J$�w��3�"�y}@���q�)����!������;Q&�RH�]���_S���WY ����9�'_e�zc/�57Npw��[Q������q-�@V��L�n�HZ��U��^�������G'|	���h{��M�A�)����n�rGd�Q>n�����"���[/h��m?�����>}�<8}�G@�8��V�2"�XE�]d^6�*\B�
B�i�L=��:v����e�z����;����E`(����Fc^����d�fK�Cy^�<�y}�,?����d����6��=}���[�����=���R:�m�c�OG�[������b+?p���-@m��S9�^��g���=������V������'�I�
H#�*%�%����7�$*_d��
���1��E���W��N3����Z����3J���x2���dlo�����MXd�������j8|����=��0����O�ux��oo��mC��;����c3uJ�l�����HJ���=G����
�������L�
Y��@a,y��
���8�>���?D)�d��J*�S}+Bt��NE�Rfc��t)�M07%���z��k�fH�_FynO+�Kz��m%wK��������{� �Z��`����^&l�2p<v���^_�Z�wRxN���y��0}��&o��Z��4$W���(��u-����\�m�L0���SH����n���^���q%7�E{�a,T0P�-&���_jis�+�P���ni�q���knR`�?�
-w��������������n{k��m��0�@��4��g�D�b>���d�Y)Y��ut��@�&N�E���Kj��A������pnu��=�m���f���-���M�0����=����x���������%+��Y!��P�R�N.�$_�X�PO��� �ql�;4�3�����*�=!�7/������-�9A"��@�B������a�
^<�����S`�D������)%��%���x�^�I^tm�����gz�?�$Q���'�v.�Q(����K6v�m'���t�z��U�����
��r�0~�,�&��'K���<�Dve�l�*�r��6�
iBU����<��A�����k���t	�Y!`�c� ����2F+�����_�^R�
^�^�P���c�NS�S?r��{M/�0�{�y��(�u�eW^�;�6f�[{bb�#M��"}�B�U^�4ba&k*�DG�i��x��(Oi���
/�<$�����KS@8��M�la�E����7�fy�:���dz]T���\"��H��}%��q;(<{g�}[@���b���9��9������1��/pf5O���&F@�x�D��(c���.Z�/^<v�z�d��������e�
S����)�#����JX�r6��}�(���@l�cK�p�W5"'�
���~0A� �6+�0���U9S��6b��]i��S���b��m(��F�Mj����vp��)�C���3G��+�!��@h��k����?�lg��x������C�;�:2���6�������G$���R] ���pmA!���GO;/0q�����d���j6�:����NY#h
�F~�&-�"�����t��xtk��$�4e����W0�b/��pg�����=H8�7��	�/�Wp�t����~A��8���+����yv�{�������Y���A��L��C�3X_�f-�������
��.����/�=�<o����O�'����T�������IQy���!qE���y�E	i@��.���7�q��r�U�^6���|����0*�l�`s$���b7j[_���d\pd���.�g#���y8y�.�~2"�4����,��S��J��s��������=J������K��<2-8�F���
Ti��6�}��M.pz3��=y{rz�G�J{�{�����?��U�����@-��/P���J�,2��f0�T�r`�d����O6xfY� �����f�(.#�i������N�~���3>��MR���$���?������B��W����Jx����������n�,�|A�����=�=j�T��c���[�8��"��1�l� ����_#�i4���L�~
�1��/{B�m�DCo�JG���6�zi�r�����i�4��y�q�_��)���.���
�q��@88zA5|
~���[�]o=�U��`s��}���fM��E����n���J�g:W�8�\X�Pn��M�e�940�/������6���tv�����
}�=���S�i��J�~�%��:���0s)1��!����G&��x����������)�����X��ni�YYZ>�F6�����S����]"@�?,@�oP�=����YlY�
'	�ZDR=��Kx3����d]g���G����P:����2A�P��������WBk��Zl��d�J����V���~g[�De�_1w�6�&�(�$�b��Lh4�Z��:�=\�{���
K�#�@7���<]p�W�O~]<�JVe�_X��<�9w.(pi���u|��Z�������#����}�0���Q�~�q,"���B�����g��Wcv���J6p�Nwp������u�pm�Dqpf����x��`���e��i7�pf����z}��������+[Z�'OX}�2����'(�Pi� =��V~�wb���05�h������AVo�(��'l���o�D�kD����3��.����w�[$�$r��OIHx���������^��W��R���=���y��r������(�o������$�1kA��!�]����	���G�����&�\�!�$d�2�$�|"���R���`��1�A�t��m%��v^R�E�I��Z���.��6���Q�S�1#N\�>,4�.e�!n�	cP���A/�$�^����	����^� R/�=E��/�E����)L��S����)�v'o�8��+���3����S�
��A� :��������fdR���5{�����E���<y�c��dl��:���DI�9Y�����%�n��o1G�u�H�&���c��b@�RyV�@q6{�d��Z�D���~���i�?���|d�:P���d���
����8���(����Mq�6����'�vS[9k&G�=]�\[��O�yO�>[�G(���3�TUy�,��^��G�i
�H*,���z	 �{�}(F��	��g���~$*.*������<I��<�s2�?y��:�i��EN�|��bN�h�/&��sY��|BV�����knj��t�����E?�Y�Y���}��;��m�k���������y.9�R��&S!��=��9�QTp�(�}��n�����y�"�(_E�}���Xu>C���������
u0�,Z���?�nFP2��Da�qn����"����_&=V6��b�����p�e���e��z��I�i����x���=�e�k�m��r$����U����g �o��>����3�r���k��l%�i�V�\�z�e��eD�+�����6�������t�
��d�H.��8p"-������;������ ������8������#���>�3�i��f �w�_�>�@���D���J��@��N��`i���f�����Uj6����-����o�f	F"b��&�&����OFz�FD^<�_q:�~39G��-'}aJ�gG���Zt��K���
'Z
�������k��8��9��;i7��UP�!��]�SX6���O|�NB����9&�A��#umg/�=3�
�b���3=?���Gg�n�O������6�psO
f����F��4���h���K��
�]W�
�_�E�G�\8�9_������pzH"^�I���_)'�|�#�>�P��,�(H�8MC��F�]��I4�O���>}�M,�%a��SK�J�w��9������x����R��U��&V��9���~�,~V�o��:���s�XC��*�!f�>���S����q�$_U�}qD���w6l��
	�B1A�]'�.Z�./d���C��#�
���A#�4-��-[�h�Y�F��l�^��p�����Y'�uLZ�(��[��n�0v��������s�c���]<h���5�y-0���s�	g�8��Z�S8�#���
�������G�Q/�j���<s���Z�������Q�<����_$���sl��|sh	�\�z��}�_��D�j�'i���c�R���"����
i���Gv`�
YL\��	��7����V���=!�������J*2�#��N�p�o.D�#S�����GV�I�aoxfzo[����h����~��� ����4|�4���n��m�����g|��J����<��y�MB_`���'����m�X6P�S���s�{���Z`q����b|��`&�^_j���b?�}�������D�b�yt���vsrz$=�������@�d�7��nk�C-�a��/g��kj/��n�et��B�G �s���ksQ�w�W�6�O��iP��\��5���uV�~����|1a7O�=�o���[q������PQ9�)%f�Ty�g������y#��A�#�ZAl:�]�{�L��5N�Yd5����u�d�=�_�</��Cz��%�%���,M��
����CP��o��a=�������3�N����r�^�d],<FNYx8b��t����\����{3���8�&��~WDH���^�i��{���|��l�)D��3�F���������_<��b��^o>i%�B,V �b!�o�d�\�M��u�����=D�
�m�4��vE���M�����\r	M9���Ca�Y���B:"��Q6cT.7�$���!��&�S�<�?����
<���`f�4'��y#��Rp����y8��5Q�<���N;�#]��OU5t11�8�S�)�F�����NT��b���4�V�b
U�B�{>����9��o�w�_��G�5.5������-�7�5�a~!:@?f`kv�&'�����&,`�"�7����8DG��t! **�]��������X2�E�P?�	�^�LN�=�
��5���$�)D`
��?M^��z���v�Y�|"�UL![��GO� k��7I��)���s����+��D,e����'�_��19���ukm�
2M�{�n����}�	"Q�`yn�*	����r	~4�6��>!WV�m�8qK���Y��@h��B}�h-r��N����N�^�TfS��r���BO���$~��^�d�yk���dj�BN�g�b74=���+
3�}� 1��w1��S<����"�����/�(�����eE��C���2�������\+��"�G���2�Q�&W��5��V��CO�>�k��jd"������@J����c��p�X�g>
g��e���,�,D�<�(�����`S�<$��-3������%}�a�J
�4�E��G���
	qM�#�\��6�v���Zs�;�-�p4hOZ��q=�bQ8L��-�~��SZj�DC{o����7h.Up�y��E�2��wok�MZ��1a$���5���LT��/y1?#B��,f�e�ft�8�|R����f�5����2�)I9��{�^�e�����
S=�/m�$�t���:����YVB��\�mo�]������<4��9WTXr?���*�����R�yI��Wy�.�"$�M����v���)�I��s��A����Y������N�^Z958�g`��OMQG��h�gak����?������y�d��n�o��z����$)�}����S�}��ZG�-����������T����]VV�c�#��nT���&�J/��9JH���h��?V��n�?VV�U#����&~���Y�*�D*[����`6��`_�J����TB��.��%2�P+w���wn�?��2m���A.q�r�����<�c�/]cgd@��2�<�J6�l\/'s�q5��:Gq�"*������V���l=J����J��,�n<�����f��;9ys���,��e�~�o�������U�
P����sl�z�"��3��2��B�vj2�����a�y9��ge�����>x�����)6 ���2=�����W�c��gB< @�&�4��'o����]b�:�\��-�tg�e�]F���!IVU��� 1lm����h�7���
�%�����pm7k����-�_�s{�{������-z�&I3!d���S��`�qt~��`o�j���Iu|�a-�e��(����G���:._�o��-��T���N���K�(�F�uP^���6�je���7J#��������7�������������>��\;������.^�ig������9I�k����$���G����+��	��:���^|#����(8���z�5q�,E�>�'����I/�s?�`�>z���QJ=rI�qe�D=�(�,����7~�����Q������	_^�99[!��_P�;W��|]��b��{����G,b�z���M���~���,&����d*�+���R�2�BLN�1��kdL�e$akSg#�:�[������Q�\u�xjEq�#J�su����
��#
R������OA[�zT#yV��?#&��q�CrE};L�3jz\����L%���>���*�d�_��~�n�Y+��l��R-����/6\�q�s�1������q�
(�q���p��]��-�j4��"by����;p��X(�.�,�\��%%��V^�n�|���V$hy�~m���J��T��+h���������"���r�i��[�NN`�V9�v�:�����m��P��������k�U��3���'&J�r�l��0�]�v���F�w-���}�����#�w�f�P�5���u�1yl�Ux�F,�'��	
�	*6|qFf(	_���C �|]����b��\#Q�H/)v����q10#^u\�Y�M���Y�P��x:+��9{3b{7�g����� a��`%^�A��hS�Q���u���x�}5u��%[TBE��H�b1x����|�V�iQBF�vd��m����SDk�TN��A;�":����M�9��6(�B�0	c�R�c���B�K����#�������@���lo�*$%���s~��z{���UG���%�s�.������3���;V8-�)}����>��{��?<}xv������������q��N�>}}~�(��n��]��������B+g��(<:q�a\��Q��}M�lo�V&���y����W+�m����n�������_�<l�=(WI�?�t��jJMy�I��=!���O���e~S������6-(*���������o���6�@m�Y�X"���t�������pp��|�����?�����{Ns��+F��L�z�����)e�j��=���6Dq�����s^C�������oO��E���MN6���)��]Nqm�"�yM{L�IX
�s���zU�(~7�z��������i��L_*S:�uiS�}`�<���9����C�}N��sM��F-�R�,�\��;��:B������X�q�����i��+�<9m� >���o����%�;��]��?	��X�B\�z0d|E�����������i�����7��jh'�,���n�A8JF#8}��_5������GGo�c�{K}�n���$�#u����)�e�lg�N�<	��fM!���������Y&��Y{���r&+uL��T����;��s�<������x�P�Fg���o���9���l�3�gZ�V��Yq�g}�Z`X���������(��"��6���������E4
��p�1�a:u��;c��u������G?o�bJP���s�� ��g�JzX32_e��=����$j��1"\g��h�"}�Z�e�	�:���M�������~���O��"�����p��#���X?%��n�.��t
kN���U��,jmaMa.��n����#<�h��xL�NF�E�N�h6@
)S%!�]b �3�o�,�h9��]R�S�U@o��T?12+7ATRW�iw�0�e=����V������c l70]�8~
�����ggR3�~@�����������y��������G?�%r��h�,�w$	�s$	����
/�<�)r����+�N����
�A�eL=�@�5���S���K�����Pk(�o�����L2tN)���E�L1�yug	������Q����D�F�pq0+�o�A�O��y@�P��������k�b�������I��zr��exg~<8�{�{�zw��'H6���w�7��0������������i�I��{���c��7'�3��_������) o��j�0��^�����Yp��|\�_i���D���M�Gg
�����#��e�4z���
|r�2�x���s�����#'����&50����_���,�0d54��Vq����W�iGk�<��6P� ��:�bP�E�����C�~�q�C����{��\���l�������������Q������$���<`#$�{�����q��"��1����>���zB  ��_��_K�������3�g5������)`�jT�`c�G�0�^����O^��9������=3a�Ge�n���d
'�� C�� qMteei������Q���A�p���h�)b2�������y��@zP�-�c����+'�R�C���k0/�A���Y��S�#A��m�Pwt��j�j�����A��������U!�j(g�	�*����u�\���.�����X��"��,(���J`��w���C��=q�	
��{�������F�?C�6e���-��'�<H���;?���dSx���;~<��8w,���iBc��]/A����*���,Y����n~�E���m����^7�Yl�5CD����;��t�����/Z���~%����+�(��Lh]�:���x@ J1o"�X��h������t$�&�{\�:��� ����r���2 �bos�1���{�.���!Fv�9�336���/N�c�uf�b'��A+o-�A������Y��`A������6�~��f�F�I4�j����$Z+ s����M}��37K�{ejq��2�4��V���
r��5��H�p��]���-;g��
+���W�y��A��%�%��Z���=�����9YiZ��z�Zb�>g
�M7��:��h@�/z={�6;�!g1`��ufEDgREE��C�)�K]��i*�va��&$�As�/�UA��*�.c6D��-��N�nN�X���:�<b�&��`��J��
2-w�p8�.c�"�P�I����_	���������o����A/������!(�}�|P�|MA�O�����H��SSS����B�$��89l�T��@,�Wpz@+����D���G��]�5a��5s����������4��Ln����m�����W-�C�1I&I`Q=�
�os��6�����`bq�L_\�Z����v�����p�Ft�ewWa;j�a��Zr����z�-����<g�����������	��R�d���9[�"���������[��uL}+�c���v�M�q�`��5��;��(z/S��B3����������5�-���>�3-���S7�14����Wa
�i�������f����>�>O�{��b<�M�|���U�O�����^��������0o����(/A~>���+S�<2�]���2wf�L�+��_���trM�TM�G���<T�����(y`c��b;�������Z^�2Y.w��K��D����F��f	���3�F?U�s���'p��#�8������W��&Y������*�$0Q|�������3��g����_u�#n����N��-�2S�]�)�]�&3+�-a/�4��������e`��+����,�lq���,�������h�,t�"w��N�H���X�����.�1�V���)��h0��T�1��
*��|{-��.�{I��"�F���&lH��5Y_���x#����hj��=����P$��T���V	~I��Wd]D���}&���L�\)��y�%#�'�[���s����Y���6]�8�3�����������G��Q�O�@����'9��\�`�Ud�N��t��>|��I/MB\���D5Jb�
z��s�5���1�1]���up��L'���%y :�5`���`�tlF�)�v��H�6<�3����0�Avp����52+�{���C��49Z#<���K�fF�Q�6
��gk/u4}�3����'��o���)������?�\unh�!�y`�d�~��]��<dA�!�z8E~�{�u7��Wi#s�yG���d�w��C�l-�
[{���/����E���&�������q�����Q��'�3z��K]P���$;Je� ��M������m�����Q�����#����Y?|&dg����}f3���s�f��6�u1�/��������������O���e[r��6?��0�l���W�F���Y���#�MNR������7�����;Ta>0Pp���~�����W���6C�1=�������sC�3����H��U�o���(�-y�)�-M�i7���?r��K��V��J�Ks���g�_���8C�MF����5����i9��J=�����c��6�d1C(���|��2bo�f�~��Dy�|��.m�lm>����"����1�1��H(p7����%����\X6o�w)�j��E�����x5������q���hBV�������6B]���1����������,B�<��Y�3�!pv�����������8�+��O�xq�Tk�$�)]��!�ec�waPw�(;��������rcAC��Lyt!��~�CC�����w��@���+@	��OA���9wo@�������(a#�o�]�����t=4�(J���I�an�{i�Dxc�S��Y�d�>����py���<��U���H��/�v��V�?je��b$��dVUD�{)�K����)�����x�2�����e�T�Z����T��v�~{���AT2� z���g��++*Qh�����$C�P5���i��?P����EnN�e�Rr���Mv�������h!wI�.��j��Aq��}x�h�U�rZX4�&���))*�����sjB���=���X���k���l��1wg���A,Hn�j{'���
�zN��������Q��u�$�n����R ����8][�s4g��c�4����tX+qC*��\ �8������f0">�5���,�� <�{/
z�aY�]�lWyP2af���>E�r���_(��|�3'�4������S��Z�%{�O�1 J(5G���������������u����(�}�nJ9k`���	�;3�I^+�~�z< E9�4�Z��,B��~g����������?���E+<�Z.��?�G�?m���Z2K���v5�iy�*��k��zw~u������AG��|�����A���f�I��<�����������5��}�e�^������]�����8�Bt����t����	j0���b����j?����y�������]4�G5���������p�.��a����-��vvx~o��_�������
>t^
m+����jw�Q����}8`�B�t�����
R�>�L��%r�����]�J���8�������V�m���k]V[B�a��_��	�u6��<��D�-4��Fx���@$�����
#��\V`j"h�x�`��$�2����,���f9�Y�F�xu1�
q/�1�(����l����{)T�G��m����y.L��(��7�W��E��h�Z�H��	_�P���T�&���mf�q��0E�5%��[������qN��i�q�����a�|��
P���H>t�������F�1�H��Y9���+(�u@DE�����s��N�V�C-T�^�����l���]�%1K�����,�e����������/���
�f��O#T�J��Y�F8Yf����D���6U`h;��sj�SO�pS_}�e���Bc�������k��[2�����X�o]��<����(7�Ua2�����2���,|�+����a�^5��=������������6�#����������������}�w���u<��!������mio�N���\�l��W��?���u��T�B�gM���4��%.�3.0�����
��� �������3�����a��-�a�	�h���=��N3��>�E���O�r����������z
,`c�������&�g����Z��S+�,�-��EQ�����f$�92�
��/p���6����SsQ�4#���K��xK,��k:-��613�RT����c'��r<�v�����BW^��&�/�h���z)�e�2�"�*
O���q��*2�S[��EZ�<����"���S.&iyr�1��������O9��l	���O�6��[LbLo�������F����i��7����G��y���g�
�P<o�5����-��|)�V�n����}q������)���a�yS��q�^�~����|��W��9�*;�*��~���,���D1eB��>��>*��)�C#f�iyxw]
F�o>EH(��=���;b����k�"�;�46�i'�h�XC�����K��^�����go��7��J#���1���9����Q_4�cO]���1�v�����	����~�X<����:Z�a����{6a	����m���/�'M����|>����pr��H�AeF�0:�=L���@k����r?��
9<(������K�v�8����`+/_�&�a�oH!�.��q�YK~���#I�n�����j=�	�.��Mp��Hg����(��E��E����^>��47���9x��������^�@����/D-5�t�$���T�Gdl�
�kO��[K+^��j2�\���_��+�n��������g��06!��'�[a9���}P�3�1a
L���&���=`�]��'@[^�����c=�Iy�����
k��~�(�|�^��>�g8�])M��El��G�[���19�j��m�}c�|��qD���)�G[�@_�c��Ni;�O���j��Ni��a�M���,�.*op�����)��/t��E~�}��y��sC��-]��fOg�!�t�_��.�`��eC�s�u��"Cf)E��^�	���|W[�;-w�P��!KY��g��X�+c�n�]�f���F�u������[�<����I�?NX�$�-��j�
�3��jOZ<o~N[&a_qE�@�o��V����wUT��B/�j������"M�s���d����/\0����xp�z<�j�~�J_����p����aT��/����wA��	9�z��u����*;��e����G� ��:^Zc5p���t��>[��:8�?��M������R��R������CrW�7w�t��.b�j��
�~A�����T��4����ytO]Y@�G�����U��S��Xp�!��+B��6+���i�[4�p��xsp�����2q�S)���l�����3��q���[��qp��pV����E�{(���{�t�������E�J��\�.0TA�j"����.�^�<��m]��z�q����|��0��n���s�}�I�'x|z����i��d�s,�'B�O�=E�����!��@j����Sd�����%�"�`@�����Q���krG���Pj�8�����H��P�����E�����h��A�H�s��3nI8$f3	��W8��.��	������l�����(�k	�Z(FvgS�+�$ ��]a��O]�R����tx�]��1���rt��d�Q��Q�����p.�cWR<�uI���>����{+%;�m���.p������iOT_��n"����F����Xte�E��\!���)��I|�FH�\Q7���s���Q��:�2,T�+p�gd?k�M[B���l������&�1!�`o$�q���D���J����PRl���;���V�K����.�'"hV�>b��~e���<�@9g�<�ia��3�-r"`�����;l�l$WK?N��%B�v�JB�#�p���5!#�� g>F���J�#��40	��9&��a��{^���	I
�rb��������v:s ���u
�6�[�^er/N{��n�E�t/�q4>�%>���^�����%%�f�M��3* ���OG���e~^rZ=�/��'���m,�TR��wU����8-G�"��|8��W�0R���e�0_N��S:q|o�T�!ecp�;�`�j����Q��lFC�fp����:��c�����P9z������8�J�T' �%J�������'���ld�p4���k�����i�0n]t|�zc8��S2�y��(���Dn��&9�=:�3!�z~6A�j�����'*�?��nN�F]�[97����>Z���!�YO�u�r
��i�:J��\���c(��.�F��y��<�(�5�n-'�>��.���<7R`
�'��p�d�A}b��~����~�G�Y$j��-S��7,�4
.?����J��iO�8EB������Km�>k�~����'.��$�q�hg�t�0}����������	o�Zj�������^8����k���!4���OG=JuS�A:�
����#(�cf�Gp	:H�w����+��q���1��t|k�S��KJ���Lc������oDF@�x��.
z�����C���
�@ux��%�^�P)��b0���5tq�J�������Y������q��R��T��A(��a��;��LD�4���0������y���77����������'o���vO��~�����)���>�_�a�������B���y�YkX'<tep�`���qD�A7v}Y���xD�q��!�l��|e��n	�8����I���������F��bNL;�����K/~0o8�~i�\W�u����`�!
?��R��u�h
���8��d��������oC��M�11w�c��O��8�)� (z�=������S���;��>�~L"�f�)��_��i*�?�b����,�)����(<�s�,���9?�t	*'<�'���P�W3X\���Rh�����;��������A
%>
��f��:<7U���S�'h�F��?a��0
�����]�d���dGf
����*���?+��re���,Y�����v��\
o�_��e�|� L+'�'�!�FX����35-��/OT6�
V�w���
�'B��
V6W���qt�K��x'����+CQ���'M������|���h$�d�5
M�������~ABw<7��K�= b���~�+wq��C�'U����<$��I�)b����\�T�������� �:��{�������)N��4����AQJ���VL����L�����F��S�.�|�]���Y��o�u��o�<73���s
78�F{��S��.�a��WHq��w����w���dj��\���zn ��O�H����&��t�h���	�=B,���]n�������b"���~���w��4��.�t�t�SK�� �������Hc�O{��V�Ss
��/���y��)����Q���v[�����|�/;� _~n���]�	�*u6sn{KnA$����H������u�������}�) �G�9�X����gW�q{p��M��!mj�w���k�f�V%P;�����	Q_���w�����?k���
DD|����w1�f9��nz�bQ���`��T�OS=�f:n�.��	3�����B���
L�j��h�
�"�������Us�I[��|.�&����b�>pu����z����	u��N,��p�nP&�`78�����
�g��6${�/�j���wE��)8���wp�QQA-�5��W1���"%K�nX�0�����|�6������w���M���Q�O��sDf?���
���<�+�-
'a3����a2��X%D�|�OG�*|V���mx�`p��/��m�3J�bV��[��c��%KQw���(UY���3���HjnS�Q���������4�jP=����Of�}G�����E.�`Y������)weV�F�=J3:
��v��r(���t�h��4MfKY���	�����t��~�K"��{�~D��d�g	����$������������u{O����Sx1�����"�MS�w�.-���q|:K�R��r>z�;~$K+-�.&K����{g���~�������������lL-���3����eH�v�=��~�Q`���"��<��a�#����:�{��T8B��VQ/�+�V;���^�(�G]�������~��R��vj��
KR,�m�17R/[�!�hR���J'h�[)�G���U�>� �\�r r�����L\�2���r��ny�I1M�.�~�3��Riz�F�$|�r[�:�5)�	~����?��_����SP��T-vd���H�'�N�(���6M�I�*�C�`�#��%	J@��I@Vq&���������U���_d��>!�<L��j���I�����T"�Z��%�;�/�6����Yw�5��RU�W��i+E���aq�l�a}��H���lZ�����uO�)/X!~��l��lSQ_aJ�(�;�y�t�E�W�@v9���0(	���$n��L�_��{w��T���H��vO^88�G<���������]3~���-��K>�+����������K�s�=z��g���V�$`g�!%}��*S�������G����lF./l �S3|��_!�QQ�;��;2V�������.���mQf
��@�I�a�'����7�����=}{��C�!���7��&r��eb�S�a3\�kbUbQlO���!���E"lOztg�K�1�Z��9����
4�`p1�x�+�
�j��}��z.�A����3JZC��r���������z���"p��o��wX�Yry4����Y��V��c�{()����!��}@AN��#$Z�0����W�o��F5h�V��J.uLK�=�3N�]�Q_����LN&���y��	�{��^��E+�m�NEu����3���X\ ��0����+U���,��Dp���R�K���'���Wiv��������>r��u��-����z�8�^����E��z)28���{�h!�>�����t�!�.����z1F,�N]�]����������g&c#��;K��j�����F����d/��pHKt�s�l��E���)�����	:w���S���H�d�g_5�['e�"`�8[t��@��43U�S��np��@���
�4�O]p��0�����u����2�p9��W�u����rI�1���g�|e��m�����W}�.+s0 ��kv�k��������w��4F��A�����e���P�]�*�>���WZ��5�u������S;Z�?�*�v�WV��M�MtI(!I�ZOH��V�����b�@��
:8k=L��r�C�[��b�Rc������@�GN���!���W<���sGB���������K;V��2�����Zo���'<*�����l�>�r���$�
(U��9'A�>�T�It��#����:<�#�p����,4M���m��,�$��H�1!�Q#���PK<7�S�6D�eY����O`�q��c0�����u1�aDDc� k���H:����7Q�v����$���~
����H��!R�f���z�dP���{�JQg�N<�i����r�~���$���b����^�W��>���fa%����9�m"pb��/)!�B�-�5�����|w�8�M)���������/�kr�s�s|$j�����y���~�&'K�=�t��d�5�EQA�F�mGMn����+�^���n���c'�����'
�������c�*���p��������
�����7,�
���@����yiZM��9�U����'_+��IS��I_��NG��O��}!��,�6��3��B$�a�ilal\���'�Qn��R���R/� Kk'���8�����a��zUD��G�F
�pS|p�/5�����,���]5s(��$�\����2S����<[���#W$u~�������P�M`_D���wv�M?�H2�wr��Z�%k_	���&m�Yk����G���m1Qq�M���8W55����[���jf��&'Z)I�M�D��*�v�Cng|�O:��(�t�C/���&5��&����i�Ys:�������?x7Pl�5V�Y'7e��������V����.L3��1\f�0�k`��C�I2G�#���%y������6���f}q>���x���*�����$���eA�>���������;����w��x����F�������[��)G�'rpnb��_����
��j��[U�h��"�PnS�|��v}��9t�C]W ��k����5!�Z�_��l��L��:}+5��:���������������8/H�o��S���s��@/������v��v��	*#�%N��U�"�K��-�L�]�4Mi'P�p�jg��"�7���������\�g��e�����%c������n}����8:�&x�����I����UxG?���3LF�_'����7�E/�I����K]����4���@C�$�pB����G�n9�2`�'DK�A�+�k�7�����4���� ��������P?l�u�����@>��C2�x}�!eH��X!M��Yz��
�b���{�
�:{s����"9g�wC�������bf:��#+�?T[�NFjb�����t���!i��!,m1\�g�f`��_��x���:Y�D�����G��
Z*/��j�|����C������� g��d~��f��!�@%yZqfh6�3����{��V�@~7�^����
�Y �e)����4�SWM^�C��5�l^���?��%t���0�q�op'�*v�5�_��s�=8N��!�h|8:��B�+-��&@U��U�$h4�$nd	���$�d&a*G�4E��'�P�����m�E�@���E��\�����L9}0M7�Q@��7��,�L�r�$+S������b��G��5���D��>R�1���)�)�L�'e�2S&�v��p2��+��j�1������6��dkj�-��C�n	tD��R=��D�}1�+���v�����\���9�H�O?�������s�w�������:�%��iq���Q�@�8�o��Wo�x��0��$�v�%mO�G�B�v���0jB*���|�9J����c�a,K��t6�-�X��,�����'�\�`�9&�V�I�5�&�xEy]�$��&���l#m�;M/�,#_��1K���j�V>� �J]�)����3�y�VZ�N=�A�U�U6��7R�3���c:���s���
gD�\�%xA~���9�W1�)��Qu����]E��;�pWvc���H��k�� �k�{���r�D`�R]�����0�,m�MDP4&��T!4[��),��_��_d:�GGTG�Z��6�KV%����{�#@�����^�3�'IKH�����vI������hHh�������)���w_��HW�B������(]G�'����C������SzSg	EW���C��1t�������MS�g,g�C�X�+�gG�I%��;���0u<"+�������0�����z������6���`���0/�Cj���t������
��8�u�z�g_��S$�>C<j��q�CR�t���H���E�-���)��
��
�:4RS�c��3uF�������M<e�B�X��J�u��l�W����y�h��t5��Y�30����=@\�.��YS����4kbr�P$���[�`0\e�
%�[�*fT�w��J���\h$�1
e]P(��2��{��K]I���I�t'���c�5�'�Ev�1(7��[�b�U�Ny ��nZ�ht��Ser��5�afx��������*��t��9K�\� ~����z��H�6��B����4�3�OZ/�3,/��\�%����[|��=]t�)�����iLsx�%�r�f{�^�D���u	�D��I�4��G�?ue>�!m�L�<��]|�%P�T�p~	�y1h����%��vr1�,{I_��P�i�KT'��M�d��E��u��*�r���V���A]���-�;���$�e�"m���I2�4��3�����6<c{85{i�$2����y�;����qz���d�����8l�7�6#��L������L�5` �\���%A��w���P��p�{H�����R�����O����|Z1%%��> O�u���9�1��2hoT�sn����\B@����OB;�L l��v���v��Y960��8m��n�f8zHS��C���`B�T��{����
��>^��m��q|��8m����
���n��m�f�G��M^������s���� ��;�������w����s>�pr��;�_^�Z6��Z�#� V�|�NW�v7&��:�D��� n���j'#_M�kg$ID>����A���2�M�kg9����;����ttXr�T_�F�0M����+��_���o�8���+M�=�"�~B
�H+����_bt�
��8�Q�d�O�|4�|�me��y6�VV�2�0l�%�P�I|�{"|����F��R'�����^Y6�:����Q�r�)Q]�
��q�I\�Rg����������C4�0�QM8h_�;2s��0��r�����M	�a�-����8�k�6��������m�d?�9l|:x}�h����������u����aP�t�X��������i���H|m*Q�Mb#u}#�n�C�c~�%��D�o��j�]#"��m��VY��P��A<����q��P%T&b6�k�����hv�ag��*�9�O��3M|'HL��i�n��/�xS��E}~��z�LI��D���m'���2���%I�����j;&"���K���T��5��x����%�(�w��D��������JXg2]$.��$��L�By�l��Z��%-����C�����`��;eEI{1omn?������=�~O�~=�������i�C���Z.8u�*[DU�1���{�����3��Lp���j�{!�]l�����S�T-�}�k�� ��c5�� ������'�8k���7(��Y��>f�c�Nx�^�,���cQ�C��]�91,3�o���h�4B��O��5U��LO/A#���K�-|�t�I�+���f�����"����j��,I�S7������������i�$�	F�Zl�l�)3��"�L���xw�R����SIq���w��$wdC�j�������	%,��t\/�:Ls��������`{@n��	�J����iv�V����#,o�E���Z�{��S#������s�|���5��9�S�����(d�bw:{�*/��[��w�������'6-��0����?���un�w<jG���g�"����)���yrLF�&yE������?=����~M}R�x��]�]��D��{���N1I�{�=����	�x����'��U	�$���YZA��ek/�Z8�+�C��C��.��q�Icm��*�C�$�>�p1'�z��	P��{�n��pi	�o���[�l�^4
LX�\Z�aW������<�8�D���0hP�8g���f���������f�����p[�6�.K��4�PLv����a��!��Ud��O��F�
5���������^�y#��Ik�����L-&�����d��K�X���������M������������v2C_���KL��J
��`N�9�+��M�������h���c �O��P�M���=��=�����i������,e�0%\�����0��v#�B4����`��`���ThMz���y��`��|�*+�B��)U������@p�Pb�S(tiZ��|�eL�������;)X:�|�����emut�rAf��\���H�R�<LF��sU�D#��+^�8E������#Y&}4y��O
�%o���t�(�g��rXj8����e	�(�I.�Uy��urA�]Z`f���{�HK���
�a��������!�������V=@�&�Q|���C��!��!���\�,!ehs��=�#Za��K�o�W��F��M?������E���Q������ �W�sI`��d�k�;��X���s���	'x�u��st|@�%^�����R'��aW�0���9�2��b�K���G���a�$����ht�Z������@��n�|=����q�43�P��b[��<X����K�m8�W��Q'dsO`>pw�nl�d�������m���)(� (���{���#�����n�����m��a��3����?�v���f-�y�_f�
���Usp��)��R�
�7���`Cs0~.�(��>�e�N���s
���A��w��K0�WU���zN�h0�;mE���]����������2ka`�\���-�����Y���&-=������L��c��
N]���.������k}�m��u�q�u}�o}���g��!�8kR�oc�v{l>���ok��_�w�Z�r����pz'�\v�sRJb*F%��NI�V�X����m��9t�1����Ksh~��$�:7�]Pc�
3��djR����d��QH���$�0��I��SR��)*��
N�_�:G}8k4w���u7�~:8z�lcr�8�n���$��J��'�v�	)`2����������SH�g�$u���P)��P��������rR��t'|%�&kY�����X�vf��T)��h�p��$qs�}
���nYPBM��e�%QsV��2sq}J��r�'��a����\���C/yu��,�5�!�mV�>�{xv$u�c������%����
G	�\s(V�!�x��QF��!L_r��n.jP���4��iY��=��y���
/�H�&�(��8��Y ���dC�����)tOV��?��,E��q�qy0����7�g���K�nZ�`O��j��A���e��2�0Q�Xy,��?aYlcU���qM\���h�.<�����jzyN}�����%�E2����_����J��l��9	I+�W.��<�
�������H��U�������S�:
=����Qm_��=`P^�����U����m�t1n�����[�

Ku�*���"�;25�4��\�����%���"9A����8�d����z�
��<�.�U�sP��~���Z���A`�_���w��d�
��]��eL"��F
�������8�,R>/��N��`!lm:�KL��
i��0z����]�6�����>�(���cs�%��
��N7�#�c($�����l��
�c���K��r�7~�&0�����hi������y+�����2�ak�w����P������S+[���?&���4���Aa~��7��R�`���@)L����rCI�L�x�X���2��&@�����P�6�m�[| Gu4��e�Gn:���!S�#��2�J����#t����]*qd�Y�q�f��i��������{����������7��S�K+��	�g��K�x����_4���T��3���R�����8���P��gn����/�|=n�i��B���Q�I���-��sp�G�M.0x������JE�P=�hp�K��.^�|���Pe�Lz��m�h����E��x0<�F5F�/%�`�>��:�RhN��C>#�><o)n1��>��~��i�WC��5�K/{WiwN�H�5�W8A�sbQ%(Z�{`������
-|~<��rg�78��S<��Hu�Z����(?������ �t�"�M��4�S���D�v���	>A
�����w�������Lu����Ph�Z��h���H��j��O�
^c�c��!�8C��;���k�5��jzw��%����`�$_'7�����������_���?��*,F�x(��v����2N
S����**�&3�
b�+I��)8�� ���V:���f��h�~��%�Y�(�����J�".���"K��>M��.�X	��`z��~C�D����b�����(
�H��dD�0z���T�9h��4EXf,�?�E�D��#���w���18��%Q[�S����{jg8�62��"�I;��-"���U��K�sy`�����eWwPc�K������3<�4
tLF	+LbdF�>���:i?��f����PY�������\����T��e[���e��{���)��ia���������.��O�T�)�/�g��@�bfb����h�����m��;J�r�z`
F�.h����"@l�2� Z���o��0��>{�������c��q���n���X�g��e�^;cA��x�f�(����J���������m��Z��`cb<���@�h�z�����.��� k���p���g���[D�?����_��r�^����|*���1��]uj�3lJ�^�m:je1�Ni��y�B1��1��d*����Z0�
�!�����ph3S������H�Z���"jw3%k3#np�H������I��M&Au������u��L�m�.����3WQ�u�P�Y���i0���+�;�kI���B�q���������wn{V1u���T�oY2s��a����J�I5.E�!xc���|����h�N��
�`�& ����"���V��9�����
�P�R�-���{��n�
$���1HPKs�,[�i�?���s\0!���;q��j�6gq������3�2s<�0=�;.�,\�|O�S����H��u�S'AA����_�����|O��������6�p�����5�l��\A�07�,�l�'�_����4����I�j��Q�}�O|����/4'�h�����,�d���'�R`F�����)�����`������Q�|�\>�v}�u�?z�x�{<���E���%� �x���eLt�2-��;�~v,�v��e�j����I)i��y�.��[r���nJ�8����b��y3V���.e�.����#������E�+%��(y&)K�<j
�m������D�K���(|A_����O �J�z�-��������j����n3~��J��� �
mt,	�� zo`����a
N�w������G�&;�#�X�^�{�2������3�~�����qr>�vs������.%u��<`0�TD�D���C�pd����^2 Ni.g���2��$�6�K�\��q�S��/�]s��m������uD���	����?O��	D�\d&U��`/$��}cF6�#<u��zmF`�2��W���qt����rov�wk���+�l(�X���c�S�GTe���8N�)�V���5�4�dm \y�I�����`v&S�4��$4�[��	��s���f@�5�m��o{^�>�G����jrh�K9�
7�,5�L������-4;,�Jf�Q5M��G�J�|x��m|�f0�^��`*\���
"D���8k����5��� �t�^Q��X�W����am���������B�iKQ04-��NA`���l����1�0��g"���>�����|)��'���h�g�S���{�����_���WQ�k1��5e�X����"�M��_�A=��.E����[W���g�j�!���e	W[��	ns�G���8���9�|�����b7��Kd2��9����Z1b��"7[B��HE+w!�@��A�6KkSv�Evg��C�U
�=>\vo�tL�]�4��w�@���\r�����Xa�v�B�����a2ZfE*1�|��R��y�h�W�zuS$���LahP��Y*��;�?���}��lBy,:�.�;HN���w�,�Mu��(�[��B��~�������T:{�����b�MB�2b��x��A6�E�o�Ew�"[{��v6UP���f5�L�V�VOP���g^�����S����&���z32S���X�q����:"\�6z��,��uYB�hDL���|�������4>��0���L�#`	13�c�zE���i`�k�$���j�����L�S|Q���]��	��;���!S��{�?�K�H|��-B�3ob|��IGSL�����[������:�X��or���$����H���Kz���oV[LK>�Q<���0�%�U4�PN>��C���O	
�>���>�0�oGG��DQh2��i�����m##w��2B'���6�I��X���K4������V��B<���l9;��2����.t@	8����n��zlJ8�<����3h��ysh���p�O��PX�����0�r��B����R��u���:��H����
v�7/v�������������b{F�v�s�,E�v��7�	;��U}�M�S�*���?�6�.-���Z%@�#M]��c���hCs6Q
@<m�?����:�By�wG���e6L)o�d�,�-��f�w��t}��J���K����E��8��i�$����s������f�;�Y(��D���{�����	��j��-VP�4i�b�,]B�B��*��z`8m��������+��.��Z�ZD/�|UD��c���S��s���\JX$e�5�C~��j�����Q�����5���!0���&W��p��1cu�+r��7
dD�
���s����yq��$�|�U���w�t��;��m��f�^����V:4�5C��d���]���E]��aI'���v2Sx���cG���F��c�=c��kj��
�W�:����s����}g��#[����3���p1Nl��$�]��f�S[�	���X�����[�v��������E�W08c[b����HF���X��S�,�_�d��Xik-����,z�O�d��)��{��X?.��S������$2O����G�/)����U3�5g��������9�$"����\)q�|G�����U"L;�����%����"�
�>
�:bR��j��kj�d�n��w�����SV+�l��;D^��0=A��cj�����E[B���EW��[[@Pg����eI��	�H3��+��](�����WJs@��x�+-��B�Ir����mdC^�1��K�d�@������v��M����Y-���>���Q�[�����"v�rn�dA��m�Dx��J�I�f������o<)'#Ef�!�|Ic�,��q���.]_�m�S�E�r}35�z��:�V�t�ed'p��N������/�E1���h=
4;Kz�����m��;�B�:���~�Q�U�?/���F��/���X��V[��Y)�'
vc�`�_���]Y��w���9�/��N����p��:0to�I�:�L�l��PI���$��M[2G���z�%�#m��FX�Mi��q[:�@��E���(���h0;�D������h�jqK���c]�������mn-
SP�?%/q2��$�}�7u��2��C �y$�4A8��8/��&(rzq���w��	�
�vg]�����������5�x�+R�"����[�A��p#2'c�CA��h�i����T	�k� a�����������T"�<�M�w������U0kMZe�:��J�g9�\B�Y�&�PM�K<a`���tJ��:�I��DtWz��b�I�l�]��������0eLy�O��g�7�����K���*nOD�+����'o���'����h�����P��m&��
�[�i� ���31�xWI���:�k`�9��MZW(�L2CI�I��x#�#Tj�*(�9Iy� �����(�Y)]:w1�
����1�
<)�����8
8�=���M���h\	d�y=J1R����0��D5��"����Px��#��^')}Y��4�����0:;}���C����|�����a�'��f�M[�8�=�_�	�Y���ur��'�lr�0��
���`�gL)6;���+�d6�6G�K��Q��|����A�b,]�^�������}�x�!� P��P�����[c�#s�/<i��lz}{jm���'f�z6l�:�����#�s�]r�MlwQ�?�H��T@��,���w��y�!�������g�R�����8���;=�0x����/KK6�:�����������i���������0��P���5���t��}�_ExT:h�E*�!�t����W��:>&6�c�N��(b��w�=;J�3	S�b��e�6��j�~��6([�h�	(i-]x�������z����������G{
3���vV�DD��ig����:'F�)AM����:
�����p��W�\��D���<��m��9�g�P2b�S<�).�=�o>|�8�V9_`��Xrm&�n�xv�zHf�-�_*��e2f\�~�������1��n�%��&T-���%�(�_��f�B(E��%+�����KZf�mg0����thJ�����1����|OSOt[��v"O��g�f4���vwO#O�w�"lT ���a��}�;�p��J��[��W�����������W��+�:_�w��n[�<�����������*V�@Z���$��8nR���c�3NEk���Gf'��+�g�����N����^����2�"$.�;jD�Ew43#��\y�u���#|smb��-H��w�3���X����/�7�<_[�c�x����M�>�D������^�&p*U��PS��}&�Z�.tf����/�R����o����P���:qx�����[����6n��d�����J����O<�uF�bt��3�M��V����F��k�)�w���:�`@.�l���/�f�S�WUd:5���9u�%I���:"����:4b��~��n��Ruh�7��X}Q;�#�&�B�&�����=���E����f��fH��j�M4~Nj�u���VF�� ��mZ�NR���#>�0�.�DH�0��m���O2�T���Z�	���n�� LvB�]���q6���t����o�r/���$g��Y.hF�d�X�,�2��(??�F'l��[��`�f�S�e���B�s����5
�v���yf���x�����oA�����11mlm$b�:�&2gY��.a���/���������k�t�x�Q�r��Y��K|�l��� �K-d+M��z�`�e��n�M
k|�h��;=�Xq����Z�=0:�hq�fZC�eB���_���B�\	u<����3��$�Y��Bq�B����;�U�N�z�����S��w�K2WEs�	��\�����7��m�U���@��*�	�����2�#D����4���p�5 O�m��Fi�\���������(�,��k���p	���^�<�g�
��O��
$�1B�ymM�Ov�W��y�rus��e��Y���:$�3��)���:�IJ%����<�c7����,����w�$�RXz��K�QE~@5aWS=�d�O�r0��0�������'`�f����fM��'pq��w������dv���Y9!a���F�z�d����"�<>��3��[����O)�R#�{mM��G��i�[W,��}v�����������|����/�^?�eZd�TQ��D���o]�P1�/����;:Pb��%�~������+(��������P�7?b�S����m���j��(��A�jz������lT2��n�M_ugJW����/�GSw�Y
����
m����a�$�S����sr7���!w6<�����}|�i��S<������.3���U<J���k�����?!����rg��6������i���y����L��j ���FM���9�k�%���������*�b[���
	3�f{#��N�"Z�����q�Jepro��	���DU���Og����q���dx��1fx������� ,�(;(���4�1
P�`������@dn��x���������s��6��
6�`�a
�	8��)��������A�&�!�1Rf�������;�����y#�GS����u�������k�0�}������S��m&	����'�.�����tq^��&L6z�_�N��8[X����|T���\d����9����>��J��
F'���`�S:����x�{x
&��I�B������8��f��
�~]�!���L�	�����ht�c�?��%����0�<�p�LZc����3��n�	R���	�8{��
-�~��1'zM���2��3�x3*
��'���Q���s�|�%�����A4v�D�V'o?�t�u�	�7)�����~T#������?U,Ua���{1
j�..�D����"������q�{-������$�U�o�k�t���k��y�O��c~������z�Z���T�;�7�ok��D����]����n+5��G��~�=����z�T7Wm��n����K?a����x�v�G�IS��p�N� �a���,lI�����8>�w`g2��E�CGe
<a��6^���[���y�%Q'��7�'���������\U�EZ���s���tJt"U}u���8O��j����z���d��Ez������,�RY�d\�.
_*�.z�����>��S��O�Y���U��Sbi��K��2AN���<���"�(�u_+!��vZ����.%���%a�w�6x��I
������A��`��gQ���t3���=��K�_����>D�"��
J����~@�n�$������>�l����$�^�"@@�e�� V������]V��v�(���I%1�TJo���X�������2�y�&K�^���F=�����v�����n_�^���[Z��Bo���T�^E5���������9	c���h��vR�y�i��I�~�]�O�J�,�� ������F�0B�(��^��������v��'�h|�����tV�Y�������NI!�Vj�����Eum������fv	�a��KJ���M�T�w�0���X����%01����t�)�JM�g0X^��];���������I���|�����Z��Hkv���Y��d'�7�cZ-Iod���5���2-��HO[
����)�!�rv@e��
3}�K<���)
N$���{��������������������>�y�����h����gg���g}������Q3�����R<D%��6�3h:E�g���g,������>+��Ky�w�~����;��)8������i]5Z�{^u��bz*�qn���P�E�
5m;%�Y6IDv��G�TQ�<&�������f�uO^<�J��q��`jEK�jG|[��\�����D����g~k�e�6;�6-����"NS(��,�b���)i��m�T�dr����$�����qt��.�Rl#��8j����dyv����fR�)�����������J����$>���6�����=^W�|�qj.�������
)�+�`nC2���j��R��"n�)�M@Da(�����cQ��������J�(y_�����xm�������@��d"����v8�O������K��~t�$���$
q��i�a@B|�-��)�� �Nr�����B��L�3N�r}����*���!���aWl��Y��0'��T�`���6F%����R_��9�K���p�l�&�K�{����������������d��#�b������6�0V���#�<�x�c�{#�9C@�WMT'���D6b*���������~k�R�����rc�1�	S����47����r<���h���J���v���Yd7��n���	p}����f��%��1���!�����X��L8	f�S���Bp�
1!�p�Wm6A%�1�P.�;($��e�	��*W/�}�����c����sG���B�[=�]�;�����Q�s�<�"#FP�������a"B^����n!3MJsmU{#8w�#v�Lr��r��� ���!����~.��D��/i{���J���:�,M���U���/�*�BJU��/����~�S��8��%��k|��sn��_�c����|�E%Y��ZK�W��y��sw�w��Q:�a�
w��N;�����-e�W���C��+o.�
���7Z
�>7��N������]�f':B���r��Z���D.��\s���kn�b{���[G�����F�	��J��@��R�j�s]Zs!�R����\_K����.���������D�)��r�n���}N�����|N1�
��R�	6�FkgW���`l���j��mF�`�h�P��(�"(�	d0�i����dH�lD��?8����e�M��w�eP@��y+R���FV����O��Lv�Wh?U��������&���m,�Tb�R+�j!�4����zC���W�&�����J��w�^3����uu� m�
������2�Z�����������hg��;��B�qR9�KM$'��5Z-'��r��;Q�Q� j���:
N��p����h@�v/X��Y}�q�������V3e}����7?��P����j��Z4'�[XK&�(J�f|�I���?�t�PE��x�{t�g- 3�xp�m��3������x�s��q�q��H�b�
b�x+mn:�����l��8��r@�b�`G�!}�4u�����&�W`��P/���B�+�*�K;������{�{~�l|���m��yrL'O��rx-���Qu��k�A�{�.}�,"p�\4�.�p�$�J�E5���;��7����i�
��+vv���|!�I��~�T���?=�O����a�/j�hl��6j����%]A-�P��'�����~����J�a�K�M|/G����t�U�P����$&-48�����]�������z���$��{��C�\��|;w&�)y,�+r�����o�W�TZ4%�$����$�6���"�#]�r�d�
��E�8�	`U�2L>c#���mj"�3�����nhf��5��)��@��P��b-@!�!����;���v)m������t���tQ��y�?�"�/	����'���'Yk	!�xu�����i�^��g�{�%�Z/��������[����Z�G��G���:|�_ -�+��2#W
�V��a�F������Uu]/������i��~����l3q*��,f�4n��MN�%?!,���������I��������H���|����L�&���%�;��;���g�������kSf<��1(��i#V��.P0�eF��-�a}��'�31��?q�S��Ms�b���S���r�C����}4�����m�f������
�P�0�9i���4�p�-����,���&���\�D��.���IG��uG� ��8���0F��u���fv������;>=w+�+sA�E����|���x��@���C���j�t=�Zp����W����4f�}�v�p�iC+����Z����CxG��<���~�`�q�`���J��Q2�B$���yWn<*��7����$]/��T$}l+�������9�?=>��w_6N�o�{>��Z���D|��Q�s�a_����R�����O%d�*���&*}s
�H�7�w�7|a@�
���7 �2��H��
�:!�sc��k�������AU8���
���v�\nvDo��F�)�F\TP[w��}F����1�2�k����X�H�V����qsws�&w�sf(��DN��S�C��07h����4m����	TD�5���[�u������e|����@DVxG��������Dp-~�*O^���F-
�u;�{��F����������@��z�!��~t[�g�BD��lxx�d{�m���p�G�����/;a��U���%u�~����������k�8a�{�H�����P�<�?��iz�����;��x��\�H�GIY�<��;�}�B�t��������-2�&]�X�$�[ OT��^�n�GNuEkd����������,�^�4k� ����x��f(�?:�m�����-�.t<�%�N�u���0�W�)"�5�h�%��K2�z�_-���V�j�D���^�zF�1�d�r/`��T�c[������Z�3�T^��K���l�"�������9���q��Q�	��2G����x�Kn���9�^[�*4rp��3��D	V�y0����C��������Ei}A50��u;��_�]+��1\�J'/��������(���H�������b�Q��=�c
v�G7��l���j��"�&fOt_���0��D*+��I��X���]��-��7�V�M\j����6��e�k3�<�3�P{:��
)q��Yh	��^<���
??%���,�W$�%G���6������NQo�j�3��l�����y�����?�]Kx�]U�1��u��d�J�w�ZJ3��nH���n����wU �B�� M�>�q�1��l3��f�
�
�	�)���iDI��M��v}t����s�b��z�1���L���8v' ��EG}8�(�ly��?d�T/W�V]$���a���n�;=&��l�h�Ox��[;*�c
�q�z���a�������g���}@q��������E���<UW��cz���
)+�-�Q+!��k���LFtV��T��<'W_���!e/
YpW���	=}ni�5U���O-�����H����i����rQz����������n����	���������)����\��}�;JL��<;�=�p]a#l!���-h��`[J��Rt�u��L����S�f!�t�T�bDN�U���?MgZ���fH���Q�_�3����M����
]���b)?�4�:E�bA����R������h���t�'�yb��x�E��Yjv���,�H����`F#8�`���,����Gmr��5F�|��� �zR|2�� ���s8����6?�.f��$��P���h�DYT�Q:�~0f��hWNm��Q��A��~�N�&���.:t��&��%r4�$�"��1m���}��H���R�{m�����;�%���L���7	�^��M�@�b2loL�md��Y09K.q�������sI�������)18�����r�C�����G|�lb��s�p>��&z�I^��/����M�
�k""���4���V��:��:�h�%��p4���#��rr�)��3�x�4�������-4uq�K���<2e�e�]�/T��*�x�.^<�&�Q�[�5�'�eI{��Z�'���^��L$M���cC�
oU2A�������Q�M #����H p�\�
%����o����.��^0-W����L��[�j����`����5����|�5)����H����jtej���[��)IVyU���E�I��Sr�$T
��k~3}>��.�Jo���5� ������\�$m�����9�U��]�~���F�f�o��
O��Q�����`'�|1#�I��R�-D��P;d>����3���6�$��h���:��;��	�!�`�1�����E����I�C���kB�����Q����OG��*�4�E�t6�?g9@�� �y��r�c�b�������
�VP�r	��/"jkq�%_vE\���'�-a�Sa��zU����%Ij�m*Aj���Q�uM7�ZX�%F���Z��&���e�����������[��"��1}����d�67�>~���o�M�_����Q����e`1�F;���������m���G����hm�����V������*f�N����h����	X���r��M�d]���r�$�}�=4�A@��iH������=`���gV��p�����	����]u[��|i8E$$���h�
��h���H��i'R��;����)���������QRbPA�+�'xR�������^���`�4[6U(�,�������)���	+�7)���"��:��5�Xby^b(��0�)����z�p�(�E����}w����"<P]�E)�����8���������7��w���6*�m�0�?�I���H�qd&���(:d(��lR�(r([(�im���F�����|U��;'n�0���6��C��2��N�
2��0�d<�+
������	���,�G���P��M��Jk�����-��������P��`K�TgB$b����lxg^����aC�B����Z���c'#P���R	+q{�drt���A��>��.o���9��Y���&���t�ndX#Q�.��$b�����W�v���%�V�S���
�k7�#�q��ZE�1��ai����S"�g�, [;jRJ!J\��h0_e�y�y�w��JQ(�n����;��<&��Ll�N���p	
+r������&��w��$��3��Xs
G�B��b�kM�z���Vu9"u�*31�� ��6��S��;Y�@h�vM�T�%1M����vi��;C��$3
��n�i�\:����X��-����,���3 _���&Z�aWRY/h=�_��'������7��*Y�vTa5y���,I���%�����?�M+����rI]Y�QZDx�X�z�(������I��nt�l�(�||����C�*a�����F�E�TS��������aK���XQ�a0���_i�;uk6iU��rz�������au.�>F�a��q���Ro��8)Q�*�B��Z��x�2^��!p+5}1�2&&t'R�L��d;T��CN]�'	�����4��J3�N��2�$�N���c����T��XJ�A5��P\��1-�e"�8����Qs���b>���((=�3���`X���[��"^�,�L^Q:JT��FbCBa�y������&�cvO�W$d�!l����@�:�OT$�g���!�j�6�f�M��/ev�|�;��94'�S�+R���l�V�7�r�S�G�7p8�w&}'�����-�*�j������yT��c��g�q��.����s0$�{�04��7�����b����eM@��������^iI�2�qd����� %n��B��U�1�Xe���>�mz�V�K�V������ix��q�y+LE
����/o�j�����>)/g�%���n�A*�6�*�%�A�	^F
��C�!cW�|i`��G�*#����]������Nd"�wx�`�AvxK���bry������^�7�v�!}Xk��x4�oK�����)���`��@="�J^�������`�����t��3���kX�Q��q4P/f���w����HFun�w��{Y�q�
%?������!�W�K�&��:_�t*�I���\���C��q�H���o55<+5P����P��7'�T�D���
�CDC������T4P��� q�1
7�m�*��L��%��b-���;�&a[�����e���Y0��Y��6E�h"W�Vr��$���$��)���
LrO����n���,���?�����%m^�`�� ��p��I2��z���*�%Z+�Wh.��W���9�3�;�q�����	�Q/m	��xJHzql;xKGzT5�=��>���~�N����A^���Ec@@������hb�N�N3��T�����c.��b�f[CR���6�I�&��>zwm|;����y��td�rFch����vE�����:��gC��������|�.sb�)0>��E��k��P=H����������o_9���H�F�IA��f�s�V"�eF>^���A�X|�,������%J����|�dM�a�_G���O��z�D�x��E���P�V��e����CJ��GV��d=s�����%��'�]��`��Pc5�[AS$E��	�J�f�de�k��V	�)������$��F�W#S��4�@$O[��t\��d���-MR��,�C�k��=��������9d^�7�,s���Vm�����P�tC��'���j���t2?��ZOL_2���a��En�:��8b�'����!���V�FP������tX3�����4�����I4��T���"z|.���`\���(8�z�I.���D���g9j��
o	�v(n����:��	$���������V���IG��Gg��Nn����b8�2���&�L�vJ��p2��������,E�dK��\�0��R#��k��=#�b��w_�3�^�����[_'�{����)~H������&������/�`�uo�����u��yeH�0!�(�Dx*���X0:�m��y���?/�!�Z���1�������o��;Z%��Svk�����{�9�����@�)�pS�	�5���)lO?
�To�Vw������e�i�Y��c�
}��G���^e�j��)���i����?�w�:��(��cv:u$V��tOfV�b�S��.b����C���� [�	TJAo@_Y�' ��T ���]z���*�|��D����M���
/����&#e&N��k����k.[�H���;�(��&g�8]%sw��R��`Z��5dDf>X*���4��d,�1t��`�������'I{�B�-��a��~���������#���Z����|f����4n�0r?�\�\�^6SU�K����e�(��?��9�E�2�����B	��
)������������2J:��m�[2Bkx�IX+e�v94�9�u:-��2�)y�b5��~�JJ<�H�����O�D�s�s�)���j8k��s������O�T�J��ml��Q
��t��xa���&/c�27��p�D�h�#~
7���7��J��I��q��r�l��@BUqB�u�k�B����H(0�Au]�S\�~4����C����D�0G�%�{��p0@f��X��l�lS��V������p�$�����\��|i]s��):�����������m��
����A�����\�{�Gx�:���w$���F�e�I������>�q��>��9� ����*�|A+a��5�7�c�6�)2 B�,�PqLE���B����8������,��ZW?��9WS���.�h"R>�*/��)�����D'��=�>��r��319ys}:c���G���%��8�<�G���l�m�'{��~|������s:�%Cb�/����G����i����M�IH���^�m�x���K�a�K7��< ����u�Q�!;Y��Zmx����v:�����)3�>>u
l��`W�bd���������F"��.��f��qk��d��cz(nfTx_��`kK��?4�y�AM���e���~��xlo�\����\����z��+���3<��_�_������x��O,�������fw�A]/�r�xk��n�.��6��������W����{��()��);��CL_5";CvP��$=�����H-�e��9��.�#�K����"�g�3�3�r��7m&%�)YI�,��ZG����V<��-���Y�O^@�nD�h�����������Ns1'���#:hf�So<�����N�N]���o(�
�6�m��%bD~G�E�:0Q
����}������~5=����-�+�k��Db���]��$��p��$_r��
n;����g\�0.
�N<Y��Ep'�vjb���]
�����O��Yg8��<G2����:���]����:��8��#[�3�\����-�~>��e���\����[4B�S]: ���Z.����
_3k����4� .����Qn�����'�n�!b�@�����R�o���P������U��W���wo�c@
��j��������������'�U���\U�>RH��p.������6����C<t�GUj��B�����s+�K�}����Oh�X��n@��\�q��A
��h.���{,� ;"T�*q�y��3��7�G��0>���\	
����R����W�q9��-���������
�"�
�|<%�S%[ K�5,+�2^/0&l�����b�����A�F,��|-���#��[��r��w����,���Ei�|�O��.��91+�C�{��#��(��*|H
��p������xt�8���_D31'�/(���j�_:�/ML�S$�7��D_�w������+��a5r)�GFte�F��u��}�`n�W%���yt~�{t������
�VjC���2e�Q�/�%�L!���\"8t��7���B�r�����[�.�'�&�05��c�;Ma�	l#s�EIdcr%dwQ���	5���|�A�h�	(������-,�F	z��"A6����"x��!���R}Q.2x���aA�(>DxV���h��r	��3oLr!kaN3��j&�*�����$0��1lUr�_���{�\�����*m()��F#=���uk~A>r=J����e��s�o�][y{r��C=���<����h*����dN�G��8�,���:�s����2��D���0����
�=�3�q�\��O��9�^��dw�mx�86�s�@��`q�O������Xl�}Md�CR?^F��7O'�]��?w�w���;�yp1Yq"��8l�0D�=J���=>�� �$�-��]��pfa�	��-��S�q�=���a�	G[I�=�H6��!}e5	����uu�H7�0V��K��nH�^���Q�qJ�Q����<`J��7:��P�B�l~V�o����.�h�]I�J�+�2E�gn��*G$ngeN�P�H��;E�p|���'������k
��%T������%-�>�Z���������8�F@�7$9������	��	0o,]3�A{�1�rh!�Co�d��w9������1�k�H>k	��h:��+#�������T	]���5A
ZDLg���x���6r>��vD�'�:f��xn��!��GUs�Po��,����'�����s)$U x����y��vt�A��G��7/���Yj^[CdV�N��?|V��OP�t(m��x&��7��,�Unl"c9G�����
�����#r�Z
�h�ED!++��IDD��wnY��T�����
m�zNT�/�q%r3������"���
/7���C��p�	��.xB���@��Z}���mY`�F�[�v1�,���J�������RN/�|f��g�Xo�l]��ua�6d�l����fc�I�I������FR�%[� w�hv�|[����3��EAV]�S��4�"�8��sPP�:��y(]R�����*}K�B�0�Z���^��`�3���h%�K����*���������z7b���y�[z���-���
�\w8�O&+�M����n�o
_�	\����N����`I�7���6���-��b����6���Xzad	����^iN'�� �X�������q&H�e��2
*�{N��
�0��9������L���8��������;�_�{k��Y
����Gt��*yg����E�\uF����b��_�
����]�Tl2��g\�����O�;��s�
uZ���1':��t�3�<5���������Y�?W|2��\�e\����#��������c� ����m������:CGy�mG{48rd�jh�����[��	������.+���F10�t���Xf�i{Nfi�����7���.H������J���l������=c��p���	���R���DG�����x��V�87EK(�D�_���0m����>��#����0���q�1�gc'����Qn���&TF���P�I/Q"A������NR6�~��0��EFe��4*��I75�R�g�L&���u:%�5����o�$-���@�[vc�%���ri�0@O]�
U0���������J!�&d/�Tu���(@���6�1�Yl�2��@�)��9����a'"9Y���l�A 8
�d��0fc�%�&AG�\���
�7�1f^�1b�9����=���B���n��;�C�����4���#g��x�E������g<��6/@��� ������{8V�a2���$��%�V���U�G�ub�(U�H��G/��t����w�c�vJ
�gS.���]t��q�]�������v�)`wz3 �0��}*��7Z�tMyF����$���mK����SL"l}�y&r��C�������4n�����nF��"�7VF��C��M���0j����,�����,��2BV7�tD�^;�Y\D��w<��M�\K;��5=��������<�l�h���lE d�����&q��2����O�,OsL�Et�-�Z�{u�����2[pr7�o��t'��5sJ�-aL��0���D���;+�\�-|�fL���(�r���)W��b��1�0���2�}�n�[Td�T`T�_'���
rx���h�<wj��G�k^�J$&�8��zM�As�	H�~7�4~I�BH��[��
%!��Q�����Dr�!��y��$�\N����I=���c���a���+N�%�-��Bqv9r 
�2������y��+�U��N�F<��s@J��j�]b�����DUx�?�/����O�?��_��E�I�?��F �����v�Z����Q��cV

O�F�*?���2]��\����~*w����'�)*����B[�?5��p�Y��Q�Q~��A;g8a�����]:�I'�#��%f=A+�e�W�v(y����Df�B2w��������K_��pA��q���-���i���Fv0dn���������_�X'Q�'��R=�*&h�G������+��f�
��IA�`&��+�7�]���)*XE�pO��%��^��WL��Rc�A%���w��~�n�r]|#����q>�4��[�s��1��vZ���'�h�>���yN=�w�	���zOa"s��0�`��A�����>+��&�/�B�@�t��%9���\c��7�w����������FpJ_F@8=�
oz��L1������[\i)����+��@�v��3��	0������G_�R$N�K�	T��k.^
L����P�"c2�D�f?����mJ�.��)���W�^�J�5�zG�pP[��D��q����aM����@�!�FnG?O�����`D���t�!�����*5���)B��v�h3K����|�|���6A���R�����$����TU�v�N�����(�s�����s/�^Nqu�]��w�������<��M��qo}=X�1u�6�7��D�O
���V�cE�`_8�d#�����1��i&����.s�?:��z���l�����r�7���&�B�r�:�	�b������~����w���U��k$%��n�"d�Q��#*_pU_x�h��@?ev��7��O���5Rd9�I���_�����t��q����R,�K��|��g�is���'�Z�3���������.��G<k,�~�M������"PQ]i��k��B�Ib�n�w�l����tOk��R,��6��0����������]���,?j#&o�d�l�K��'+s��n����_�&�� 8.�~�I������Q;���k�b��!�QR���\�&=���4F�Io)�6E8,����;����/�]�K��`��DO���;��f��G1S��/�d���}L�#�	_�4����q��%���2�m�3���s���mg��9Z�.�fhw+�/p�C�%�<^/2Mv?��=����{�0_-���/���O����x/��%�<�w����B!��������tM�������l�h<��
�K?�'�b�l�&d���l��A(L�������Rj�
D�J��zqYI%~��/�������v>���k@�Y���0�4��$M�/B�~66��an��&�}��S�1d���-6���K&��8�O~�M�aW(�V�B����f�e����U}�]��jQ"���������7������Yq��c^�D1"�v��
�c�;zZK���Q�����������B��/67f��}���-�� j����]0�s|u�>��V��
��u6d��9pv�?���
m��Qs�d,�7��)U����0��yA���>��6��t�r'��	��E�"e�	'��EZ���T�l�����TF��q��To����\_I�q�_������'����f�^���������~�NH��)�&]�3��
�_�ZM�75��j��=_�/��A�0��kv�V
<M	[�i�Dba�T�1@0�F��P>w��Nz��/�����x�ZG�a2B�����I���������yh�.-��4�9Q 0�=jv�(	b2l��G���vB[�Lrs�,lQu�%}��m��po/���iY�ED4��&(9�l3���n�c791���e�q����;/	6��\�����7)�(%�'$c��_�_f��K�����wP��>R"�A��Ifk�N7.�R51(��b�9�N��w�
��-w�i��������DX,J��^�
��1���v|�4��Lo�B��+�Z���Y>�g���D���bz5�����z(�^�������G��f�d$�k��K��t�q���B�8O�5������m�	z�f0:��Fw���^c2�h�0�U�u��02�w���;�la�S,L�\���De��Z	�:^P�W��A�����2���6
$�N=�d�wRv��;���d�c3�p��X����q��Y%�67T��j�v��y)����1�G�����R� �'J�ZU����|#����df����r�o��gE� c/p�L��g��s$�0-�����=
N#9�����i�����f������Y��|'k�'��3�w�M���`pT������Qd�1��j�U�6�����Zy�X��}�9C![�gT�[D�}���<`�'Y��n:�tb��Bs�����R�C��V������>�]�0���*Qf�x�������=�D�"�a/�N=�RD���R��,}�������NG�������n
s<$s/��X7g;�ea�
Uv'bq��dC2������A:1mn�6�P��.3�?���n�h�HHQ� Pf<%�;��1���*,�Qy�L�%d{5��$��U����}u��!?%<�`��dP�W��?�ND�R����d��y3��k��r]��kBJ�f�/���F����'9���u�CEv�I�����LUF���]��A7����B�r9}���X��R�mU[m �����,�	�"%�o��y�*�A�JE,s�|sB�`xij���"JnKB�����L
a���^���r���g�1���.���(:z���U�'��d�4�

�GBvCS���
�_��I�{�� �����&����R�A���U�B0e<��0�wgrIG�2�FlQ�d����C�~<�i�N=��%��>A���}�_��b�r%�V��F+�K*���J/Xx�
�(��o�&��]9\Pn��_��uY�7��Y�H��+$z�M�As7O�<&E9��Z��RP��{_(��<�M�����6���n-��t8�8�z��k�T�c����D_2OF��o��g�����C}��c"�|+	������;L�p���YmhZx�#�����R�c?�C	�����X�r`�q�v&]a��
�H�>Q�YP��M��>�Z=$����;��/b�)Z�M�.��FO��jk��l��ke�8�0�w��e_�T��Z\���9�	�h�-O�,����h�J>Xv��%���^��I�`D�e�3$��"�e'u��@����Kc;��[�������z��\�����l���Zii+ �7�prqru`t�8^�F�2��&&������Ezv�G�������U��)����|����O&,��x`�
S&��_����h� u��H����6I�
���L�p�#Hq���!?��B"�2^!�D5(�5��EP�D�P]��E��|��b�����)�1
�&b'�S_b�L����T�-	��F�C_�a���z������$�A��D�$K�5o��F�}m='�7�����U
����j�@6e����4�7s�]�|�������[��W���"/Jz8-���E����G��1o+fU=��6��^�}��e������=
��=[$T���5���/S���7���mY_7��2��[���>('�m�4!W���9��_P��/����������|�(�B��]�Z �������\���c�|8��:�h���cP�2�>��6z��K��X�|:������9I8>��#�0U��H=�Q
��/��b�s�� Uw���s5g�!:),x�o�5Q��HI9��5��$gh�|wR�{�OF?�g����>|����|��������Qc�`��X�����o��S��a#&�20��2m���xG6��rL{��R�R��U�AG��S�a�^����c�$)��UG{�{������ >i�&�����|�~
L���_HJ.�~���^�y�dkw������va�p{xdy�U��<5���YT�z�����3��������f�c��/��D�F����!�8����y��tl2�f��CW*e��t����!9�}��\�,��y�E���Z�_���,*a��!T���tl�:0�H����0�V�����&�/�h� ��������\D�69?j����N�X���`����O'oOv���b�^p�����"��y1��v����s:[��8��
. ���a!���AD(���Y.L�^�M�,�bg��?������+�~D�B��N�;4���]a������vL�,kw��!��rY�"Gn�c��]�+r���F����L��l0�Q"�I�8��������~d{(Z$-�"S����7n��q8�G�0<��"�d#�i���fR�3�A���[�h49���0��N7�_6A��0`x�?���T��"B�
���Y��R��q��;B[��C������[����ez�ee��P:g��s�8�0�}D?���U�z�d�mW���RY�Qq��?c��@D_��/��0^I}V��#��}'��B|L!��m@�������D�x^�q�W�o�
��K��+7_8����@���HT{�JX�1���X���.�7U{���r�Ti���$��D�+@l�2�F�*�����|���F��v0U���S%(����i^C,���f��)������o���X����]�z1�w�n.����2����rH]I��(�.�\��~N��\c����2c�l#�j�N�%��&�4x�4�q�����CGC8UV"��nR�?�C���J
����rRp������z6����d!��	��	����;L3} �o�������7����d|��j�L>�f)��{�������K��BV��������[a�9'4:��o��\��o��W�R�IY��[��U���2&s!?���]y�~����>��B��[D�;_"]�<F4���Y_.�h1=����v[���j5����M���������u�]3�[�j�k��7�P�a�eE�K����#�m,.`�.@	��H�w}��]{y�:�A8�����E��,��(B������c��H��X���O�+qf�����w|��(��~�o��a�j���7�N����W���!����������������������5N?���k��#�E����6H��s�I	� �W����<#��*4ph�&�����*��uY��KG\�N����h�@��&v����+U�G-�b��������?VxX:���C�g��m�����Y=�U����
��<J�Z0��	e�0�&]�$���1���.`g*U��"M�D�l�-�#�E�lw�P�
Ap��&H�W(�����C���
����MS�=/-�5R��=XO���'�9��7-xi/�=�x/��eA$E��������F�$��M�a<5%�����TH�������������7��H��I���L���2��2g����z����%�\�C����V�>w��T`Kh�/�9����kNJ���>�	W%Pey��O�*=5S�Z^�����Bq�f�g�q<:�S������	f��@TN��A�h��Uei~[<�K�k�c�����H��,���>�SZ-��-s�Ik�,Z"�F�-�����q�;��$~T����s�;���x>�q�D������A�@�9w���W`
�^��S�(b����r��}4&�R	��Lm����k�1���zp��s�b����Di������f��Ipz������I{�=�'�<"3c�gh������uV������ip��L;��N������5n�"���
�W�ec>��Z^|K~��ui�(t!}h��!����e��Ny�T����?�fJ��YY�AC�^!Q��/MA��/Kb�{-LV��^�������Z���GHF1��A�}�N=�5<?kn2&s+���{XnZ�jy���#;"J�9�t0����g*i�������6zq�rQ�S�JOX�������+b���v���*PXC��oP��/���d���MX��L���>�A�Q0����$q�2�'�P7��	�A�/q�EmqJ0����
+�^;�����#�.��I2�C;j�(oSX[M�7[��d�x����J�g�V^���.T]���G��qd!07;~1��+c����Z��x�Jl[�o�lq���j����h�2�;��'�=0����l�h�z"b��<���["Q����{�8l@����%�fNH9
�4N���3Nk�-�����pJ(~`~��;e��75V�	{�NP���������j�V�j�G�T��L�N�'�����h�����s
��T=7�y��38�^8yY�"��WvZ�g�����#m����,A�/��?��@����]L����6u�����=
��8���/��r#���pa[����n^��^�6����r�o'7����G��o��������<��67�>~����>_k�kkks�����?}R{����z��:�� e	BL��i����r�A8�����kr�,�t��~���~���W�R���Z�at��n1��B���W������g�����Sh���Q�i��}1W������w�����>�8O�+o��<�;�����������i���`o�������::8o���7�%E���e��6�"����G��;����w^��n:�ON���6��h��o��6���Oj[��'m��>��F?bN�WK�%��y���M�5�.'���(m��h��)�1�����JQ������2~"�:~3���/p�����1;&,�:��-xj�`��n��y����_jCf����;��JP@���M���r�'����F�m<��~���$��V����w������A��fksw���s�^R����Z�9�gD���Ia�H���t��u�������V�+�w�il=J�z����x��<:��	�X��'�G��o�����V6���������>���j�S[�^���r�H)���W(��+�U��_�e�������L ���>��V�MT#��W���������<;����u.����'���yf�q�sg�������*�<?+��:zX}h��}bRl�?9�;�)rtp(er�Pu�N�7��K�PF�����mU���Q�b��vY���
��`
�:�=���L9nU�{��t���=�~���giC��2�f����1�|hfN<�i���1#�
eG{h����&���fP�l��)X��� 'zD���|����d���Dv�s_����"n.���t��:)�����(���ZY@�����D`SP��G��6;[����fg��_��U�!z��t(����f���� c�\�j����8�f�������
t�
l#��[ �]������
���.�!s�v2��
{�N.��0��~|{���M*R����2�chW�o+i����������]�T�G�I��0�����(t2����Q����Nz�r��Vooq8��o��_A���������:m����`�����L����K&�Ex��6�j�C�
�����v���ym�)��W:p�RQ V��<r��R�h��������56�K�s�(���>���c'�W$&-��qn�����^����y�������m:��>���0��@����V~��H��a�A?o���A]%7�����g��
6��<�?�e3= 0�������lon�����g�_�^��Ck���_�������J��n+��j9���U�����R�����/j[��T�����$~�=���z�l��O�HA�tK^��S&U�_���
Je�b�eU��in
ee���%4A��>X��,���?��=���k+����<���W������a�����/��6k��?omo��&�p���{�x{pT���*��QlY�9�`�����f�|��K��bAe�|���B���P��z6&��t|1��'C���X2��S����F��&����8���?>,v^���(������M)��i���?��8~��.��-�4��b��}�1�iAo��VXR�]��W+D�L[���i�pp|�W�3{�j�E�:�_:���mg�+�u~5RP������=B9��H�26�X
{d}��u$20(g������w�va:��c&oKn���5�#K��q�����G�b����z���%9^�;W+I���9E������x�Qg��E��<{�y�tN{�_�t��/K�i��3���_x��qA
h}9�����I�6�UA;����C�\��q���:9�l�h��]�o_�$(uV��1d1BwWpq]��i������C�#�ex+�{Y3
g.���%M�������z�Qk���s/��g����$>?A�����W�g��
U�H�/��������KLB�T�����
B����T��������[�f��3W���a�OI']���Z��P/U��I��
������_�������
�9��-D�������.�#��fn1��P�ety��SJ��aj)�6��-�uH��d���!Re3K�&^�{���B��~r�p���R��)E����~�����yo��%�cg��^<���9/Q�{��/3
��@'�K����p������0%�ATg��&@8��B9�v�*�J�G�������pR�3�Mrn�M,������m?�)���x����Tg4{$�����K�|N+���'�	x%���x3����:*���f���@lU
E�_��
mjk����T`�oHH'A��,G"������
J��9?��$p�������N�86C����x���MJ������rR&��(�B��&�������o<sy��WVet��}`�US���8::�����A���%*%<y�80���n���$2�ZOzQ<B!���)�,�-���a��Z���6C"I	�M62�y���H3g,��N�+~���-;���`��p���������)�
VRri2�2%XM�]n��J�������7y���/�U/P���s|C�N���n�v�'�����i�+ ;��`��(�H��el�����
P�H���v� nK�7��^%��kF�D���:#kf����_�sN��5��b���o���c�����ue�9�@�o%T0�+��`0�8�!�g)x��OS�
��5Y�q�ys��:�Pf�������:O�Pr{�����y�ks8������=�E+l�;;?m��?8z�R?]��pJj�Us��*)��>f��$�!�1B��O��	�DJ��~=L���^(�?jd_Ej�F�?���G�����@"��*:���t�s���D@�;�p�!q:~3�����hM�hcD�����	��;h}���N�\5���6_��t�����= �3N���-#~������s��af��;��������<�|-�W�����g�73e��B�y������)�����a��Y��83���b�Q�3�<[����4���jt"����	7�(�=�&�SJ���v3�b�|��h#M��%WQ�f�;�E�L"o��#���o6�J����Z����NL9v�=���~mB���r]�U|��{�os��xh�j���d1��-��R��B�*��������fQ� ���n�"���������
��9K�2;{#T9=E�����	Z�����5����7����`���1����(������y�>������[��(��p7�d����})�V�-���?A���M�<�D%�osK�-�W&c8��r�@��<�a�
/�1&TAw�hjK��,W�(������]x���!��58�bv���q���g8����w���$4S�HZH���v�1��B��a�+;;�o^��|W�s�������%#�F��#3��K�"8qg	�J��B��S�/
rs�J���������d)lc�M�+Y�V�������4�~�BA�R�TE1s�Y������@	�lD��\�,���Z�j[0�u�N���3��W2z��K�>l����,��g_��k��z-i�gKKD�	O�A<��*�C<���A�|$a�%�#!�Ip�%�ou�-��f!�V$14�a�,��+
�r����&��_��&,�A����VSX"������]��!K�2��"�J�D���ed�9�0�0���:��1�<�?�H��������0����},����Te�i��R��-Zcy��mWwhuG�4��3' �A=�1��|��R��.��A�a�.	�w��MhZ�3N�./�]�����WR������`��=���]����$�������7���8���2�����I"�����s�������'��������.�����R�-=>\GR�4b�����_�:�0��]��3������9��4��V�����q����M�;dGr�v���vG�B����=�s���Q�T��B��}�x�?�>�w%��I����j����S��|����e�����0���:Q��d��\��E�3����N<'|�r�d��_Q��M?�.�&d'���<�B��C�q�K�i�i�����5�`X->�M&'^UBj*�:!p��l���+�����~�{��D
��G`���
q�`��;�n'�4d�����T 	1�"�*�5[1���fn��D�N9����"�j*��FJ/�XQ�4������ X47��s���
#T��	�G��9d0�IU�����{�1Y��:���a��7�����}8)!{�������4}��U�s��"Q���z�|��'z��>�J�>&r�8<��c�*�m��=��+)�aN7V��s�P�T+��^i�{�������h�����a2r�T��*��|�fQP]]�?>k��������s�`C�
�Bq����2�-��cn�Ox�	��+c���*"�$MqN�������L�����[E�3���5���L�XA=T�7�5�Y4NF����!p>L��~�BL\N���ft(`C��^pB�2J�&��5]�iB�v��c�
/w�N�Kmj��Y��'������,/�J�o���\���������p��3�\4r�"bq����Fx-�����G�4�*�6
��u�l��1U���\p�UoDd�|t��2-�dj�ka1S��24�(]�T6k��lw��P���?S|���-"��}�!��t��r�����L}�����e=3g�TD��IQ�|fO�'H|w�������`X{�'�z���=�����U��o�#|SY���+�h�|fz�q��������!sc��^<R��q����2��|�Xb�W��/X���J��Q<5u~J�w�f�$��������)��mGV#�ENs�*P���w�?�
�)�y�@t�)�iv���j�L���%�F��Y���E��Ij����s~;n:���c��<!����bf�e�{�I�~�Va6�����:o�y��yM�N���DZ�AY�����]��)1�P��.��5�r�

����/c�[�+R�$�j�	��j����( ��4���hc��'I9�%�LF(������3�����o��X���K	}�5�,/O2*�)�B ������q�k��O����+��lt�6�$��"�G�E�B�"�c�����4�S��ws��g���l��	�
�3���M�NV7��1������R�f@V�����Hif�U�:cw]�)L�������,�����E��[��Q��@�f�!���R��^z:���xSZ`{�W2�����@5
�2���x���n���_b���&�e�6�5��>k���e�o�Fq��T
�qFp�j�I���K�T�w�'��"�|�'E7�&�@'�.��~�T��J�m��nz1��S� �%G��\��0��\�c-�"�'�E��	��q�P@YA��i�Fg8����
|.
�F��'o��s.��$qii�]c���������9��N��*�PH
��K�HVf{����$v+���l���+��d���O�h9�����+�v��S��T?��oi���;9}3�9��W���d��+��du�dJ7O�=����8����6�d5C����G�4��@{LZ�$S��l�Q6 �#p�:l'_�8�MZWNjv��\P,����q�N�{�L��hrk�50��<9>;�{���o����(���9;���q��xzp��}}��������ob����?~�I9��&���O�Q����[9er:��\���9y�$��`<g�o�mq�%�*�'��C\�AL�cU R�3��G�1���i���p���nE?���7�:'�,h�F|�Y�1[#���m=��yjb�9����f%px7z�qS��^@�.�p&���mB7��m��I+�N�_RD
S���5���

:�/���k��c�V�m�ln�LX���s��v��A�s�c����:���b�����9��������Sr�S����
D��P����J������������2��ys<j�YrD�������M3���_?��s���B[,_�~��V��u<��4����eS��/�[��v
!�f��������wK��+m5������X�5���x�Y��x0���`l��=�����/�j�L�����:k�	Z(*e*�M���6�5W\�b�U���{9�M){�O��u^;�P�&���C�<�n:^����N�U�]���K�V�v����P�8�po#5,�I�\�S��\(sNx����� �m��8���]��$,�<�uB����k.��w�,�'s&I���������.$�i�����*��%���������t}����(�)�:�����5��o�y@��.������;|��qq��T<J���)U���V��d�7��c������^b�}�~�������>���f%u�o���0��N�k���6Ei�@�s����
z�u��f��N,$>��Q.��#�&������L�m0Bl�$��������w\��u��A�]�D��*X	��Y"���Y�%`����ic^��c\Z-w3�����I�4\zY���\�L�c)o52���^��DKG�}\�xE�����o�������^�5������n�]NDB'�.v9��d�a��s���������E���Aw�Q�p�8�-y(���@60Ita�)�Vd��KFt�%��/^<%��'��h�h|�/�,5�Ux��W	gaG������Y������X^�g�j?c9�}y}�Aj�����0x�,	�g�.)� &]���rdh�F���T�4��,�jdz�ra�~ -��)�C��c������Vm{'��s�q,<�7R�����j��S�A�y��7�X2)�Q�A��$�;i������
���h�Ei�n���A������.��T���^���C4�Y��&=���(O�T�ke�lh�~�1fb���<pMP��^
F#E_�E�Z[*E��[L\�����%M����iw2r����[�Q�7�s���Q��f#�_����������%s�-/-	B%��bT�8�Au�1}}O\�O��^��y�Y��k[����[����/�5��_��������	��uR��E��Y�&x�J�����7�fh���j[W���}��c���_1�<x��,M!k�+�e�S�'�lEv�����h�3:�	���������TYgMr���K�������.�3��:�D�vzk�8�|��������-������(\�����pK��^'�P�DXl��)�R����L���w�(�51�1����[8�bF��Y}���!^"59��3�(^�'$���
t�9E��+�����]5���h*_	1�k2'`��T!�r.k5�����k[O03��g�o@w%�|��D���=��&I�
���:����x�i�pO��CD��9�������Y�h����}��b��������k[�3��
Y/a�$�;Klk��!l'}�[Z%���U�@r��{Og�:%	'y�i�^���|��g�[��������n<Ob��^-l3�4���
��;�M�"iKk����Z���D0_-��%�30r�CT���|���,s��U���H�CjGwZ\Xz
��gQ�3�6����S@eC��s�[�6����ak���5L�Xo��P�����o[�����H��d� ����8��Zx�%Z+2�}_���Z��}~�n��g
��1[ra�x�T����8�7R6Q
6	�����������A��<��)���o�+�:6������(	����e`�� n���H��p����j��#�c��	�F*�A�R��L�l?����o�D�g��-�Chc�1��~U]|���B��v_���d"�M�xL����TA'4(�X���o�����H�H��}eD�,p����B�8R{��HI/1�hvd��g'W����>�8o�G'"X&h	EP
�N22F\��E&m���4B��!�V��$H�1S�W�9��h+Y7��2�*p�(�,>��g9�:�aZF�S���M�Z�?�W�|q��5����,X���zZ��77����=�]z�#fl�uY�����dL��>�<���P����FY�Hc�
B��b�IOO!XB'�`$�3��$�) �fw���Nv$\�J��<I����8$E���g����l3/Tp����'e�w��C��;��<��Y��d9��yl�(����Mb��S%������AI�U�APE�.J����X����i:Y6��s���F�'l�����Xj`)��c�j������E2�\��*p����0�h26���f���|q�d�q�Nb���@.�3��eU;��*s6�/��YE>����R��PY���8�B"N�$�a�3T�n�G���8bjM�-Z����,�����������l�Y��������H���"����q.�F��$�+��z�[.V��A�^�t��D>����,<����|{|4_t���#�����nY��(.a���K�u��2j5���D�:����d��M7
_�M<��pzX&a�?\sdz����q��}��w��M�Q"}��o����q�7��m�V �A��V�����ao>�v�*d���u#Xp����g���o�3�)K��W!p�����	4:`�Sn������9��L5k���jE���0	�T�[������^m�8��j5����p���b�b�����X���v�$����y�L�gs�8�o�:)�����H���S^��W��m�5�����*�S�������J������_���M)���=���p�OJ�\:&��m9�=��!Z�4������N/��n�t���<��;	%��*1��t�s��+���+�8�l������k��
A�uZ\4���/x��40�5�!�A���|�������
�
��x'e���?�}���U�K�rfT���D����������
�d
y���~��9;���d�m\Vm!�l6>F*>8�h$�.Z�Y�:��0B���6����%�;A�	.�#��T��8�U�<�b5�� 
�D�\Mo��p&��3?�a�u�L\�Q���T�7|���3{�u+M�������H��O!**��r�U�l��������O���10[P�L?����3(>�Y#�
`gI�@I�3�����M��%�!�]#��B%9�[[�n�=6�����n�Q�.���8���q<fi,0FU0��*P1%DF+[�6^��k��x��G�����N)����b^d�/�i��~4�+K|q:��sON Z������������Y����c�8.!G@����x(7V��aD�7����-��A[E�5P�Q�g�����)Q�5������s�k�@0i�6m��%=S��l�� z�Z>��E��J��jG�{�C�M�v>*�����	&�P��`{���`��\z���z6����I�`-v�{;�t�!��qw����(�D��9��l��Y����zHMA+�����fGK9o��_�����/���j����1�d���S�H�%����_[������$(��/Ek��w%�#�cMd�^��%= ��2�)PE��f�h�,A������nNK�P��8���(�C(&�}TF@(N�+��
'[b�W�L��=��bF/1��mG���)��bH,@K�[6��xW���o+��,!���j�J�q�Q����Nu��'��W�_~���S���?B�|�E^O�w��K.��:�;�������iz�=�������>�Puu ��
���
n��Z;.����fy�����2�����VBF�iA�>*���y�����n����H3{I\Q�Z�]u0U�������7�2�x�9�q!��lh��Ol� Ff�{�jU��@��������uL��3�2
�}�L��^�Y�v<��!Q�%;��H��U�"��)sO��Q���_�'��A�B��#(�w�X�?���c	�1
T�_���Fcf���P��Xf��BL!X0�E����)�Y�1* ����1�p�9�������3�#��xX��]��;��H�.kU�x��.M>���w.T!��.�5gfeG�������W�/5X<�g��Z�9[r����l"���b�MM�����sYS��BF�TQ�����/�C��	�A���[�c����"
-�b���,&L1>�������8�cB��P��A�aJ�|���������PR��RF����H�kD���r���&�I#�����-���n���|��1�h1����y�)A4��L�w���j�����"b�
�0�
�\���5Y�
�n�u�T�n�����P@��T4�w��U#������T���Ny��-�� ��p���K�4�/,�t��['�#�-F�t��s_L����KVy'�g��j�g�����f��^qIY���]?���g-���z� ��Jz�?]���eD�qt��a�\��{{�j:��b
�)���qx������!A#;.�={���+I�<p�D��D�Nv�iD���tF�q�b��E�����Ne&�������]��G��zE�<��K�x���"���8M�D�
�MM�jh$��	�	U(^�_vb�3W�9����l\������Z,o{�qw��<�����Ey��n;b��)��=,BC,��q����g�������x���sF6e������s�����X����c|����bE��)����b��L��:�9P9��Z�� ��&��<�D���F�\����_y,�+�:LT6��/xq�a���B"�`o�+���]$��/X���Whp"0�������-�1S��I�NsD����l�O-�Jv-ODe���*���������-f���K�=J�D�F���n����Br�i�[�_��dW(-�SV]�p�8A<T�:re��]�"��L$�=*5�!��$,:��Q�O4Y'c���N�4�d�'�c�����Wo��c!��B��z�_������U���]A3���*���R��o������@T!���e��73G	�\��5i
u�D_�5E���4�k%��f�V�����������dS�7��������<�h���!�n���8�r���� q�V��s[�M�R5x���g.Y;��4�D��D\�]�g}%����u�mv�B2<����+.������X�(k,�;�;�,���p��)��z�����	j�$�1�H���Nob���}�91��@���N�o��O�S�����(3����$�T�}5�w������+Lw��j_���!+����Lmt���p���-e�?���)�����|%*��7|�%X���=B�qP���P84{b9_$�x��8SD�}���C���������q_��lig-UKyh�y[�NpU���p��=��5��A�}���l������LG5����������zWY�%���]a����*�{��{�dJ������u�'��i
�tw)�U*�q���lW%�_	����m��(V��u�#�9�Y��bl�Xp*����u����`n����p��s����+������
��}l���HE�$��C���:�r5����o���HB)��:GAw�t����E�S�%$�2{�#�3��\:�U�
!�S��������9��������m��hE�a��2�?�~�(;�q����'�(��2������'Z+	C�J�uT�l/���	��m��r��gtM��<F��c�
W�b��N;���/%���������@o2}�T[��)���?�F����6����R�uyz�K�y{{�y���h^wN����S������+7$���D��4�bc+��<?_���J|Q[���bLiXb�����D���o�S����R�������x@����j�F=��YgR��h�t�����vg��-�b" �7���{m��2��ba/���L����F5Z.u�hMR���e���]Ea�������	�x�m��	F��Gx����q�Ci�0�F]IG$�njX0z�����M5�R�$fbw��w�������l��'�L���y�9{��"����h�+�;������a�JjV�=��J��M'��0�_��90w�Lq"��:&�� ~�]�Gz��;;T������aX��_�p���w<����;�i���B�P�4GL���������iS-L�P�����*�koQ�=�s' ���L��=�e����_3w�<EY��;P!�����Y��Kr��������f��
������;k������tP�7��!�%�Q�w)P":h�H��C��O��S�6��������4]R������!p�"�5N�������t�]z]K��j���,�2���
��"���|�f�j���7J��d�D�1�Uj%�k�G���:1e�1����!����^R.3:<Vf��R�cl�� 9T���������6~�c�Q
�V4��O�!�[�)jEn5_�&�G�[I�����A�%�(@��O�6���l��o��n�B�he2m�b!/a��4��	���$4E!1&bGAL<x"��@�d��|��]�K`#-h&�F,��*�_�/�Xw�X���D!���pR�1AF������a����Y�l���2*T.������{�����ruKKXfYN���;�|�2��D�v�%x��&��-(��`��Y(Yv�	��#�xY�;FD/0��q���������M�k�3)��ZW������q42�e�aL�o��1@}K^}�fr�C��8��J�m�+��#��C�{������|�qqKk�bf'�n�%,1�(���27�G=�$rF>�O�lM6��� ������Y����}�P��5I�g�:z$�3c�e�5l!w��,�������y�@Ob>��d�Z9���z��V��e��"��U�4]$�_�"�:5���>�
��"f�}��]��'�L�Z�i�Or�w��'F��9c�3��|�~��F,A�����;x�7Y�H�M��Z2�����9�h�1�I`P
�p���u)���0��c�c���v��9"�I	��D�"���B8�8G�����
���z/olq����O����I�+��c�n��#kE��������O$;JtZ�3�c#��A���h
�,�7�^�A�WE���t�d �,�tX~����a�����
�,y9�����R����J�vT��t�Z\x��H`6r��_x����k��/I���h��+�%�m��l�����z�p�J(k����R�U��K�	f����c�(�o2���S@y~;@4��xc.�����=\��g�q�;�z�?<
����y�����m�MB\�+
��w�wC/[;"�$A�uDN
������� !
���&>�W�9��Vh��G�H�"��7�WW-�!����->*�(��g�$q��%��#�X��$u
�����>�����k
�~T0
;q+��0/��J�����;nEY���>��Tk��|�X&���E��*��B��:�(	;���q�Jf�O2Y/[�>�wm��gt���-Af�6����#�M��g��0b�5�6�S�O��E��7���
�0���n8Xj��i��e�Q��~J?����X�Ys���@A���d�$�8J.>��|9�7�&PgO+7���+7���js��k�A&C�zW&��o����E�dD��z�U�H&leF����8�a�h�z2�>�(e� ������?�vh?�,�*�~;C��1�HJBA��U�I�c�F�����T��S������@(�q����]���O���F��t����;Z���&��<*��P�5�n!��NE���C4�����4v�]m<�3<v<������3k9X~����z��Z����5���R1�)R�I�K�N@�������
�����v�=5;�''Dyl)�d$,��6S%8#o�8�Q:�*v!!1��J�?��������7����Lw�.�L�Ua�+"�m^h�j��!���G�r����Q{<M,�T!�������c���h-��c��<4X��7�~��;���L�2;~�a��Ce���������+�����?����oQV\�>�����U�`W�:��y�,���o/_�C��= ���N�N)���jJ�xw�|l�8X`eU��R�xS���1yBWH�CW%4+�u�z����5��U�\�R�?c���c��&�]����2����J��q;`e���M�:��:��
�}\�}���y��:FS'�]A5Bu�R)�H�;{G�P+A#q�����NM�#T���n6TB�����p����a���\	c��v��H(0�>���.�1&�n����=XI"4��P�`7�������QA(S	����Mng[XCRN�Y�fK���Q�Q��M��B��Ojz+�F@"���
�����SR��J��N�1��h��M]��ot���d��|Jw����u�����ig�u���7o�$���v&z�8|��g��CjK�S2���+H��VD��X�� W&;�#���� )�<0����(+�Dm��7�QBZ�O�����Y��CG�0����C(-e�D�:)�����F	�4��N&��a&�P��Y�:#k��a�!�������8�hHE�,�R��z���6p�7X�*��������K����}\	���s�'/�<�]��J	���!��2��F�P��V���u>��LE�?i��Hx*e���[�.�����4D1���I����hf��3!d�54l}�Ga�)����/��N��C��u�5�27��)��G79���O����Og���
C���)��Y�)K�02�:���W�#6��8&L��N�oz��������u��
������w����>������X��Y����UT��/o@#zAm�����V��
`h��	E������\9c'A���<�V�����P��+��S#"�Z��X��%6�~���IUfJ\Lv'�!b$^)�k��e8��K�,��/0%(�Z�M<��+�C�o��\���������)��L8��<�?Gj���3���'� '0��s��c?��iM�M��	����U=�fUu'T�U�mWA�,�B�u�@6�T�r�i+��C�m�Ne^���7��b��rB��B�%���DR��/��X��oE����N�ZN1��\���$���8}b,S^�3t��,�xT�Z��St\�no�NDfA2�%M���H�("" ���y�)���ZQ�R���<.}T+�_f7$��@��A)��Q=cv�_��o&���!���!��[{m����n����%6V �7O�:�Z����
���b(���F	�"�(}@��FWgF��z����,��,��EK���brC��8��M�m�0�[P�"�>��.5�p��hP&H�*-�E����Z�` �nw.�����i���T�7Mb���I�3����@ZV�@��������g�-�.����g��?B��|��)?��*�#��I
!'��!��AGKo����.<?�V�u	����2��K2\}E�]�:�L��UU��,�h)��s����j�T��kB�v�������nW�Ar��{���n�������hnW�-�u��!�{��S"�Dg}"k C�g�g����;��N���]eVb��K�e_����[�e����%kB����n��I��5g�g��0�^h�5�LA
��t>UuCE(i���)Z�����R������3Z���X���N�B��0I���d�%��Ev���y��p��M��T�[l��������Q�����gI�9"(eF�X���aM9x���D���z��r�W.�+�clte����`@�*�h�Y����+t?���:d��Z��<�s�9*f;��QJ��s�Z`�UY�^B�MRp#��pN��+YP��c�MG�"���F��
Y���
L}4/p�'/;a����H���MF��`��@��%���)Z$�X�E�����	����{����C��d�����r��e@�W�-f��o9L(��W�u��S6��m�#�&u�(��d�������W#���%D0�v�1�I��{`�8>���b{�Nw�qb�H������c6^wbe�Zh\�	�`P�	��%�u����fg���q?�L|3T�	s�,�'��$�5u���l��"%�#^%�\E�
���������������R�$���2�� z����_��ww���D�h1�L��6R�7!=��������A� z��m��[T�d�.m'�#� z�/^(�^��8�Y�//���\f/D!B���G]DPT�?����^���1D<Q�9~�5�Z���8�Y���g�VY�]
T2��M�t�RA�EO�q�&���(�W����L~�
���{�
��hc���/c���"69�����^�\�����&�S�2��|�8<{��~�������`��# m`��UJ���7��iO?"���~����,��\�@��9/�	�X�0��t��9�&�o��Y�%%����zc/,������K|�B[�R/+M�C��?�9_���R�0�9	��k��`yC�7("��������N��yy����J�E���v�y�9��b���R������|��8��d'������r;'�T/����p��D��{Y�EET|��F��0�k����������MZX�����k_9|��:�$����V����lm��X�xK�����l�����})�v�_�_�)�:���xJ]�}���N����u<;8��v�9Y�;� 
ZG��S�_V�����g'��\Wv������Z[����#"��
W]Cr�b-z;���:�>�[�'W��MX����i���5�:��-/����k\xoqa��t}�2�j�=K�-�����
@��|����"q���i�*�5�],F�%/�������(=^[�����Vq���R�Q������R�y<N�b0A��N����PDtdgC��(���;��Ta	4��)4sf�[[m�h]
�b>��k���F6l����47��`G�i�����}F�4P�)�����.��I\���	���1Cz8K�����v�c�����R�|X;�jMB<��=�Cn#�Iq�Iq�R�v���wV;�>f�
kT��I�5�n��!��i�8����G���c����S���CP�Qf�%i��.���t
��%����$j��}��sBG�]���F���������v��
�.O��7A�����-��Kf:��E=������-mx�![a3�g�]n�,�(( 8R�qB*"Q��
[�l>����M4|�K����w�5*@9�L����n�go�,���Y����h�.��>$��`|b�{��%]B��S��. 4;r9d��Mn���8z(���L�j�}Y��g86\
�Y�{��"4R�Rr�G���������@�VL���r��'���,V�����!>����
L��Q�	��G���Z��`�V�K�K�g����<]/f"�`��-]kTJ���$S�����!?m��)2;���L��_t��J�P���e�"���r@��db��H��Q��>���72]M�`�D?��%�E�#G���qpM��!*���ys�-]�J�4��"�;
��6�T1[�U�%�G�����d���m{mM���!�/���T����S
�����t&�i�\��\�6!#�R@�\F'P/R�$M���-�Q��j���g�F����SZ����IU��&Z�K��A3m�S#da��QO�??^���A�\n��kQ�#�*l��7E�M����O
��dy�&-�T��h���Z�_k[[����-�<{��`����1�e�1�=r�0{�p&/�I��pv`����7���#�N�Q���xT�A��`x;�Tw
���U"�w�?v_��RA�4��B���#������w�2�bO>�f�7������ ���G���/���v!(�w��$��_D�t���(�j�=+@���U�5h����l� ��}����n��T��\��O�aJ\*�`��,�x�$_���p8�U��n3RcGm���^T���hz��Q�hf�K��.�2FG�X��5��zu?�,�%REK�X%���#�T�^�A�@��u��h�����|h���k}8Ca�W������|y/R�jf30���*��V%��Z�*\IAa�E��mr�Lk6�
s	����2:����$Dt��k}y����$
$]�OJ��'�)
��Sn�R���)���%����q�k���AYi\&��?@������o]�@�Sz��m4�C��A����8�*�Z�v��j����k�z���nc������r:
um0���<o���g7�w����������sk�/�@��3���h��y����3W+G�Z��|�j�@m���P�>J�:���X�l���� ���Z�<;�m��)�>7��5�c���5qf����*�*������,�U���s���#��;�SfI�^��v�)����K������b������\_���
3,�uqx�	o�V�|��b��~�NG�����=�5���� ���|���/C(�����C5����D��'��I'�����.%��T�m(3Be�u��K�K
�'�>���&b�x�
I��<������A�S�+,���]cP��������~��-�{��^���|M��h��0����M��6E>�=��J�l�������*q�����e�:�~�
��_�a���[N��'7�dG��S����R��thB�1[TR9R���W9:���������	����G��F���rm������&%�?z7v�q	=�;��0T8���G��g�"��<c(;�hh+}�'j��`��N��g�eY��%��21��z}�0��*V��@��m_�����A���,==c��,��p~�K}A%�G���%���0���?����Yiu����a���9��Sw k-g�3����uW6�������z'b��j�y%�P'5�n�L�z��gN+��*3��-������Qt������'Sn_�����:2����1-�h�P�_����{8��E�:Xh�p��,�*+5xS=��5��X�K�\�����_�F�2}���d���g�6#�*P2G����� ��b?��A?�=�$������-�@��J�a/B���
�J��zY�y�Y�����&S��6T���}RR����FB�3�����|G�w���h�����IT\Z����8�;�����6�����R]��d���`d6JA���F!_2����ap_;7����v1��B�����tc��W5�Q��_.t��Etc���+C\�>���_��'�
�JS".N<	UDE�
����c0����c\>��:)�-�\�CM+��:�x������J�G����X�H�Jo%�^��>g���*��{~r�
R0�g�pF	��),��9�/)C��`!���+��9hq�	�6���ry��28jx�\�k����N7j�'��('�D>�6�C;�N����I��h�o;���{
��|�_�����!���M
q��3�[������-
���q�.F����$�Kvq�����+M�~��_�����F�^Y�#vE�b�!V
�?�U������o�� �Z����.[�JN�����@�&���9AO����G@hm
�i��Fd�}���u
�>V^���m���s-��gL�H����!V���U����������
on����E(<������F�n-�6o[g������V����W=��x2��(�v"b1�2�YG�+�����

(}�+����A�v���_ U���TJ()f91���_��}�TW/�K��������6M����$��f��o�5p��d2}��>
6{[���*����$GNQ{1������d>]�R��Dwv���c
������$�0�q�a����JQ)���\��5�yS�JP���$-�h2��v�Z2�v���)b�wk�Nn�)��o�SR*k�v/}4�A��=����/�2��wx[�Y��V��J�YG��`���Q�o9<���==.��y�=�������
��Wg_y&�t:O;Ld�����(}�J5����
4�DkA�~t�Fzs��=��/s��T�d�D��h}Jy���ZaDp�H���`2d��`�N��6��y�����n?N>������%���n�+�tkK���q��T'��i��iWB����lGT]���;s����2NFQw����
a�$�&W�DLsU�VK,�-����D�U��a��Y�]�K���c������c0����D������gh����i���5XpKd��N�!�b�PB�|�a����R�-e[9E�4u'�9���m*WB��NbW�G�����r5]�u�Y���BG<���1��
(��l��
4_Lf8|RU$w!gH'e`�(�A�����L5(�
�����-���;�����S�U�L�p����&13�j�������l����ZI�������T��I����q��/�&3�a2���u�\T�h��J�(�C���#�"q�lU�D#�����b&��z^���	}.�lS��L1�G� ��B�+N�S&�'��dN�
��$����5e�@{K�	
���9#�����IJ�0�4<�(FS7�[�m=sV�V�_o��~!����M�����������-���^3;�Lb��,���
����Euh��OE�X��LD�VD�n�����&(�H	
��V��n���0��:���l�5�X�����/����u��?_��A�vqIuZ0���?�����d�*B���kVz������#���0��8��J���-B�����-��X���B_��8���w�'\`.�=Sn�k��W��0��2��m��UCA���C ^�
��l"}�Z[�Cf[�,���������7��$"�b�~ b�aP�C�]��1��LS��*���Q4e��y�\Du�
\c�2|�<�W���4�q�y^V��N1R�1����*�lO������Ie��(� =�����	q/���F��V�Q�������W�7�&D/%oka���j=sXh���$��p�����uq}usK
�\�>����1�F��!���������t����=��E) �f'%:k��7��KFp�J��|���/�\8���1��x�%J0+D�2)��j�"�m�0?/��j��dF�nC�4��T(�h�'4���I�8�_(��������
�z�Xx_'L���q.������i3/B�a+kJ�R�$O�O�u�)a�D��D���H�rBYR�_
�S|�Y9Ij����z�U��[-���d��|���LI�����9WE)7���?������
��H��=��b!�KZ�*Q'ZG����W������=U7����w
>U���~c%���$}��������������f�4R���A��,�I���[���pG��,��<r14��4�h��3J��:��������':s/5���~��E�<���������x����\���G�^������c����Gc��1���v(`47:�� �E���v�����m�������}�rVW��"���3�Ea��}�����S�p�����W�wK$Q����$#l0��tC��
N&d�l`�
����,]��3��2�Q%�Z�
	%#���,�&��d��V��n��Tn���i��=��O��q�LX����Oyu������`}9#�peC�|#'Z"r��yJIIz�(���WD���3Q�I=�A�x_�,�I�����q2���l�k�
?n��a��0��/��)}����p�����Y�������X��qaK<@C���6�8Q��e"�[����lam>�G�Y8w�0kKWi��8'������WZ&%������R��t���\R+e��tG��J��aX�*��g�7�~,�f�^G���%���&&l;��I���(U9��7!e�:%���9��9=�y������G����C��������s�z�"2�|�~���}�V�:*����I��Y�4���#�H.E�0]S�~>��QwM��
������
lpJ��&�2u+[�������j��;�$�m��J����.%u�>^��4��:�!�P�6�_���%��Z�;%:��k�3�,�a"y���{~�����,
M�=�-��%���:y��|{&�fK��9�l�������3���p���kI)�u��`�b���s��~c;�`s2�S\��� ��� ��m���[Mu2��$_D|*�y	���?��L�H�) �9�57&#
��W7C����eQ�NO��v}��XI/��)�\��mL[��i��|�Stf�MB�v�^��'����&����I�q���0��5�M~q���!>CkKh,%��L�Mni&SU?��bn������L�FA���*	<��r��TR/�|l������2C��u���~(Z��
?8�����zN|G�!x�[�i�:H_��w�����3N �dZ������QuK�[*���EFgYs����b��8�Gt���I�[��A�D�]������&-u�~�hzN�X
���������|�M|��-�r�7gL*���c�e�����}Y[��c<��#Y�����u��5{�c<��H��`�$��C�9�0����v�/s&��+��3��YJ�5cAj��M��7���Pe�.<��}w����
9���H,f�'�Ip%B+�hMT�]$��������>R�/�����������d�RImU��,���,��s�s��
}�9���*�kuF=wl�����������44L�a�L�����C�����T���kI>��UD�
9k���uX���t�&�fiT�}���*A�eT:��Q�Xnm�����YVRz��)�/��+��u�T���������:&cL������Kx����O�f�;m��]���"���9
��pXZ�N��;|_`w.�9g�K��T,{	uA��^>����e��E2���[% e3��$J:&7�;
�[%v�*�!�����#��I��&v��#E���A+���|H��ebK�pQ��U���>FQ�������08�tRz��P���ZB��1X0��*���X����G$LX(��GA��d+qZW��
e��!�O��ER��"���m2@!��E��i���vBhM�����|u+x��MW'dB��	L���}��������}7�����k��?�I-m!�����U{��!��@R�<��I(��z�|S�S��a)4�g�i[��n?"BM�R�����'��h���v����lvtF;���3l�36�9����E�����"P����	P�?�������C��`2�
�/�>��57Jl�3��d�{G3JJ���P#{9��>������U8`N"���$K7�va�cw��;}as���TAg� �^�������#M����������M�f���#�h�0��t��1����[���������j���6c#�,Q��%G�&����K��F��a��]$T9��F��pV5�����e������85���~�KV~�TB}#����X[\
(_�����$����S�J�k[��\2�g��N��t�T���������3Q^E�N��B����6�"e��L�@1�U5y��D����S�#�����/P]���e�0�;\���$�+�����*r��~����{��������#C��S�.Q��(�T��rl^�����r4+ �]�����U�o �D9����������U	�+)��!��!o`�C2�zX������z��U�PnYQ�D�E:�B���e�y��;�w>������� �'Q���f��3V�b8L�T�"w����_SXszl�m��������R_�{%i�YV��@��
<��0�2\����(Pw�����B�M��.��H�N��K�z
��*����|Lw�(Ip"��Q��l<��������������4������������R���3�g�fL�H��k?O�S��
|��s�u���;9��|?��u�a�SYgb�p�y`X�$��.��,���JYH�&���q��[
����C�N�)�Uc�gD^�JA��R���=�*<��%�C�b
�i���9���m��/�F<��P��J%��	��MzI������F��R�r��@���s�������d$����<�0�]��!���3Y��$���>����x�.QR8�������=>g��>s�
!��(,S��gC������X������� ����y����2+]C��)__�N�~��}e�>� ����{d$Sr�9���)���������x��%�`��p,,�t�k%������_2�������M��Z������I�<�������3��j+{�!m��c�(�|���~T �Hn��X�3Mg�����!�t<��;��>�*��mVX�>�6�����j�->lRe��v�g���!9�<�� {����� �N+��e�������m(��,�E�b8����W�2�����1�y��0���,#`
���b���(,^����V���E�������IC�{� ���ub���K�AE���]a��T�2�A����k�����tYeL����L#�+�7��*!'r�#��.�g^0�K�"�� ?=���Z�Cbg\��h��??��c��Uf1��T^�~e	������e��rQk�$�Ie��I������^r�lqrO
h�����O���$"�6�OY9\�����e���=���0���^���<��!��9QoHZ3O���������#�YL�]8��m�K�8|Q;���V�:dN�gO����z�B
(6Q]��9���?:���B�x�v�'����Kr;��Y��l����;����1&���D��n���
�������:�Md-H��hs=�"Ct��v]���K5|[���g�Ka�b�j��?�Soe?5�B��|@;�0�d8�H�2��9)�9��1osu��c{��b��#!q�Ei�,(����qyE���lu��a������=p������g�����������~4{��8����A�0� �&���5����`[;oG3�1���`�{y�yo� Z��}�0���������V�����n���h�� X���������R�xs���1�n��������Z���D(���o�x�UC�����D�a&���r��l�8�#�
�sT�A�����2�)	�����#���H��d��\�*Z��58�������)@X[&yt�K�3I#���p�P��0=�4}�d��f�,6z������C��o���0N�Lf������UN=����y��-��oZ��gKN�r���h�N�s���J��6��J����	��-�a;_���J������Y����Xg�^"����/2���������U�t���L���m��=�.��W�A���X��6v�����L��v)a[`R.�	�}����<���m";,����?13p�	?�n���������,a���r9������^7P���n|���?sy�?b�����+�2~�1��'��~e9U��+�n�,����S4��:��<�J/���K8�S9�M)m9n��Tx�}��j�.����;�,xxJ;����Hs
rc�dqP��i�BX�UV	����`����Z��UdJ��Q�av)�!����_q�!������k<�yDK�X&_uk|-AL7}��EU%����nQ��+���{�M$�G|7����m�"k��y�<%d�!��G�'Z�"���i�SD���Y:��������F���O�?MSJ��=���,�Q��,�YN��h��\����jc9���AT���~H�?�:�^���WF�=��������H`b9X�Ox
�1F1�3���]�3["����g�r�X�:{�z�4�c��Zph�v��F�^H������7���A� �F����QuA^�v~��D��q��~vOpuo�;��8S��lf_��+���������S����A�
�hO{5���P7�Wz"Ib�Uu�0gV�r���}QB�Z���r��������E��_�z�5�|��2"�@��	79�wh\h�qL���f�
�U�M_�����-�:NG����V�4�Q+:�0����6��
�b�� ���PY����,|O�8��D����3�L;=���J���nm
�y4)��0�>&E�������W^���P�6Q>�����o��c>���OH�.��=2-w��f>������19�H���+,L	a%l����v���_I��`�9O'p���$�oN�����]��R��P���0�i��E�^�t>���e�U��'q�:����8W�]J$-&*S�*�N�u��>!�mX"������g�t�*���Y��s����i���i��^���^�#�hkG�Ru�"��m�B�@E[��Z	~(^�����9����-9/���	O���?��as��%�gv@p`�g���[��_�T5�K����?������u����� [k���i�w_�	��	�G,ln��lo�*���e����8�������9�J]���S����)��f�*�u���tw�3�'�
�hW��}{�������
kEN�:�g��w�!O�|�i"N(�r�[,$�M��B
�Pf�u�Y�}�1�����<.=���@�O�����4���/�g����n�R��t�#k�w=��	%��E'SUz��-��p:A�B�b>	����Q�8�_�8���*X�Z��>�itc�f��~�=�:�g��U�,������Z�%o�UP�N]��!Z�c��>�L�z���
�n3z�����������I����M�X����&"�xb�dR<:T��#����MDb�^tB�1��E������B�P>���a��_?�D�pR���\EU��c�j�js[6V�
����WW�!�dU�G��������)�����������D�WW���k�	$#��?e8ZK%n&�}e��S���F�����N������u{tH����������'������"����f+
�v���wJ�
x
tt���:@������F�X����nX���d?�,+Y ��x�_UG���r�Vm��1S����DR�E�����H����(-YO�P�1�=N��`*.��������6u����M����W����*Ir�=�����7��I�$ ��#��0�Ps8�GyN�
KQ`��E���Y<�)���'c�@�3m$�xd9�����7+�������&0\=�Du�&�{�Fc�:��]��;���U����S	8�����b�r�����5n�}Yr�,�#,��������m~���V�(x�.*��u�;��u�Ua>gF�hx��a�"�[����b&�
0xn�Up���d�x�
��k��pi��~��*����xM��v��|^��\����RfQ���:����`�0N�XKu��������0u��]<N_c��L�8}N,���H��F#�NH��������A7��[	���������T�U�t;����-�"t$����,��?�������r80�p9��:��4��5����28=b!g�t�����l2�7����'�l����K�kv�V�*b�7�|Cv���I��`cb@�5tV�@�G��,��R3~���3�1�\*�?G'p�����x�EKA@lO��	q�������6��]�����P��Dp2�^|=N�B��C����UJ)pP�����r�~��<��1����$�?8<"=���!Jj��������S����on�.0x-�I?�;�9��0>�(
~||o	:�i}�9�7��������j0�{��p�7�e������C���l��������N�����������y&�����i#0��Jd33XU4�e��a�����Y�Fc �9��?y���9�V�����s&�iR��S~��9%����3i_,��{|���'���,<[�p�B�
�|�	��o!�O�]YV��A�����h��O��:����I�<%��z�I�����(U �`��`
b��a�:��v���t�oQ��v�O]�������2J,Y���������<�'}���yPf��dk}g�`_�������w9���$v��0�~��W_E�&0�;y1�C�s���QR��|K����U�n�_.��0��z6b�������/�7��~�t�~����%&0�O��#!B�m��3�����������l2:��U �������|�C?��
��H�Zlb�$|����~g�y�����iG�U��>K~Q����'�P��x4]R������~wC�$���+!tQBV���Q�k(�H���{����0p8qZ�`P*��|m���8:�<R���L��NG�8��U��Q�R��0P�H�^�s6D;��4>�����m�Gd��KX�y�B�e_�|:���,��S��/��9�}"�2b�}V����Q)������3�.(��U>��p�>�q{.�o['b��_a+�1����x�����d2 S�3���y��.)��T�I�����l��w��z�����H��������& 41�>`#������o�a��&������'4��zg��<����,�#�����|�@�M�Q�iC�dh�r^���'o�p14	�����e������d���>���nr.\J����5 r{����-%�D����"���0�)$��`��������_�U�u��l^�u�����l�#@��������O|��K���w=������������~_������"B�zU�0���r�-���}�Q��'�����;Ox���Wc�X������Y�?_?�8��<���P{)�N�����XM�����v��Sbqpt$����7����:d����I�k�A=�5���}����e��[������<3�}]G�f�z��L>�G�L��;q�����!��yB)d���+_I��8G�[�w���+���D���g=i���n]�� o���^C�)'}[g�,�������&�-���H��XYu������I�����1B���P���Q�Z�����"�W���f������<� ��z��T�;�s�JS&�A�_�-o�����:���o��`�bUs'�}�n��cv_��2���	�`�Zn���Z�|wtT��5��x�>���"3~��:
0005-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-pg_receivellog-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
�e�:R0006-wal_decoding-pg_receivellog-Introduce-pg_receivexlog.patch�;m{�����_!�M��&-��Rv�m�����$l�<y[I|pl�����~gF�-;N��<4��h4���/o����y<yvtt4y��q�0���I����`21�G�xr`<{����\6�>k?e�V����V�]~	h:������]�=7��w�5���iZ�E���x���q�����V���[��V��1��_yO��������F�����li8c���e���g���������u����M�=�O����;��n��^���m3��;�a��h4�,���a~���G����9��w�G�d���YM���>L61B�Cc�9�#{�zg,�o��e|�S�Q������UQ�����}���BF����@@#���A�d��WF7��t��C"z���d�v�8f��P��=j2�
y��VjU�w��Y���N�Zf&��8[x������dq�l��)k4@����BQ&[�m����������f{rt��K�P'���<�n��g����O�G�F�?cpk�y��.y�.*7����	0�K����R`��Y�V��c��,�A��|�m���aFu�����)��v�����>�p�]!������B^���
a8L3�@\�j��2��������0bQp�".\�����SG��0�~��6��� Rf����F{�l������f��6!�$�	��U�j�ARe������4w��'�gA
@.�x�zj>;l6��'`b��Yz�b�� \�A�.������A`�����/��.��s��_+�=�?��eU�kE=}-��?�5<���4���m���`�%���8QR��Q��M�\DB"2���DRY���p�AK�\�|/�@�ay��H����[�@����C����[���w0��_+o�����j��F���������qf#�n�������=4�6���4��SB�HU�K]�}�����ptvu5�n.g�rZ�(������pt�T����m�����E��a#��.qhBgK�N�����w��W��q�~1�]���c���D|�b�]�^�,Xc���_���d�k:�p�e����0l7�?4�F���XW_ ;��y���[B�VXd�0��5����0I�6�n%y^�VR@�/)���G[��lN���Q��b�������q6��iP�Z��|��!����?�S��=��3�d
&�e��$M�I�,#2Tsl��C��)��af�"���,}O������{6�X��B�����5_ �h���+v�x�p�1Z���.���w���^���F��k1?�6����E�Y��_l�tbP�_�#Oys��}��1�G3y~4v<w���B@C�����?��$�
��a���2���JALo����)/8���<�o�_�7��j2#<bYtu���G��1����������zfD_�QHl������l�=���������n�?�^u��T*��/�	�!)��5`����Gd�������I\(� ��x!>l�����y~���Z�{`R3>�`]0@B���uR�
.Cn�}X�;Q�:�64����P����
���0<#�@�����m���sX��@�v4�8���B�m ��������o����-�h�<A��1o�� ����iiy������Aa:yh�8
����_g���3��N�N��(i���d�JE}j!��c����5,�;�~�8���\Uf�	8�1*@VO�O�d� �J5?�\���k_ R��5��+;C�oC�fS�7�����jC(�x`��j��d�=`&{w�G����l�uX�����15�u�hf�/{W]��R�{���X�;n�!@�8]��z
���cy�����24����1�!&e��&��n�}i�a1��$�g*@A�m��0�!Dspo��������;�������>����{�.��('�P�������3��L�@�,�?�1��\����7���ayL`�P����G��y�}1Nqb%s����p"s����h� �8������4� #d��M{j��Jp#x1���:���X�(�)���W=.FrK+�`�����-�U@��c���� l#�<T!0w��Z(��x���=�C�c���uv�����k��[�������9zpF���:*�D��[[�&O���W^��>1�N�Wo.{�8�������!M��"N���s���E������;�um�LjT.#�P�A�0U��G�q�1���5��������p���S�@=�f������"Y�T�L<��������?�\�s.[�n:'�#���4�@A��O�Z����*�Q�4��d�7l��b+���5p�����}����D�F�&�S����������� ?��q�H����������U���iz��ES��*�#����6S$�g)U�}�[�"�a��MY�hY"\���[X]U���s�~��v5�G���I��c���d)�"�(#C��Sf�UfD�b�M���6����_�������]�2�&|�:�f����w7bKCY&p �QC�>�KJ'�[��@`G�!h�i����T���a��3dG����7)��8����M�-{�*�E��4��=b�CUtC���*�E"J�d�BS��S� ���T�����pj��S'���x�v��[�@��[��TXg��T5"_�`pC{�HX&�D&��|���(.T
*&�
'
�t����������D���&Q/
���(V��5� d�Rm�E�^8a�*^A��Z�6�o��:L���g:����V@*���
a�0�U�����U�s0)��?���xG@UT�/j���x��:Y�2-;�n��
(���p>r�M�UV�*�K���s#�������g��(��ZM+����=�h�2~�$����{�>���C8K��I\�Y��d�YJ���y����^z5T����
;��d��K�*�������������%�}�.y�T���@��(�$8p��g�@$ �DL'���:��Ft��m�[��dD���;�[b'+�M=Y�o���JUV�:��t��C�!�R1��+H�U�F�'������l0_�\������n�J�;�{�����A ����jV8�e���e����i��5@b+�n�t��b�����
P��Q�+���f6��IW���g�w�,��H�����s��M9��s�f��Z-qY�{}#0�6��$��k=?J���AKR���9p!x)r����������E�M���tc��L�Y��%�B����tA��T�fE�]=a�p����	���DS4#G/�u����zRY��Jy { ����U�������<�iZ����,	�	��?�y-��&)�n�_~�bVw���.I$0:C����2	t�(�-�Q%U������t�������?nF~gR�����&����h%��"���KH��F�5n�t�n�<���t�<�A�3����J�m��r�}���LS=a�Hi�����>�����h���
��k
ln0��<���V�����P0R�Vs��C�7�`��)CQG�O+�1X@�G��}H.�'pL������������M&PP
����{:������&$�aE�:�To�.��b�U�7�����?P~�R�L�/�F$�bRJ��"i�s�H�����x@f9����R�H����Vf"�$���kRV\�k�b����c���\TH����-z�I)$h�<�
q�0
M���Q��L����0����ia���������F�wjA	�VCy6-�(t�-�6=��������|�G	�_^���;��<J�J��'����h������A;3�MZ'�� <�A8����$��X���Q ~]?j�M
��z^���U��U���b*9�`,�<{����Ed+�����Y�{I�����b7�M!��Y���m_	t�v��
��C��B,�g�x�>%��2��U�d��t}�$�	(�r
��OFI�4N}�5�F��������F���]���"IF���K�o��$;``�e���d����\2�����x#�������j��'l�-��)4�))S���+~��as�'Y\�	���z�A]h�WU
����,���H4�Cf�Q,����<�������!CW/x�+�8�5kQ��$<j/�����%�gW2{�t����[lF_3��!p��K�%CT�.>������8�v����`Li���m�������|�1f�����A+-�dL|���t�~��nV��p�����z�;T��F���a�Oh?j�:��@�h���*B+�0��I��:���[�4.��������=j�3"EG�}"D~�}�g�\�EmU
li8� 2�Y�u=g�]-������X,`���}h�1�`�~����L����
���q�e�b�-jXI�S����*����c��~;��2=��0���{M~�����Y�����o�q��y�����"��f��]p����6�q����@!I��������zC�x����y.�u�%��a��#d���*����Z���eID�I
a�VS+_d}�^bs���fT���A2FY�<�[��c�M?a{�\5��~��9
 ���M���CQO�mZ��;�)���;�_�*�wZg)�%xe|�� eHU�����m�0���`��d��4v��"I��C���#Y���p�|��������~������y;����~�/����'.�
�������[��%����7�@�h{����Xt�'���DKJ!�^���%iK"��H����l���Wbw��$!������5�T���Ff�R�Io`"�o��2�]�}D��a,f��z{�����WP�KA��[�d���b:�<��,�;J>����B;���K��{��L��Ho�����lJ`����~S�W8W��UO������Ol/��t���Wg����k�
��H�?�P�`��x0Ag�����,�����Y�,�Jx��(K�Y�$n��z��b�a���/��d��Z��vm�SO��b�W��F�-�0�:���!�c���4%�w�F�C�@e�6;�+�.�0B;
�	�������,q�����2z�r���s<�t���k���r>;����VL����*
7��Y������n�^�:n.:fr�[Sc����.��Q�wy`8�~���;(��:��6dIc�1������t�k]��S�;�����	�l�����������8<D��7���j�N'KT�e7.�J���h#�<��kA�T���F��:b�f��������G�����G�z#q^:!W��O�s�R7�
�Q����C}����SL/�'��-�b��3��aR�uvq(A�UK�nQ�J���O!�1�}�w�!�&l�UO�q�]��!���e����B|0���8��J�6�:f����������ue'�f�8m)#�%�Bd_T� 3sB����9�
 �sl��B�����wI�=�VQ�����t&��P�����~��5C����Re;�����a��z
y_����)���{�o��)��{gu���w�t���N�v��y�w��#]�U���K;2����\�Z'_2�ep�1Q�.,�e�>�D"UN���XW�M^(k��S�2�j9���Z"�k"5|kR��Pz��;��j(�{��j��"��u��-N��/|��(S2��Q�����������a���I����{�.��������_���uC�#6�!��S��E2D=����P�gxJ^`P������Vw��m�X�g���h��G}���f�4m3��E�b����X�c��6�����"EJ�,'��s 	E�������>��!�-f�5e�.F=?����V������un���_���;J�d�9��b8`%���v��`	��
 �&��G�����R-�I&��<g7�j������6'��c������ou�b���W����(j<-��{��[��K�`��{b����R:��(
Syv��T@!P�!m������pKr�0~�Xn/+Z�La������>U���G��X��E����	�c�Ox��
 ��J�~%�{�����D���km�&�xr��t
����|H�=�/��g��^\Q/���{d���
�Y�����u����c�-�`,X�����e��S���ci!����P������7��2S\�)�G�?���7�\\w"$WD��\���sD���Px|���O&z�Y�=�16�����6�e9#��eT���������m�*c�&�X���=�r�>I���u
4a9!NC^a��	���ht��e��p�t����Y�}�8h$�Ea3�W�������M��r�f�\
�Z�*��p������4�K������iU�fU|16��U�a��4w1�*�������i���E����>R~������	j����%r��������vD�p�F�bv��������[!����a�p����m��5���0��d����/�����������*��(:4�����96��&�R)Z�������{���P�\�������{g��2R���R���4"��=H�^q����|������5���u>��"�q�N����D�!\Z~��s-Ue�V��~������G���SH��~U�'6-�i����9+��@������DIL@�Hi�L��G"+�1F���A�^�;<�A�ic�)�;bT��.�o<i���'J�?Q�O]�����gJ�+,���J�s��a�~u��i��H=s�*$+��[����}�Iu������T��-
1�Q��aA49���V����5t,����Y�����j1��d�#=u62 xz����14{m�224����%��~I��R(�?&?#_����y���d\W��!2������
��(�C+���.�2����
��^]�Hv����q�����O���q��
�����~"!�Qx��\x2cR+���8vV��H�1�u2
�AI��7<��1�>:Ki��lpI������
��$v��"���]Y��v&a�m��;�2�6Z������SL����8���VSU�G�G��yL����.Y\��LW�U���&�q>�����(��������b���g�pAN3+�b�n�zC�-dx�>�GD�
�0��<VU�(���{������=������J�W�����XV��(�1����L����W����dy!�X����6�~��E:o
�|�1W�=�8�����)[��b8D��i�u[W�U����D�� ����#P��/h��0�e��$6&*H��P�����6�p���2f�2�����u��Z��c��#�*L���V��S�va*� �}-������i0'ku{����.!�7N�������cv�����+��\-5�����oNNIG<|��������NUM�?>��NB������������gR��o��{�
~A�"W��H���]cjV����w��
�]�+���XU @��)p~g�j�=X��D3�G���"$x����[��%�|��Y(t��l�Q+��%*�C��/S�N�����_�/��'�$]�Y����}�y'iP���������(�2�JW��2�C���=Q� ���J�/����T���O�P!NN������RJh*;�K�`U
Ay�U���:���]���W;���n�y���u�k(������,G��j�uU����Q������ \@�"��A��m�Kx����Ux1F�B���y��MS)u��u����b����*8�q�j[_.��h������5v�b�X�aC����yF;zy!i�����JF��q	�qL>��������_eA�J:n%�TJ���v��&���?�N{�wc�!MI����I]`����-����C�i�\�a���g3��Q��Hbn��9��8�z��),N�?��|Q���n@���d�V�����}�r��l�IH���G|���"�����7�tT�]mQ6F���Fl����~�Ey;Z��)�)y"J��������G8�{|��nK��{ENJ���9#{P>�k����P	_wZ�����4g��|�^|V:�.��E�g��%�A-`�V�����H���)��OTi
u��L
������M��(N�9+�J������v��j��J;](a�$��R���\����Q�����c���]�i��������go�M�J>n�b��c9���Rhb�������l���a����3 ��Y���x)	fW���&i�4�*��f���+������A��:�C[���������
���"����;wG�^{4�����^����sV��9�;<�U����2.x�0��'�H�s����s�n���������^cZ��z�N�;."��<�8���%p{G,�wl�uL����4$�����!���C��R�K
9����(�FT�XjD��.���������[��L�������
���b����N����B3��u�
��S�g�`��r���wo�O������,5����������a��d�O��8��k�!���z1mIO&kKz��c[z%��^��������S���g��x����j�u�����h�~ �%n[�%o�����bh���2U�]�t�MU5���l�;)��z]������< �K`��
�
�c�*�5��ir�%G�
�(q��p�1���t����y9�e88��,�pOI���-<Q3zXTI����](s\	x���AH��SO<����fw����heK�P��/�s�X��l����,�uOT��=Q��'x�b�Q���Y�	+R���@�y�SB(/����9%���9%�[
L�����M����37���R�Q)���;o�����������7��>$�����,+�!��{'�!Q�uHp'��;�����"�:�vCl�G����
���9�7���;F}���fF�J�Q����Wr���!�F�TeA�J\��7�'������_��~�5�W��w
�qz��_��+����J�	�~%i��/�������v&}��w?���z������
OU��L'#�;xm��&_1<�8�J+�N�X�����SQW�%RE�TTT5uOb8��,?M�+u���@�8�JM�Q�r:4��9���B�\��U��3�JE�!�V�/�zH�X����������4�=m�
 �Zk�O�)��KI��L��]S[�z�������~�5j��������f�k�
0007-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
#10Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#7)
Re: logical changeset generation v6

On Tue, Sep 17, 2013 at 10:31 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Rebased patches attached.

I spent a bit of time looking at these patches yesterday and today.
It seems to me that there's a fair amount of stylistic cleanup that is
still needed, and some pretty bad naming choices, and some FIXMEs that
should probably be fixed, but for an initial review email it seems
more helpful to focus on high-level design, so here goes.

- Looking specifically at the 0004 patch, I think that the
RecentGlobalDataXmin changes are logically separate from the rest of
the patch, and that if we're going to commit them at all, it should be
separate from the rest of this. I think this is basically a
performance optimization. AFAICS, there's no correctness problem with
the idea of continuing to maintain a single RecentGlobalXmin; it's
just that you'll potentially end up with quite a lot of bloat. But
that's not an argument that those two things HAVE to be committed
together; either could be done first, and independently of the other.
Also, these changes are quite complex and it's different to form a
judgement as to whether this idea is safe when they are intermingled
with the rest of the logical replication stuff.

More generally, the thing that bugs me about this approach is that
logical replication is not really special, but what you've done here
MAKES it special. There are plenty of other situations where we are
too aggressive about holding back xmin. A single-statement
transaction that selects from a single table holds back xmin for the
entire cluster, and that is Very Bad. It would probably be unfair to
say, well, you have to solve that problem first. But on the other
hand, how do we know that the approach you've adopted here isn't going
to make the more general problem harder to solve? It seems invasive
at a pretty low level. I think we should at least spend some time
thinking about what *general* solutions to this problem would like
like and then decide whether this is approach is likely to be
forward-compatible with those solutions.

- There are no changes to the "doc" directory. Obviously, if you're
going to add a new value for the wal_level GUC, it's gonna need to be
documented. Similarly, pg_receivellog needs to be documented. In all
likelihood, you also need a whole chapter providing general background
on this technology. A couple of README files is not going to do it,
and those files aren't suitable for check-in anyway (e.g. DESIGN.txt
makes reference to a URL where the current version of some patch can
be found; that's not appropriate for permanent documentation). But
aside from that, what we really need here is user documentation, not
developer documentation. I can perhaps pass judgement on whether the
guts of this functionality do things that are fundamentally unsafe,
but whether the user interface is good or bad is a question that
deserves broader input, and without documentation, most people aren't
going to understand it well enough to know whether they like it. And
TBH, *I* don't really want to reverse-engineer what pg_receivellog
does from a read-through of the code, either.

- Given that we don't reassemble transactions until commit time, why
do we need to to ensure that XIDs are logged before their sub-XIDs
appear in WAL? As I've said previously, I actually think that
on-the-fly reassembly is probably going to turn out to be very
important. But if we're not getting that, do we really need this?
Also, while I'm trying to keep this email focused on high-level
concerns, I have to say that guaranteedlyLogged has got to be one of
the worst variable names I've ever seen, starting (but not ending)
with the fact that guaranteedly is not a word. I'm also tempted to
say that all of the wal_level=logical changes should be split out as
their own patch, separate from the decoding stuff. Personally, I
would find that much easier to review, although I admit it's less
critical here than for the RecentGlobalDataXmin stuff.

- If XLOG_HEAP_INPLACE is not decoded, doesn't that mean that this
facility can't be used to replicate a system catalog table? Is that a
restriction we should enforce/document somehow?

- The new code is rather light on comments. decode.c is extremely
light. For example, consider the function DecodeAbort(), which
contains the following comment:

+       /*
+        * this is a bit grotty, but if we're "faking" an abort we've
already gone
+        * through
+        */

Well, I have no idea what that means. I'm sure you do, but I bet the
next person who isn't you that tries to understand this probably
won't. It's also probably true that I could figure it out if I spent
more time on it, but I think the point of comments is to keep the
amount of time that must be spent trying to understand code to a
manageable level. Generally, I'd suggest that any non-trivial
functions in these files should have a header comment explaining what
their job is; e.g. for DecodeStandbyOp you could write something like
"Decode an RM_STANDBY WAL record. Currently, we only care about
XLOG_RUNNING_XACTS records, which tell us about transactions that may
have aborted when without writing an explicit abort record." Or
whatever the right explanation is. And then particularly tricky bits
should have their own comments.

- It still bothers me that we're going to have mandatory slots for
logical replication and no slots for physical replication. Why are
slots mandatory in one case and not even allowable in the other? Is
it sensible to think about slotless logical replication - or maybe I
should say, is it any LESS sensible than slotless physical
replication?

- What is the purpose of (Un)SuspendDecodingSnapshots? It seems that
should be explained somewhere. I have my doubts about how safe that
is. And I definitely think that SetupDecodingSnapshots() is not OK.
Overwriting the satisfies functions in static pointers may be a great
way to make sure you've covered all bases during development, but I
can't see us wanting that ugliness in the official sources.

- I don't really like "time travel" as a name for reconstructing a
previous snapshot of a catalog. Maybe it's as good as anything, but
it also doesn't help that "during decoding" is used in some places to
refer to the same concept. I wonder if we should call these "logical
replication snapshots" or "historical MVCC snapshots" or somesuch and
then try to make the terminology consistent throughout.
ReorderBufferTXN->does_timetravel really means "time travel will be
needed to decode what this transaction did", which is not really the
same thing.

That's as much relatively-big-picture stuff as I'm able to notice on a
first read-through.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#10)
Re: logical changeset generation v6

Hi Robert,

On 2013-09-19 10:02:31 -0400, Robert Haas wrote:

On Tue, Sep 17, 2013 at 10:31 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Rebased patches attached.

I spent a bit of time looking at these patches yesterday and today.
It seems to me that there's a fair amount of stylistic cleanup that is
still needed, and some pretty bad naming choices, and some FIXMEs that
should probably be fixed, but for an initial review email it seems
more helpful to focus on high-level design, so here goes.

Thanks for looking at it.

Yes, I think the highlevel stuff is the important bit.

As you note, the documentation needs to be written and that's certainly
not a small task. But doing so before the highlevel design is agreed
upon makes it too likely that it will need to be entirely scrapped.

- Looking specifically at the 0004 patch, I think that the
RecentGlobalDataXmin changes are logically separate from the rest of
the patch, and that if we're going to commit them at all, it should be
separate from the rest of this. I think this is basically a
performance optimization. AFAICS, there's no correctness problem with
the idea of continuing to maintain a single RecentGlobalXmin; it's
just that you'll potentially end up with quite a lot of bloat. But
that's not an argument that those two things HAVE to be committed
together; either could be done first, and independently of the other.
Also, these changes are quite complex and it's different to form a
judgement as to whether this idea is safe when they are intermingled
with the rest of the logical replication stuff.

Up until v3 the RecentGlobalDataXmin stuff wasn't included and reviewers
(primarily Peter G. on -hackers and Greg Stark at pgconf.eu) remarked on
that and considered it critical. I argued for a considerable amount of
time that it shouldn't be done in an initial patch and then gave in.

They have a point though, if you e.g. replicate a pgbench -j16 workload
the addition of RecentGlobalDataXmin reduces the performance impact of
replication from about 60% to less than 5% in my measurements. Turns out
heap pruning is damn critical for that kind of workload.

More generally, the thing that bugs me about this approach is that
logical replication is not really special, but what you've done here
MAKES it special. There are plenty of other situations where we are
too aggressive about holding back xmin. A single-statement
transaction that selects from a single table holds back xmin for the
entire cluster, and that is Very Bad. It would probably be unfair to
say, well, you have to solve that problem first. But on the other
hand, how do we know that the approach you've adopted here isn't going
to make the more general problem harder to solve? It seems invasive
at a pretty low level.

The reason why I think it's actually different is that the user actually
has control over how long transactions are running on the primary. They
don't really control how fast a replication consumer consumes and how
often it sends feedback messages.

I think we should at least spend some time
thinking about what *general* solutions to this problem would like
like and then decide whether this is approach is likely to be
forward-compatible with those solutions.

I thought about the general case for a good bit and decided that all
solutions that work in a more general scenario are complex enough that I
don't want to implement them. And I don't really see any backward
compatibility concerns here - removing the logic of using a separate
horizon for user tables in contrast to system tables is pretty trivial
and shouldn't have any external effect. Except pegging the horizon more,
but that's what the new approach would fix, right?

- Given that we don't reassemble transactions until commit time, why
do we need to to ensure that XIDs are logged before their sub-XIDs
appear in WAL?

Currently it's important to know where the oldest transaction that is
alive started at to determine from where we need to restart
decoding. That's done by keeping a lsn-ordered list of in progress
toplevel transactions. The above invariant makes it cheap to maintain
that list.

As I've said previously, I actually think that
on-the-fly reassembly is probably going to turn out to be very
important. But if we're not getting that, do we really need this?

It's also preparatory for supporting that.

I agree that it's pretty important, but after actually having
implemented a replication solution using this, I still think that most
usecase won't using it when available. I plan to work on implementing
that.

Also, while I'm trying to keep this email focused on high-level
concerns, I have to say that guaranteedlyLogged has got to be one of
the worst variable names I've ever seen, starting (but not ending)
with the fact that guaranteedly is not a word. I'm also tempted to
say that all of the wal_level=logical changes should be split out as
their own patch, separate from the decoding stuff. Personally, I
would find that much easier to review, although I admit it's less
critical here than for the RecentGlobalDataXmin stuff.

I can do that again and it actually was that way in the past. But
there's no user for it before the later patch and it's hard to
understand the reasoning for the changed wal logging separately, that's
why I merged it at some point.

- If XLOG_HEAP_INPLACE is not decoded, doesn't that mean that this
facility can't be used to replicate a system catalog table? Is that a
restriction we should enforce/document somehow?

Currently catalog tables aren't replicated, yes. They simply are skipped
during decoding. XLOG_HEAP_INPLACE isn't the primary reason for that
though.

Do you see a usecase for it?.

- The new code is rather light on comments. decode.c is extremely
light.

Will improve. I think most of the other code is better commented, but it
still could use quite a bit of improvement nonethless.

- It still bothers me that we're going to have mandatory slots for
logical replication and no slots for physical replication. Why are
slots mandatory in one case and not even allowable in the other? Is
it sensible to think about slotless logical replication - or maybe I
should say, is it any LESS sensible than slotless physical
replication?

Well, as you know, I do want to have slots for physical replication as
well. But there actually is a fundamental difference why we need it for
logical rep and not for physical: In physical replication, if the xmin
progresses too far, client queries will be cancelled. Annoying but not
fatal. In logical replication we will not be able to continue
replicating since we cannot decode the WAL stream without a valid
catalog snapshot. If xmin already has progressed too far the tuples
won't be there anymore.

If people think this needs to be a general facility from the start, I
can be convinced that way, but I think there's so much to discuss around
the semantics and different usecases that I'd much prefer to discuss
that later.

- What is the purpose of (Un)SuspendDecodingSnapshots? It seems that
should be explained somewhere. I have my doubts about how safe that
is.

I'll document the details if they aren't right now. Consider what
happens if somebody does something like: "VACUUM FULL pg_am;". If we
were to build the relation descriptor of pg_am in an "historical
snapshot", as you coin it, we'd have the wrong filenode in there. And
consequently any future lookups in pg_am will open a file that doesn't
exist.
That problem only exist for non-nailed relations that are accessed
during decoding.

And I definitely think that SetupDecodingSnapshots() is not OK.
Overwriting the satisfies functions in static pointers may be a great
way to make sure you've covered all bases during development, but I
can't see us wanting that ugliness in the official sources.

Yes, I don't like it either. I am not sure what to replace it with
though. It's easy enough to fit something in GetCatalogSnapshot() and I
don't have a problem with that, but I am less happy with adding code
like that to GetSnapshotData() for callers that use explicit snapshots.

- I don't really like "time travel" as a name for reconstructing a
previous snapshot of a catalog. Maybe it's as good as anything, but
it also doesn't help that "during decoding" is used in some places to
refer to the same concept.

Heh, I think that's me trying to avoid repeating the same term over and
over subconsciously.

I wonder if we should call these "logical replication snapshots" or
"historical MVCC snapshots" or somesuch and then try to make the
terminology consistent throughout. ReorderBufferTXN->does_timetravel
really means "time travel will be needed to decode what this
transaction did", which is not really the same thing.

Hm. ->does_timetravel really is badly named. Yuck. Should be
'->modifies_catalog' or similar.

I'll think whether I can agree with either of the suggested terms or can
think of a better one. Till then I'll try to make the comments more
consistent.

Thanks!

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#11)
Re: logical changeset generation v6

On Thu, Sep 19, 2013 at 10:43 AM, Andres Freund <andres@2ndquadrant.com> wrote:

- Looking specifically at the 0004 patch, I think that the
RecentGlobalDataXmin changes are logically separate from the rest of
the patch, and that if we're going to commit them at all, it should be
separate from the rest of this. I think this is basically a
performance optimization. AFAICS, there's no correctness problem with
the idea of continuing to maintain a single RecentGlobalXmin; it's
just that you'll potentially end up with quite a lot of bloat. But
that's not an argument that those two things HAVE to be committed
together; either could be done first, and independently of the other.
Also, these changes are quite complex and it's different to form a
judgement as to whether this idea is safe when they are intermingled
with the rest of the logical replication stuff.

Up until v3 the RecentGlobalDataXmin stuff wasn't included and reviewers
(primarily Peter G. on -hackers and Greg Stark at pgconf.eu) remarked on
that and considered it critical. I argued for a considerable amount of
time that it shouldn't be done in an initial patch and then gave in.

They have a point though, if you e.g. replicate a pgbench -j16 workload
the addition of RecentGlobalDataXmin reduces the performance impact of
replication from about 60% to less than 5% in my measurements. Turns out
heap pruning is damn critical for that kind of workload.

No question. I'm not saying that that optimization shouldn't go in
right after the main patch does, but IMHO right now there are too many
things going in the 0004 patch to discuss them all simultaneously.
I'd like to find a way of splitting this up that will let us
block-and-tackle individual pieces of it, even we end up committing
them all one right after the other.

But that raises an interesting question: why is the overhead so bad?
I mean, this shouldn't be any worse than having a series of
transactions running concurrently with pgbench that take a snapshot
and hold it for as long as it takes the decoding process to decode the
most-recently committed transaction. Is the issue here that we can't
advance xmin until we've fsync'd the fruits of decoding down to disk?
If so, that's mighty painful. But we'd really only need to hold back
xmin in that situation when some catalog change has occurred
meanwhile, which for pgbench means never. So something seems fishy
here.

I thought about the general case for a good bit and decided that all
solutions that work in a more general scenario are complex enough that I
don't want to implement them. And I don't really see any backward
compatibility concerns here - removing the logic of using a separate
horizon for user tables in contrast to system tables is pretty trivial
and shouldn't have any external effect. Except pegging the horizon more,
but that's what the new approach would fix, right?

Hmm, maybe.

Also, while I'm trying to keep this email focused on high-level
concerns, I have to say that guaranteedlyLogged has got to be one of
the worst variable names I've ever seen, starting (but not ending)
with the fact that guaranteedly is not a word. I'm also tempted to
say that all of the wal_level=logical changes should be split out as
their own patch, separate from the decoding stuff. Personally, I
would find that much easier to review, although I admit it's less
critical here than for the RecentGlobalDataXmin stuff.

I can do that again and it actually was that way in the past. But
there's no user for it before the later patch and it's hard to
understand the reasoning for the changed wal logging separately, that's
why I merged it at some point.

OK. If I'm committing it, I'd prefer to handle that piece separately,
if possible.

- If XLOG_HEAP_INPLACE is not decoded, doesn't that mean that this
facility can't be used to replicate a system catalog table? Is that a
restriction we should enforce/document somehow?

Currently catalog tables aren't replicated, yes. They simply are skipped
during decoding. XLOG_HEAP_INPLACE isn't the primary reason for that
though.

Do you see a usecase for it?

I can imagine someone wanting to do it, but I think we can live with
it not being supported.

- It still bothers me that we're going to have mandatory slots for
logical replication and no slots for physical replication. Why are
slots mandatory in one case and not even allowable in the other? Is
it sensible to think about slotless logical replication - or maybe I
should say, is it any LESS sensible than slotless physical
replication?

Well, as you know, I do want to have slots for physical replication as
well. But there actually is a fundamental difference why we need it for
logical rep and not for physical: In physical replication, if the xmin
progresses too far, client queries will be cancelled. Annoying but not
fatal. In logical replication we will not be able to continue
replicating since we cannot decode the WAL stream without a valid
catalog snapshot. If xmin already has progressed too far the tuples
won't be there anymore.

If people think this needs to be a general facility from the start, I
can be convinced that way, but I think there's so much to discuss around
the semantics and different usecases that I'd much prefer to discuss
that later.

I'm worried that if we don't know how the physical replication slots
are going to work, they'll end up being randomly different from the
logical replication slots, and that'll be an API wart which we'll have
a hard time getting rid of later.

- What is the purpose of (Un)SuspendDecodingSnapshots? It seems that
should be explained somewhere. I have my doubts about how safe that
is.

I'll document the details if they aren't right now. Consider what
happens if somebody does something like: "VACUUM FULL pg_am;". If we
were to build the relation descriptor of pg_am in an "historical
snapshot", as you coin it, we'd have the wrong filenode in there. And
consequently any future lookups in pg_am will open a file that doesn't
exist.
That problem only exist for non-nailed relations that are accessed
during decoding.

But if it's some user table flagged with the terribly-named
treat_as_catalog_table flag, then they could have not only changed the
relfilenode but also the tupledesc. And then you can't just wave your
hands at the problem.

And I definitely think that SetupDecodingSnapshots() is not OK.
Overwriting the satisfies functions in static pointers may be a great
way to make sure you've covered all bases during development, but I
can't see us wanting that ugliness in the official sources.

Yes, I don't like it either. I am not sure what to replace it with
though. It's easy enough to fit something in GetCatalogSnapshot() and I
don't have a problem with that, but I am less happy with adding code
like that to GetSnapshotData() for callers that use explicit snapshots.

I'm not sure exactly what a good solution would like, either. I just
think this isn't it. :-)

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#12)
Re: logical changeset generation v6

Hi,

On 2013-09-19 12:05:35 -0400, Robert Haas wrote:

No question. I'm not saying that that optimization shouldn't go in
right after the main patch does, but IMHO right now there are too many
things going in the 0004 patch to discuss them all simultaneously.
I'd like to find a way of splitting this up that will let us
block-and-tackle individual pieces of it, even we end up committing
them all one right after the other.

Fine with me. I was critized for splitting up stuff too much before ;)

Expect a finer-grained series.

But that raises an interesting question: why is the overhead so bad?
I mean, this shouldn't be any worse than having a series of
transactions running concurrently with pgbench that take a snapshot
and hold it for as long as it takes the decoding process to decode the
most-recently committed transaction.

Pgbench really slows down scarily if there are some slightly longer
running transactions around...

Is the issue here that we can't
advance xmin until we've fsync'd the fruits of decoding down to disk?

Basically yes. We only advance the xmin of the slot so far that we could
still build a valid snapshot to decode the first transaction not
confirmed to have been synced to disk by the client.

If so, that's mighty painful. But we'd really only need to hold back
xmin in that situation when some catalog change has occurred
meanwhile, which for pgbench means never. So something seems fishy
here.

It's less simple than that. We need to protect against concurrent DDL
producing deleted rows that we will still need. We need
HeapTupleStisfiesVacuum() to return HEAPTUPLE_RECENTLY_DEAD not
HEAPTUPLE_DEAD for such rows, right?
The way to do that is to guarantee that if
TransactionIdDidCommit(xmax) is true, TransactionIdPrecedes(xmax, OldestXmin) is also true.
So, we need to peg OldestXmin (as passed to HTSV) to the xid of the
oldest transaction we're still decoding.

I am not sure how you could do that iff somewhere in the future DDL has
started since there's no interlock preventing anyone against doing so.

- It still bothers me that we're going to have mandatory slots for
logical replication and no slots for physical replication.

If people think this needs to be a general facility from the start, I
can be convinced that way, but I think there's so much to discuss around
the semantics and different usecases that I'd much prefer to discuss
that later.

I'm worried that if we don't know how the physical replication slots
are going to work, they'll end up being randomly different from the
logical replication slots, and that'll be an API wart which we'll have
a hard time getting rid of later.

Hm. I actually think that minus some s/Logical//g and a mv won't be much
need to change on the slot interface itself.

What we need for physical rep is basically to a) store the position up
to where the primary has fsynced the WAL b) store the xmin horizon the standby
currently has.
Sure, we can store more stats (most of pg_stat_replication, perhaps some
more) but that's not functionally critical and not hard to extend.

The points I find daunting are the semantics, like:
* How do we control whether a standby is allowed prevent WAL file
removal. What if archiving is configured?
* How do we control whether a standby is allowed to peg xmin?
* How long do we peg an xmin/wal file removal if the standby is gone
* How does the userinterface look to remove a slot if a standby is gone
* How do we decide/control which commands use a slot in which cases?

- What is the purpose of (Un)SuspendDecodingSnapshots? It seems that
should be explained somewhere. I have my doubts about how safe that
is.

I'll document the details if they aren't right now. Consider what
happens if somebody does something like: "VACUUM FULL pg_am;". If we
were to build the relation descriptor of pg_am in an "historical
snapshot", as you coin it, we'd have the wrong filenode in there. And
consequently any future lookups in pg_am will open a file that doesn't
exist.
That problem only exist for non-nailed relations that are accessed
during decoding.

But if it's some user table flagged with the terribly-named
treat_as_catalog_table flag, then they could have not only changed the
relfilenode but also the tupledesc. And then you can't just wave your
hands at the problem.

Heh. Well cought.

There's a comment about that somewhere... Those are problematic, my plan
so far is to throw my hands up and forbid alter tables that rewrite
those.

I know you don't like that flag and especially it's name. I am open to
suggestions to a) rename it b) find a better solution. I am pretty sure
a) is possible but I have severe doubts about any realistic b).

Yes, I don't like it either. I am not sure what to replace it with
though. It's easy enough to fit something in GetCatalogSnapshot() and I
don't have a problem with that, but I am less happy with adding code
like that to GetSnapshotData() for callers that use explicit snapshots.

I'm not sure exactly what a good solution would like, either. I just
think this isn't it. :-)

I know that feeling ;)

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Peter Geoghegan
pg@heroku.com
In reply to: Andres Freund (#11)
Re: logical changeset generation v6

On Thu, Sep 19, 2013 at 7:43 AM, Andres Freund <andres@2ndquadrant.com> wrote:

More generally, the thing that bugs me about this approach is that
logical replication is not really special, but what you've done here
MAKES it special. There are plenty of other situations where we are
too aggressive about holding back xmin. A single-statement
transaction that selects from a single table holds back xmin for the
entire cluster, and that is Very Bad. It would probably be unfair to
say, well, you have to solve that problem first. But on the other
hand, how do we know that the approach you've adopted here isn't going
to make the more general problem harder to solve? It seems invasive
at a pretty low level.

I agree that it's invasive, but I am doubtful that pegging the xmin in
a more granular fashion precludes this kind of optimization. We might
have to generalize what Andres has done, which could mean eventually
throwing it out and starting from scratch, but I have a hard time
seeing how that implies an appreciable cost above solving the general
problem first (now that Andres has already implemented the
RecentGlobalDataXmin thing). As I'm sure you appreciate, the cost of
doing the opposite - of solving the general problem first - may be
huge: waiting another release for logical changeset generation.

The reason why I think it's actually different is that the user actually
has control over how long transactions are running on the primary. They
don't really control how fast a replication consumer consumes and how
often it sends feedback messages.

Right. That's about what I said last year.

I find the following analogy useful: A logical changeset generation
implementation without RecentGlobalDataXmin is kind of like an
old-fashioned nuclear reactor, like the one they had at Chernobyl.
Engineers have to actively work in order to prevent it from
overheating. However, an implementation with RecentGlobalDataXmin is
like a modern, much safer nuclear reactor. Engineers have to actively
work to keep the reactor heated. Which is to say, with
RecentGlobalDataXmin a standby that dies cannot bloat the master too
much (almost as with hot_standby_feedback - that too requires active
participation from the standby to do harm to the master). Without
RecentGlobalDataXmin, the core system and the plugin at the very least
need to worry about that case when a standby dies.

I have a little bit of feedback that I forgot to mention in my earlier
reviews, because I thought it was too trivial then: something about
the name pg_receivellog annoys me in a way that the name
pg_receivexlog does not. Specifically, it looks like someone meant to
type pg_receivelog but fat-fingered it.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#13)
Re: logical changeset generation v6

On 09/20/2013 06:33 AM, Andres Freund wrote:

Hi,

The points I find daunting are the semantics, like:
* How do we control whether a standby is allowed prevent WAL file
removal. What if archiving is configured?
* How do we control whether a standby is allowed to peg xmin?
* How long do we peg an xmin/wal file removal if the standby is gone
* How does the userinterface look to remove a slot if a standby is gone
* How do we decide/control which commands use a slot in which cases?

I think we are going to want to be flexible enough to support users with
a couple of different points of use-cases.
* Some people will want to keep xmin pegged and prevent WAL removal so a
standby with a slot can always catch up, and wi
* Most people will want to say keep X megabytes of WA (if needed by a
behind slot) and keep xmin pegged so that the WAL can be consumed by a
logical plugin.

I can see us also implementing a restore_command that the walsender
could use to get archived segments but for logical replication xmin
would still need to be low enough

I don't think the current patch set is incompatible with us later
implementing any of the above. I'd rather see us focus on getting the
core functionality committed and worry about a good interface for
managing slots later.

Greetings, Andres Freund

Steve

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Andres Freund
andres@2ndquadrant.com
In reply to: Peter Geoghegan (#14)
Re: logical changeset generation v6

On 2013-09-20 14:15:23 -0700, Peter Geoghegan wrote:

I have a little bit of feedback that I forgot to mention in my earlier
reviews, because I thought it was too trivial then: something about
the name pg_receivellog annoys me in a way that the name
pg_receivexlog does not. Specifically, it looks like someone meant to
type pg_receivelog but fat-fingered it.

Yes, you're not the first to dislike it (including me).

pg_receivelogical? Protest now or forever hold your peace.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Peter Geoghegan
pg@heroku.com
In reply to: Andres Freund (#16)
Re: logical changeset generation v6

On Mon, Sep 23, 2013 at 1:46 AM, Andres Freund <andres@2ndquadrant.com> wrote:

pg_receivelogical? Protest now or forever hold your peace.

I was thinking pg_receiveloglog, but that works just as well.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#16)
Re: logical changeset generation v6

Andres Freund escribi�:

On 2013-09-20 14:15:23 -0700, Peter Geoghegan wrote:

I have a little bit of feedback that I forgot to mention in my earlier
reviews, because I thought it was too trivial then: something about
the name pg_receivellog annoys me in a way that the name
pg_receivexlog does not. Specifically, it looks like someone meant to
type pg_receivelog but fat-fingered it.

Yes, you're not the first to dislike it (including me).

pg_receivelogical? Protest now or forever hold your peace.

I had proposed pg_recvlogical

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#18)
Re: logical changeset generation v6

On 2013-09-23 13:47:05 -0300, Alvaro Herrera wrote:

Andres Freund escribió:

On 2013-09-20 14:15:23 -0700, Peter Geoghegan wrote:

I have a little bit of feedback that I forgot to mention in my earlier
reviews, because I thought it was too trivial then: something about
the name pg_receivellog annoys me in a way that the name
pg_receivexlog does not. Specifically, it looks like someone meant to
type pg_receivelog but fat-fingered it.

Yes, you're not the first to dislike it (including me).

pg_receivelogical? Protest now or forever hold your peace.

I had proposed pg_recvlogical

I still find it wierd/inconsistent to have:
* pg_receivexlog
* pg_recvlogical
binaries, even from the same source directory. Why once "pg_recv" and
once "pg_receive"?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Peter Geoghegan
pg@heroku.com
In reply to: Andres Freund (#19)
Re: logical changeset generation v6

On Mon, Sep 23, 2013 at 9:54 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I still find it wierd/inconsistent to have:
* pg_receivexlog
* pg_recvlogical
binaries, even from the same source directory. Why once "pg_recv" and
once "pg_receive"?

+1

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#19)
Re: logical changeset generation v6

Andres Freund escribi�:

On 2013-09-23 13:47:05 -0300, Alvaro Herrera wrote:

I had proposed pg_recvlogical

I still find it wierd/inconsistent to have:
* pg_receivexlog
* pg_recvlogical
binaries, even from the same source directory. Why once "pg_recv" and
once "pg_receive"?

Well. What are the principles we want to follow when choosing a name?
Is consistency the first and foremost consideration? To me, that names
are exactly consistent is not all that relevant; I prefer a shorter name
if it embodies all it means. For that reason I didn't like the
"receiveloglog" suggestion: it's not clear what are the two "log" bits.
To me this suggests that "logical" should not be shortened. But the
"recv" thing is clear to be "receive", isn't it? Enough that it can be
shortened without loss of meaning.

If we consider consistency in naming of tools is uber-important, well,
obviously my proposal is dead.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#19)
Re: logical changeset generation v6

On 9/23/13 12:54 PM, Andres Freund wrote:

I still find it wierd/inconsistent to have:
* pg_receivexlog
* pg_recvlogical
binaries, even from the same source directory. Why once "pg_recv" and
once "pg_receive"?

It's consistent because they are the same length!

(Obviously, this would severely restrict future tool naming.)

In all seriousness, I like this naming best so far.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#21)
Re: logical changeset generation v6

On Mon, Sep 23, 2013 at 1:11 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Andres Freund escribió:

On 2013-09-23 13:47:05 -0300, Alvaro Herrera wrote:

I had proposed pg_recvlogical

I still find it wierd/inconsistent to have:
* pg_receivexlog
* pg_recvlogical
binaries, even from the same source directory. Why once "pg_recv" and
once "pg_receive"?

Well. What are the principles we want to follow when choosing a name?
Is consistency the first and foremost consideration? To me, that names
are exactly consistent is not all that relevant; I prefer a shorter name
if it embodies all it means. For that reason I didn't like the
"receiveloglog" suggestion: it's not clear what are the two "log" bits.
To me this suggests that "logical" should not be shortened. But the
"recv" thing is clear to be "receive", isn't it? Enough that it can be
shortened without loss of meaning.

If we consider consistency in naming of tools is uber-important, well,
obviously my proposal is dead.

What exactly is the purpose of this tool? My impression is that the
"output" of logical replication is a series of function calls to a
logical replication plugin, but does that plugin necessarily have to
produce an output format that gets streamed to a client via a tool
like this? For example, for replication, I'd think you might want the
plugin to connect to a remote database and directly shove the data in;
for materialized views, we might like to push the changes into delta
relations within the source database. In either case, there's no
particular need for any sort of client at all, and in fact it would be
much better if none were required. The existence of a tool like
pg_receivellog seems to presuppose that the goal is spit out logical
change records as text, but I'm not sure that's actually going to be a
very common thing to want to do...

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Peter Geoghegan
pg@heroku.com
In reply to: Robert Haas (#23)
Re: logical changeset generation v6

On Mon, Sep 23, 2013 at 8:12 PM, Robert Haas <robertmhaas@gmail.com> wrote:

The existence of a tool like
pg_receivellog seems to presuppose that the goal is spit out logical
change records as text, but I'm not sure that's actually going to be a
very common thing to want to do...

Sure, but I think it's still worth having, for debugging purposes and
so on. Perhaps the incorrect presupposition is that it deserves to
live in /bin and not /contrib. Also, even though the tool is
derivative of pg_receivexlog, its reason for existing is sufficiently
different that maybe it deserves an entirely distinct name. On the
other hand, precisely because it's derivative of
receivelog/pg_receivexlog, it kind of makes sense to group them
together like that. So I don't know.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#23)
Re: logical changeset generation v6

On 2013-09-23 23:12:53 -0400, Robert Haas wrote:

What exactly is the purpose of this tool? My impression is that the
"output" of logical replication is a series of function calls to a
logical replication plugin, but does that plugin necessarily have to
produce an output format that gets streamed to a client via a tool
like this?

There needs to be a client acking the reception of the data in some
form. There's currently two output methods, SQL and walstreamer, but
there easily could be further, it's basically two functions you have
write.

There are several reasons I think the tool is useful, starting with the
fact that it makes the initial use of the feature easier. Writing a
client for CopyBoth messages wrapping 'w' style binary messages, with the
correct select() loop isn't exactly trivial. I also think it's actually
useful in "real" scenarios where you want to ship the data to a
remote system for auditing purposes.

For example, for replication, I'd think you might want the
plugin to connect to a remote database and directly shove the data in;

That sounds like a bad idea to me. If you pull the data from the remote
side, you get the data in a streaming fashion and the latency sensitive
part of issuing statements to your local database is done locally.
Doing things synchronously like that also makes it way harder to use
synchronous_commit = off on the remote side, which is a tremendous
efficiency win.

If somebody needs something like this, e.g. because they want to
replicate into hundreds of shards depending on some key or such, the
question I don't know is how to actually initiate the
streaming. Somebody would need to start the logical decoding.

for materialized views, we might like to push the changes into delta
relations within the source database.

Yes, that's not a bad usecase and I think the only thing missing to use
output plugins that way is a convenient function to tell up to where
data has been received (aka synced to disk, aka applied).

In either case, there's no
particular need for any sort of client at all, and in fact it would be
much better if none were required. The existence of a tool like
pg_receivellog seems to presuppose that the goal is spit out logical
change records as text, but I'm not sure that's actually going to be a
very common thing to want to do...

It doesn't really rely on anything being text - I've used it with a
binary plugin without problems. Obviously you might not want to use -f -
but an actual file instead...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#25)
Re: logical changeset generation v6

On Tue, Sep 24, 2013 at 4:15 AM, Andres Freund <andres@2ndquadrant.com> wrote:

There needs to be a client acking the reception of the data in some
form. There's currently two output methods, SQL and walstreamer, but
there easily could be further, it's basically two functions you have
write.

There are several reasons I think the tool is useful, starting with the
fact that it makes the initial use of the feature easier. Writing a
client for CopyBoth messages wrapping 'w' style binary messages, with the
correct select() loop isn't exactly trivial. I also think it's actually
useful in "real" scenarios where you want to ship the data to a
remote system for auditing purposes.

I have two basic points here:

- Requiring a client is a short-sighted design. There's no reason we
shouldn't *support* having a client, but IMHO it shouldn't be the only
way to use the feature.

- Suppose that you use pg_receivellog (or whatever we decide to call
it) to suck down logical replication messages. What exactly are you
going to do with that data once you've got it? In the case of
pg_receivexlog it's quite obvious what you will do with the received
files: you'll store them in archive of some kind and maybe eventually
use them for archive recovery, streaming replication, or PITR. But
the answer here is a lot less obvious, at least to me.

For example, for replication, I'd think you might want the
plugin to connect to a remote database and directly shove the data in;

That sounds like a bad idea to me. If you pull the data from the remote
side, you get the data in a streaming fashion and the latency sensitive
part of issuing statements to your local database is done locally.
Doing things synchronously like that also makes it way harder to use
synchronous_commit = off on the remote side, which is a tremendous
efficiency win.

This sounds like the voice of experience talking, so I won't argue too
much, but I don't think it's central to my point. And anyhow, even if
it is a bad idea, that doesn't mean someone won't want to do it. :-)

If somebody needs something like this, e.g. because they want to
replicate into hundreds of shards depending on some key or such, the
question I don't know is how to actually initiate the
streaming. Somebody would need to start the logical decoding.

Sounds like a job for a background worker. It would be pretty swell
if you could write a background worker that connects to a logical
replication slot and then does whatever.

for materialized views, we might like to push the changes into delta
relations within the source database.

Yes, that's not a bad usecase and I think the only thing missing to use
output plugins that way is a convenient function to tell up to where
data has been received (aka synced to disk, aka applied).

Yes. It feels to me (and I only work here) like the job of the output
plugin ought to be to put the data somewhere, and the replication code
shouldn't make too many assumptions about where it's actually going.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#26)
Re: logical changeset generation v6

On 2013-09-24 11:04:06 -0400, Robert Haas wrote:

- Requiring a client is a short-sighted design. There's no reason we
shouldn't *support* having a client, but IMHO it shouldn't be the only
way to use the feature.

There really aren't many limitations preventing you from doing anything
else.

- Suppose that you use pg_receivellog (or whatever we decide to call
it) to suck down logical replication messages. What exactly are you
going to do with that data once you've got it? In the case of
pg_receivexlog it's quite obvious what you will do with the received
files: you'll store them in archive of some kind and maybe eventually
use them for archive recovery, streaming replication, or PITR. But
the answer here is a lot less obvious, at least to me.

Well, it's not like it's going to be the only client. But it's a useful
one. I don't see this as an argument against pg_receivelogical? Most
sites don't use pg_receivexlog either.
Not having a consumer of the walsender interface included sounds like a
bad idea to me, even if it were only useful for testing. Now, you could
argue it should be in /contrib - and I wouldn't argue against that
except it shares code with the rest of src/bin/pg_basebackup.

If somebody needs something like this, e.g. because they want to
replicate into hundreds of shards depending on some key or such, the
question I don't know is how to actually initiate the
streaming. Somebody would need to start the logical decoding.

Sounds like a job for a background worker. It would be pretty swell
if you could write a background worker that connects to a logical
replication slot and then does whatever.

That's already possible. In that case you don't have to connect to a
walsender, although doing so would give you some parallelism, one
decoding the data, the other processing it ;).

There's one usecase I do not forsee decoupling from the walsender
interface this release though - synchronous logical replication. There
currently is no code changes required to make sync rep work for this,
and decoupling sync rep from walsender is too much to bite off in one
go.

for materialized views, we might like to push the changes into delta
relations within the source database.

Yes, that's not a bad usecase and I think the only thing missing to use
output plugins that way is a convenient function to tell up to where
data has been received (aka synced to disk, aka applied).

Yes. It feels to me (and I only work here) like the job of the output
plugin ought to be to put the data somewhere, and the replication code
shouldn't make too many assumptions about where it's actually going.

The output plugin just has two functions it calls to send out data,
'prepare_write' and 'write'. The callsite has to provide those
callbacks. Two are included. walsender and an SQL SRF.

Check the 'test_logical_decoding commit, it includes the SQL consumer.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#27)
Re: logical changeset generation v6

On 09/24/2013 11:21 AM, Andres Freund wrote:

Not having a consumer of the walsender interface included sounds like a
bad idea to me, even if it were only useful for testing. Now, you could
argue it should be in /contrib - and I wouldn't argue against that
except it shares code with the rest of src/bin/pg_basebackup.

+1 on pg_receivellog (or whatever better name we pick) being somewhere.
I found the pg_receivellog code very useful as an example and for
debugging/development purposes.
It isn't something that I see useful for the average user and I think
the use-cases it meets are closer to other things we usually put in /contrib

Steve

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#7)
Re: logical changeset generation v6

On 09/17/2013 10:31 AM, Andres Freund wrote:

This patch set now fails to apply because of the commit "Rename various
"freeze multixact" variables".
And I am even partially guilty for that patch...

Rebased patches attached.

While testing the logical replication changes against my WIP logical
slony I am sometimes getting error messages from the WAL sender of the form:
unexpected duplicate for tablespace X relfilenode X

The call stack is

HeapTupleSatisfiesMVCCDuringDecoding
heap_hot_search_buffer
index_fetch_heap
index_getnext
systable_getnext
RelidByRelfilenode
ReorderBufferCommit
DecodeCommit
.
.
.

I am working off something based on your version
e0acfeace6d695c229efd5d78041a1b734583431

Any ideas?

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#29)
Re: logical changeset generation v6

On 2013-09-25 11:01:44 -0400, Steve Singer wrote:

On 09/17/2013 10:31 AM, Andres Freund wrote:

This patch set now fails to apply because of the commit "Rename various
"freeze multixact" variables".
And I am even partially guilty for that patch...

Rebased patches attached.

While testing the logical replication changes against my WIP logical slony I
am sometimes getting error messages from the WAL sender of the form:
unexpected duplicate for tablespace X relfilenode X

Any chance you could provide a setup to reproduce the error?

Any ideas?

I'll look into it. Could you provide any context to what youre doing
that's being decoded?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#30)
1 attachment(s)
Re: logical changeset generation v6

On 09/25/2013 11:08 AM, Andres Freund wrote:

On 2013-09-25 11:01:44 -0400, Steve Singer wrote:

On 09/17/2013 10:31 AM, Andres Freund wrote:

This patch set now fails to apply because of the commit "Rename various
"freeze multixact" variables".
And I am even partially guilty for that patch...

Rebased patches attached.

While testing the logical replication changes against my WIP logical slony I
am sometimes getting error messages from the WAL sender of the form:
unexpected duplicate for tablespace X relfilenode X

Any chance you could provide a setup to reproduce the error?

The steps to build a setup that should reproduce this error are:

1. I had apply the attached patch on top of your logical replication
branch so my pg_decode_init would now if it was being called as part of
a INIT_REPLICATION or START_REPLICATION.
Unless I have misunderstood something you probably will want to merge
this fix in

2. Get my WIP for adding logical support to slony from:
git@github.com:ssinger/slony1-engine.git branch logical_repl
(4af1917f8418a)
(My code changes to slony are more prototype level code quality than
production code quality)

3.
cd slony1-engine
./configure --with-pgconfigdir=/usr/local/pg94wal/bin (or whatever)
make
make install

4. Grab the clustertest framework JAR from
https://github.com/clustertest/clustertest-framework and build up a
clustertest jar file

5. Create a file
slony1-engine/clustertest/conf/java.conf
that contains the path to the above JAR file as a shell variable
assignment: ie
CLUSTERTESTJAR=/home/ssinger/src/clustertest/clustertest_git/build/jar/clustertest-coordinator.jar

6.
cp clustertest/conf/disorder.properties.sample
clustertest/conf/disorder.properties

edit disorder.properites to have the proper values for your
environment. All 6 databases can point at the same postgres instance,
this test will only actually use 2 of them(so far).

7. Run the test
cd clustertest
./run_all_disorder_tests.sh

This involves having the slon connect to the walsender on the database
test1 and replicate the data into test2 (which is a different database
on the same postmaster)

If this setup seems like too much effort I can request one of the
commitfest VM's from Josh and get everything setup there for you.

Steve

Show quoted text

Any ideas?

I'll look into it. Could you provide any context to what youre doing
that's being decoded?

Greetings,

Andres Freund

Attachments:

0001-set-the-init-parameter-to-true-when-performing-an-IN.patchtext/x-patch; name=0001-set-the-init-parameter-to-true-when-performing-an-IN.patchDownload
>From 9efae980c357d3b75c6d98204ed75b8d29ed6385 Mon Sep 17 00:00:00 2001
From: Steve Singer <ssinger@ca.afilias.info>
Date: Mon, 16 Sep 2013 10:21:39 -0400
Subject: [PATCH] set the init parameter to true when performing an INIT
 REPLICATION

---
 src/backend/replication/walsender.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2187d96..1b8f289 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -743,7 +743,7 @@ InitLogicalReplication(InitLogicalReplicationCmd *cmd)
 	sendTimeLine = ThisTimeLineID;
 
 	initStringInfo(&output_message);
-	ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, false, InvalidXLogRecPtr,
+	ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, true, InvalidXLogRecPtr,
 									   NIL,	replay_read_page,
 									   WalSndPrepareWrite, WalSndWriteData);
 
-- 
1.7.10.4

#32Steve Singer
steve@ssinger.info
In reply to: Steve Singer (#31)
Re: logical changeset generation v6

On 09/25/2013 01:20 PM, Steve Singer wrote:

On 09/25/2013 11:08 AM, Andres Freund wrote:

On 2013-09-25 11:01:44 -0400, Steve Singer wrote:

On 09/17/2013 10:31 AM, Andres Freund wrote:

This patch set now fails to apply because of the commit "Rename
various
"freeze multixact" variables".
And I am even partially guilty for that patch...

Rebased patches attached.

While testing the logical replication changes against my WIP logical
slony I
am sometimes getting error messages from the WAL sender of the form:
unexpected duplicate for tablespace X relfilenode X

Any chance you could provide a setup to reproduce the error?

The steps to build a setup that should reproduce this error are:

1. I had apply the attached patch on top of your logical replication
branch so my pg_decode_init would now if it was being called as part
of a INIT_REPLICATION or START_REPLICATION.
Unless I have misunderstood something you probably will want to merge
this fix in

2. Get my WIP for adding logical support to slony from:
git@github.com:ssinger/slony1-engine.git branch logical_repl
(4af1917f8418a)
(My code changes to slony are more prototype level code quality than
production code quality)

3.
cd slony1-engine
./configure --with-pgconfigdir=/usr/local/pg94wal/bin (or whatever)
make
make install

4. Grab the clustertest framework JAR from
https://github.com/clustertest/clustertest-framework and build up a
clustertest jar file

5. Create a file
slony1-engine/clustertest/conf/java.conf
that contains the path to the above JAR file as a shell variable
assignment: ie
CLUSTERTESTJAR=/home/ssinger/src/clustertest/clustertest_git/build/jar/clustertest-coordinator.jar

6.
cp clustertest/conf/disorder.properties.sample
clustertest/conf/disorder.properties

edit disorder.properites to have the proper values for your
environment. All 6 databases can point at the same postgres instance,
this test will only actually use 2 of them(so far).

7. Run the test
cd clustertest
./run_all_disorder_tests.sh

This involves having the slon connect to the walsender on the database
test1 and replicate the data into test2 (which is a different database
on the same postmaster)

If this setup seems like too much effort I can request one of the
commitfest VM's from Josh and get everything setup there for you.

Steve

Any ideas?

I'll look into it. Could you provide any context to what youre doing
that's being decoded?

I've determined that when in this test the walsender seems to be hitting
this when it is decode the transactions that are behind the slonik
commands to add tables to replication (set add table, set add
sequence). This is before the SUBSCRIBE SET is submitted.

I've also noticed something else that is strange (but might be
unrelated). If I stop my slon process and restart it I get messages like:

WARNING: Starting logical replication from 0/a9321360
ERROR: cannot stream from 0/A9321360, minimum is 0/A9320B00

Where 0/A9321360 was sent in the last packet my slon received from the
walsender before the restart.

If force it to restart replication from 0/A9320B00 I see datarows that I
appear to have already seen before the restart.
I think this is happening when I process the data for 0/A9320B00 but
don't get the feedback message my slon was killed. Is this expected?

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#1)
12 attachment(s)
Re: logical changeset generation v6.1

Hi,

Attached you can find an updated version of the series taking in some of
the review comments (the others are queued, not ignored), including:
* split of things from the big "Introduce wal decoding via ..." patch
* fix the bug Steve notice where CreateLogicalDecodingContext was passed
the wrong is_init = false where it should have been true
* A number of smaller bugs I noticed while reviewing
* Renaming of some variables, including guaranteedlyLogged ;)
* Comment improvements in decode.c
* rename pg_receivellog to pg_recvlogical

I'll work more on the other points in the next days, so far they are
clear of other big stuff.

0001 wal_decoding: Allow walsender's to connect to a specific database
- as before

0002 wal_decoding: Log xl_running_xact's at a higher frequency than checkpoints are done
- as before

0003 wal_decoding: Add information about a tables primary key to struct RelationData
- as before

0004 wal_decoding: Add wal_level = logical and log data required for logical decoding
- splitof patch that contains the wal format changes including the
addition of a new wal_level option

0005 wal_decoding: Add option to treat additional tables as catalog tables
- Option to treat user defined table as a catalog table which means it
can be accessed during logical decoding from an output plugin

0006 wal_decoding: Introduce wal decoding via catalog timetravel
- The guts of changeset extraction, without a user interface

0007 wal_decoding: logical changeset extraction walsender interface
- splitof patch containing the walsender changes, which allow to receive
the changeset data in a streaming fashion, supporting sync rep and
such fancy things

0008 wal_decoding: Only peg the xmin horizon for catalog tables during logical decoding
- splitof optimization which reduces the pain 06 introduces by pegging
the xmin horizon to the smallest of the logical decoding slots. Now
it's pegged differently for data tables than from catalog tables

0009 wal_decoding: test_decoding: Add a simple decoding module in contrib
- Example output plugin which is also used in tests

0010 wal_decoding: pg_recvlogical: Introduce pg_receivexlog equivalent for logical changes
- renamed client for the walsender interface

0011 wal_decoding: test_logical_decoding: Add extension for easier testing of logical decoding
- SQL SRF to get data from a decoding slot, also used as a vehicle for
tests

0012 wal_decoding: design document v2.4 and snapshot building design doc v0.5

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Add-information-about-a-tables-primary-.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0005-wal_decoding-Add-option-to-treat-additional-tables-a.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
0007-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0011-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
���ER0011-wal_decoding-test_logical_decoding-Add-extension-for.patch�}kw���g�WT��4�h:rBK��I��Tb/���	4I�A�C�&��{�j<	>@I������H�����c�s�~��^�(k$Q��q����<�Ok�j��&i\?k4�<��Z\�W��;*z.�� ����(��o0���=�h�wk�^�ng����?���?G����f?������^��b�}�om��fFA�0���;Q�6�v4:���1�q?��=~����a�����'���m�:�;nX�'��y�1��n������3ha�g���+�A������g����/[[;�h
f�����Z�b����pg�Nc���]��Vo��m�~�kc,����p����kuZ�������Z��_����&�m�]�]k�	
�`�0,���4n�������q�w�1�A��(�������5�����]��^m�����;��v��37�w���0>��c�{�g|��:���+���b8����>��.�����1��N�(���6�kk/Fg�����O9���_nu��y��7�����0EQ^v��v���]�h�il�Q��=�}gtav��pP��0�����D������l�~���������.���-t�k����qm��;/.����E�i������������������~kX���L-p���|�s6�
���0�G��g�!S���9�x���0�(�����_��n����}z���_�n��(����ob���~w�}��G%�&��ta!C���YOec��/h10�Wi��S�p���}�3���af����h������p�F~���n$��kk������:����^����^Zku��{�6��"�����qZ��r.t�K�Y��,��v[�p����9�z�����G�����{�h��<z��2zy�2��x=xi�|iq��@�"�7."���7�����1]���m�n���O��q�S\��^������Y�eA3�}|�,�=��������6��`�>�����k�^�}�wr�:��.�zm����st������������^���y����7�;���{������h�##W�kv;OH�{�����/}������~1L��M���/�w�4�E��,�A�Q{X�\�)�a����p�����]�e�����;?)�>�!N��u�u0:E����������;9��M�D�������=����2����A�1��y�{��;��u���w���������� ?m��fb���2���������������c�����~�YT*"�l\���� ��C���t�����%6�#��2�W�|Yn�%��Un��?*�v������/���i�}v�9K�c�9Me���gf�/nann���F�z�pS���m��`O�%u�����A9\��]\���f� ����>K�x��K���
��J
�n�����;��eq�Vq��b������>N��a�<��^���3e@No��po�x�M����`�;�f�1�O[����h��iV����tG�)�R��g����!�C�k�����}���/o�_��_�=?vO��>q���1w�<�z�&O6��91>Y���O�n{t��������wm��~��������8�b9X�#���-���V�c�{���;�{6(o��1�Oq;�%<���1��#�>��"���q@����l�n�>���u7 X����x��Q�I.=�Y����}�w���/��tWi�J'�����,������Z����w��FA�s�>�����aZ'T�M�k��[�M�c�w�����Vgg�}�������c������{����l,�������[!g�����>	���7O��W����"���b2�qbX��L�T��dY��%_�X���$���}�/�l���	���0����nx�'5,Fi���h������A���Y��[�e�q8���C=�����la��x�_��E�:�;��}�����=���e��M7����a��7���C��A~���Q�N]�z�����w�58vp�K�0���������������?����??x�j�.|������Ku�/^��/��z�N�>W���7y�t
����������r��'�D�$�f�+m����73;�2�K�X]�K]�������p������&�����3�\i"�5��$��������.���'�������������^���V���/�����j�	������+1}��^�3/��/������Z��<��v�����@Q�X�J�����]CF������o-��v\��|�����-�o���w
���]�'3�W����g/����G��{D+��5d��*���������C2_��3���KW_��x��#qo��_�*�;;|���C��W.��?���&+L�u�<��H>:�
�����<u�����\d��U}0�_*-�Z!����w�m�+�{��7��M"z�<���G47�x� ��e%�1u�����vz�Sg��2=i�4l��f�.��mY2���:���}���~I�e�7�q���������+a��j����}:TmS]�xk�T��&c-�r��8�����\|��kw���\}��q�]���>�Z0V�����' �k�Y��&+�y-�Z3����q-�Z�G�p��}��o|-[�q�p�h���P����d
n6��9�ws��6�J�M������s�`�����-�
L6�����<+\sIt?���J����.������'^p7������zf5��u���~���A��(�"�*���#���~�;]������i��-����xSM_rY%���\h`�JH�g7�u�m�;������?����b��^���T�o
���ZU��|��%^t
>w�a����(N��y���k2��}�w�G����A��o����g;������m�&�}5�l&�ZT�Mw�G��.��!����h���3��m���Mw����������,�%_��������<���a�����&�pp�������@���������J�gX�|����/���|�nLt<���C�_���,�x�01��2/5�������Y���t����,��][���^�q���g~f�7���V�0������n_���2���UA���� /������xP����X��>]^xCf��n���#Xe��?t{������}gs�+������]��N��'�F���'��������W�8_���]pM�=�s����v:�[c�[D��*�m^������aNg������|\�s��;��@����*�_J�������{^/�4�����K�{��z����s������~a0����}W��i�3~�C��;�||m����v
�<���-�Y��K�����{����������v5_7���������3�8��DyO����u��(z�E�
�[����W���K���R�
�S�%�y�����������/��Z����M��>������n��������?�|��+����|��j�/������F�/_~���/<:y�`�������5������6�����6Zv�}��0Yz���Qf3]&���In�~��w|��z�P���9�`�s��0�Mw��^�y�p�)�r�����!��R��>)?�ZYw�{��/��g.�Y�=�&
��"��g�8�����\sU3K���������6�xeXQp�F�|�����cQ7/5%����w����d�^;��BRp�����0�YT��-�X��&{�w���n��N{����
,�o�������$���<��|���
��>����tR��{���N1���s���RV�<���s��W?_u���?)t~qG�n??/��9?�����f_�F����������OD_����7'��O�C�~1�S>��Y���u �wy�Z}}+O`���Ovv���6B�u��k���������������O��`�q��f���T���}����3 >Xv�}�<��W��K7�Nz���FR��0��q���`��6�*v�r{zFk�����]+c!l�7��8��n�i��p�����T�O~�����8��Y�
��.#�����4��5����@���x�T-�
��#D}��>�~&�#��,od�2p�'0p3���p������6�n^�������O������c��H��i��X4?����?���=��1��<Z�9?�k�<�vs����?�3T��>Y��W�;'P��W��=�S���z�{1xj���#q�8�&_>���=�je��_�cA����8����_�3���������~���y�����<��9)�=������-�Y^�h���Jt�Y��k�G#��%��*���y�2��,��2����j5W�����k�N��G�W}~u���mu�����]��V���E����^���m�9�d�����r����k�?�����������X����DQ���({�*���n��~+��n��t�
p/�T:��D8nkk�4�bx����y��&W�_x�K&����!�/x��np��\���R�1���o_����=�����c�9~m���h�8@�XM���=��-��fA�w����o��N����AA��X�`_c�������+^<�a����p�h���+���p�k[��6V����ssZ�y�UF����}�h+l���^������K���`�����^��nn���Q;�������{�lk��>�f�6�~_�r�,�H�������P�>���2��h���=Hw>��>����>�������Q�5��>�#]�Ji����O�t�O���!�O�����~�
O�z��r�2P�����<���)�2�Dx���=Eu����e�0�.��L�ud0��7�_�YE����O�o��5^�{�p>w��6��$���:�.)�'�n�����~�i������u,����/����ee}�����F��*?w8�4��=�5��5�&�����������.a��o�Y�����r�����_d������� �|��G�cgMf�������I
�'�������+���6�MW
n�fu~������O��?�:M;'s�;��sN��\�l��������VAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���|���� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �F���%� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � ���6��{������^��?�ya����x��C�����}�wx��Z�[�a��N����y�������!^������Y�a�q���0
ka=�� 
�(��$�FiT��QqGq'q5N�Z\��$H�$J�$I�I���z�U�jX��q5�V�i�V�W�4H�4J�4I�i���z���ZX�jq-�Uki�V���zP�Q=�'�j=����z�&��f��)��N���0�w����mI\V�ea]XV���Y|'�
�0J��Fi���FY�(��`�IW�8
�Z��8���%Q�`���j��aR�z�da5���QX��*����ZX���,L�"��4�$L����Z���4k'X��Z���V
k�����p��(��a=	�����u�
���	�,lR��a��Y5��0��D	Y\qEAIT� ��Z����n���a��"���e/�&P����
eQp�N�H���i����Y��]\�@��%�(I��%�(��j���x�8�B��2iT�E�zT��4�Lp�4��$J����Z���4�j��Y����D�jT��B��Q-��%�	����D�jTO�:t�E
 d�eI�U�,��Z�A�����eq$qP��4jqP�ln@���@9�3d	�A*.��p��X3��c��Gs�06�	��`D�"�Q��I@]����T��4NjqR��,�TC�..���`\i\���z\��4��b�1^��)��V��z�fq-��B&�U-�k�������Z��5��Y���^��i\�m�arT��a~qV��4�jq����s�$�&A��$�'�0�����2�
�J���b��U
��r@ f����71�o���0�S��,�B����������I���(�k�B�S�J�j-���j���j���E)�|R-I�I�%���
��c�Z5����A��Z��Z9�2������&u�<x+8�z
qa�UI�&Y-�����<$�	�j�V�Z5�W�k@�AOs�t�p�s(h@�B���,�
�J��H:j[�S9�3d�I�C���������0�>�r!-���]�a�c�eW�j�{�(�k�B���{��i��f�Z@�{�bwqQ
���^�e�z@�S��b�1^=�����p��!����Vp���V�@����z
qaA��4���Z@'J�S���p���p������N*���S�8����]/�2��iE0���
����1U�z��@��_@�����5�)l���e)��Eq�$5�#����,�t��^0J����@z�������{�bwqQ	� �<�24����i
��
�CX9�2���Z-��@@J!���kR&Rs��dz��*O��9���W���WLk�����8���9��Ph`XL�A�/�hL�Ja�Ph���(����MLp�Y����y��
e���ZV��C�yx/%t
[�w���J0�j���Ejb��,
r���)CC��/��0e@�e����)oE�7�^�^L@�J9�
�a@Z������SN�n�����b�
z��s����$ssL�B�K�,�
d����1�
�-���XU�������)7���������y$�<^��
QU=8�����`��5l!�A|�c
P%�
�
5������Wf�Tf!Qf�MfaJfGf�CfA@ft�1gF���ef���eFE��Jf������eg�|3s��9��\[fN*3w�����df���ef���d����of���Je���93�e���mbf����2�bf7����b���(�)��H�2$eL��4`�mAk���A&CI��1�c �p�AC/X�,1$b����AC$;\0�`��p��Oj'���I��\+��$I*$���H^�(���B� A������������j�P�6�������������f�L�2�h�4r�2
�fI�����h.4
�>�jLe�JZp������'I���F��C�3�Dn��q��o���4����3Lg0����5�a]"F��y���A�����6L����"�A$*:`:����(��Y�lF/M�C�b:a��<j2�bh@��+����1��)C�Gg1~ /�����>�b�� ��d�<I
tv4b&�t���-#6F"dX2="-�i���3��=�|0�`����~�TB����@� 4�]�#2��d�����#�"�=i���n���epLM��i�	�L�3��<L\1S=���y 4@C?�.
��H!��h[��/��������@�����a�A 4��<L5 �h��2�d��~�<"T'��y�f���1�����ay�<@1h�y��������SC8^4��<�*����sCc�xU�<,��<E�!��<�fh�y(Sg�Fl��h�y�����0���E�c,���9�\���fa�5�!�cb���I
S&�������9�A�D���@$���@���t�14�A�a4��<�!�}h��2�g�~�8&x3�<�����(�1�A��)C�{4��<��h�0� 7f�`A4��<�[h�y�i�XS�K�<�K�VT�<��!p�<����6�2(��j6Z���y����DL�bh���@���~��,�~��[B�~L@X3`d D�g�G����L����&�EL~ sc�4H������
� su��p4�KYIa@�~�X�0���@��~�BF4��y ��.�@���~��%4�f�1P	��"$AC?�A�A�}4Vq�z!�kh�e� D�h�!����%nfU�F��h���@�]x�4� �\h�y �CC?���|��:��<�p�����4x*`��U������|F����r�.�>2I��>�c�����a�y #BC���+fW�y ���v dh�y ~Gcv�~��jOM�"a4��<������Qc`���
� Dth�y Cc�,�y 'D��CVT�<#�eE
c�f�I#��&i4��<�*#���y Sd�
� D�����i��G��<������Ptx��<�;�1�D?��
�AH!=-����K"���S3��������2aeZj�B����t�I!�a�y 5CC?��S=��<����@Z��~�	4���0�,L@���~��k4��<��R�P�@���y ���y �����
c��<T�,���Y�!9����$�Q��<����,�������-1��LN��@b��~��{�5�m ��hLU��@F��~��T�@�T�Xve��~��)4��<,Db]��>��X����1Od��T�a�j&�L��[!�I-�1A�<,��<�#������_������9����\
� d'h�
>d�<����	 ��h�e�!��e��s��1�fu��C?��B��)/�A�x���@F��~����1��L���@���~��)���!!��hL���@��~�RafB���X�fr�~�X4��<,�<���_��8Se��<��!T
|��b6��,>���b�A��I+�i��3�g���������F3�
,�E��~�O4��<�*�<�@��Cc��3X�@
���<�P��=K�E����P�
J"d/$�>���V����
� $�< A�0<�A�l��<�`����az�Cu�y6Sn��<�k����t����2n4-0G?�i7�A�C����/��q��<�/#%|Zy GC?��8�s��<�����'���b�J(+|�\YE�	y����9��t��L���������f��Y���?�����#Y���E�Y�3j����h�J������x��E��f�9;�O2��l�l}"�b�#�g���TVY�bE��X~YX�Y5~
���Mwm3"�R$��Y�e��54���V,x ��	X�f��I�X@���(����*<-`��]V-Y�c�����	B&��H��r��Y�d���([�+�r������t��Y�d]��*[:0���p�Ew�Y$e��E-�i��x��V�Yqf%�BV���,�T*���}�Y�e��ji��,��L��+,��,��G�Eae�(���$�i�t�n~���L���2�e��t��&�H�����mL��n1�b����
S�����	�
L�3|gh���!u�U��\��Oz2�d��p��^����/�V�1�a(�0�!H�U��R>��TM&��>I��WC�+Ri��Jz���[���
e��(]$�]�]�
]I��������B�A�AQ�y���Qd�qd9Y��������t3��|�x��M�[�t�g�<i����y�x���� ��� ��0�b��� �(���A�A�a&�B �A�Uy��~�GyD���)���GyD�Gy�i���Af�,(B�A�AQ��
�y����yD�GyD�GyD�GTgY���V����A�A�A�an�,����{!���!�������jx��)b�����{��_���jg���3I����g#����M�B)�j�D�x��3T���<���tc,�B1�'<�D?�#�<b�#�<����
y���g|�y��Gy�;dQ��!���!�8��A1�a.��[�#�<b�#�<b�#���� s�,�B1�C1�C1��y�a�{+��G�8 ���!���!��0�l^*2I$��^�#�<�#	2����Y�(�ZE��������:��Yl�x��~�Gy$�Gy$�Gy� ����x@��?h�����yn��a;F?�����g�<�%���
y$�Gy$U����@	�a���7��@	��@I�G��<����r�#�<�#�<�#���
� �/�!��H ��H ��H�<p��+�'�Gy$|��H ��H �� 
�����s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>������s���\|.>��������g����N3��������v��ZV�6P}�����{~����1~�z�����w����x��C����{�������k5lu��O;�����I����x������� #p�
�^��-�x�� Bx��u	$����5h!�B�!�h
���UAUF�ZM��j���� �F�V�4���k��jP�Z���j�:�J��������^������p�e����9mzonN���� �����UaYX��ai�*��
�d��Up*(�
B%%�"�l;lf����H�,������O�'��I�����,l'6"i�3A�$x�"����~�����DI��k��9XV	�?�`�u� l�
^-�A���<�k��
A��BPadDH����9�R`�o�nFJ�Na;�f,��1�,3��&P����
�Oz��+'|"�<��	-�E�u�1,�����Ha!���mC��2)
|:#�B&�U
�-����b 1�-��Y���aU����"C��`$�Sg���X ,R4T+�@�HU0�
<�������|#R8��xF�Ja�Ph�YB`�JF��&F4���1E�#3�	c3 �lF+��O�lBn!����'�����k�� ���]\T����R�
�TC"��b�1^
?
�����0 R�2��j��U0��Z!�A�!.���[��N�& �"r��3x�*X$�$F 2��
�N��0����E\M���fC}3:uj[�S9�3d	�et����������0�S��B*��z��8xW�����-�;U��D
�<`�PC�..J����j��p���24��jUx}8}�|�|����V�*|=\=<==��Zab�Y����Ck(� 3�x�s
�6��b����.v
�Qh{1�ES�=@���I@gCm��b*4b�,1��~��s3�X3��gU�#���2�k:lxl��j���QB���x'���U�z5�@�tl�W�!v�������Z����)CC����V���u�qdt��0t�/������V�@���)�2&�,���A��k�(]vLI�����W�!���W:��B�O���w���t�4�����T`PzH+�W�"�T
�
�1c~6�>��p��X3���bH���X�<�K3�t��^0J����@z���!� ���^���]\TG�Ax�j�p{�f()�C����������e�dq
B@@J!���kR&Rs��dz��*O��9���W���WLk�����8���9��Ph`XL�A�/�hL�Ja�Ph���(����MLp�Y����y��
e���Z���|7����a�N�Bd�`K�|L��
�u-���E��0����h�!��),���VN�Ny+R,�! ���b:�T�Y�T�
�2}L�@��r�t�����M��m���;4> ���cz�^�e�T ��<O���l�m)WL����XL?��M�������$=�#���2�T���"p���{�(�k�B������ew���U�!�&���Wf�Tf!Qf�MfaJfGf�CfA@ft�1gF���ef���eFE��Jf������eg�|3s��9��\[fN*3w�����df���ef���d����of���Je���93�e���mbf����2�bf7����b� �5�(��1&M�i�^�Z�L��Y��u�lX�a��u�HX��|�j�XK`��5������g��d�2�`'�_��2�dN�|1��(����1^&93gb>�\�yL��U "�
I��A� 
�����q[EN����n���.�����N��^p@t3t&tt49M�k�1M��Ds�QP���Tc*+U��{��d�<I
tv4�,�6��(���sa�4'��e�8M�D�A?C{���3�f`�J�D���� W���[�3hV��01F��y�����Xh0V��� F�����d3zizZ�U9���&C(��<�r�(��U9e���(�,��E�{�1��U�,��A+�1$O����*�\:����#2,������������S�L>�}0����J�<#+�DVL@�y����v!�v�4"�G#���H��1�g��3��15A?�L&�201`�� �US�+f���� �h�y�E��*E
y �Dc��~�x
� ��R���-4&����)�e�BK��<j��_�,�a�A����K��<@�h�y��H��yX��*y�b���)�1o%meV
������W��~��Z�+�V|���[a�'��GBVU6��4b� �DC?������3U�rH	���,c4��TrS+��%�L����
�&)LE�p��M��<��1�@?���AV��yA�}���3�F?�4���J8M�@���~)�z���y��|��f
y �AC?�Q
c(��<��NS�<��h�y���X)`�Y���DC?����~��-��y;���iE��(7�������"�Db����`
� ����DL�bh���`	�Z�O�~��[B�~L@X3`d D�g�G����L����&�EL~x�@7y Ac��	
�A|�2�@Wy GC�����y�����!��
� ��hh�����L���A��X�a@����*B4��<D����G��	��DVx�Z�����
�!�a�$��YE��Q�*�_4��<�$�.<b�~�r.4��<��e���n>f�I�AH8���@��OdH���
a�5�1b����QSN��G&�V�b��~L���1��)�@F��~)kW����@��OH;��4��<��1�@?��N�HM�"a4��<�����:�S$��C4��<�����-�'Mv@�!@j�g�2���bd���F{G��R�J#��&i���X�*#���y Sd�
� D��T�t�d��� �xh�y ��'_�\��#�H��<�����a�c�e��X:bI�R}�cj����4�_R7SY&�LK�Z���.2)�)��@j��~��)4�z�y�IC�i	�AH$�Xd���i��h�y �FC?�1Z�O���2���@�!+�B�����ER��E��T�$Q������A�$�Q��<����,���a��[bI������
� �����
�����*�A�(����SH�?�2U4�]�����t
-�'��Xe��u,�g���~���0g����3Sd&�V�eR�~LPy��P�@���~��:4V|�wfv*�p��A�u���@v����S;8��p�n�2�>����������ZTs��1�fu����	,C��)/�A�x���@F���SZ��1��L���@���~��)���@��t� ��h�y�i/�L��0�L���@���a����b4�KYg��~��7j�p��liY|dQ��"+���V��2�g"�t�I9So��3�����a/�X4��<�x����T1����P�!>�yX�20��
��C
��,�fE�&[�C�*(������!v��� d�h�y ���?
g� �I6�AH����@�O��C����r���\
� ;Ug8y �F��p��<�v�e���!?���
�R�A0G?���?�gZy GC?��8�s��<���O XYg���PV�X���r��3�@?X:`��e&�v�������N��d!�6{@�d����^�v(�B8���>o2s(W	C��^
��6�6�_�����nz:W�=�[wG{/���������������������<Yw��r�E�����dP�[��n:�i��WZ�_�mlm�Q���{�E��i:�?��l��V0,s�N�"����
F833�8�;��
��U>�$�#�n�p���1�s��.�`�/�U���v��D���1lp����ZgnxQ��G���;�v0��o����/gW����b?r?��0�O���V#o���^?[�N�I�8��~>���'XO�{�.Z�b�}�9�/�n�AAA�W�2CAAA�x�(� � � � � � ����O�?9������T�O��'�����I����~R}?���T�O��'�����I����~R}?���T�O��'�����I����~R}?���T�O�������YQ����
�O�)3v�T�}�?X�|������������|����t���=���|O�{:������t���=���|O�{:������t���=���|O�{:������t����|�~EprJ7�����_��r�n��tg��p���s3�����}�?�gC��v�������n�~w�o�~�w{�����F�������(?���bxQ����~�y2t�Z��A����n�pX����d_�}���`��;g���9��c
'���p��^{t�����i>�kyc�z��_�[X�Ia<�I{����k?\��]�E�uv����[C�?mt;�~����������^������������>���Nq��Z��K�%GV����N�x��m�}T �^����f��)���666v�?��m���7k�����6����Z�}���h���[:��wxxp��\�~Xt�������]��y�z����`�_4[
��n
?Pg��:��~��
N��K��3��|9��*��������fm��x�_���Wb�3������zX�w{����`�����[>iuZ�'^C���
��8^���?�?��9M��w�����Y2�J�����w_�7���vp��a�
���y�Vg��x������E���Q��I����e#��Xw�}�v��U������\�o�|�o+^��{,��e�'��>��XJl���,=U������1�v�u������7o_����/����]���6����5��`p��^~��_5�u�������3���n����u�U�a��K�����2Gw)��f�oza�������(�-p+2���uvg41��^�K^k��z���e�V3Kd�����wE�B�sm�2?��<���*{1�G�i�����w�o���v�4,�"���w���8LW�V����>Gde�lAE#
������h]��O{����(�&6�Q<���E<��O��%)������:
s;������Xy�_�w��Fa������\o��m�y�f��mz7��mg$������c��@�B��X�T�g�ieY���OyRO<Yw_����dr������yx��w�D���f"nt�}�GX-���P�������j
/���<��a���
��f&��t�*����-���f����x������/��/��z�N�>���*����&���i��M�xu}��\1*G��q�O4N�iV��v[*<����u\���k]\j�G\��
�{�w_��7�l	7�
:����j:�d�3p��vm��N{�7>cfq{�����Sw�?����%�����o]�|cg�/�44�_Y�4^qQ�7y�����dm���3����/�#DS���6XZ������w�m���[�����&=t���#��G�2{����:��T<~�����,T.��q�����}�,��0()x�����Q����)k�o�m�_�Fn���K�^=[k�������f<�C�d�6^�`03��X�ly���w�;�~.>�]�����1�_��A�
��0�IM��YI�=����q��j�����[�y�����u}J9�q�(���^�D<���A��3������si�V,l�~v�^��f�s���<���3\��b��^���T2�����r���o��B��N����	�-zQ����J��-��d*�n�p���GWE����ss�������-����������E� �
��_vC�7�Lw4����y��b}�!G�k���{�c�_|��m|{x�����������������t88|�B��fwyU��a?�r��3�K��[;�*w�c7��pY��-�X7jG/�����W+�l��#%����w9C�V8��+��[e��c�?�����\����x�.���Ch��������<N������M����6���]���0����y�f��]�n�������b8��S�����[J����/u{_�J�>?���������v5�����)y
��j�}�_��������}��E�C.�o������x��5�l���R�~��>�?�k1F���UV��*������/��<���)�|�����0{�`���j2b!��gt��������_���8s�!�Md��v��0#��3�S������z	|*�rQC���1����������K�����;k������W����2��Y��"O�����������E����)/S��p��a��4+�\!����������?Y���&!�=;�p���y��s���6�V�m��;W�9����r�'~����Q�^X���K'u[�n��2���x
�|���Y��m�=<����'9w���O�W<';r��t�����i���O��I�7����=:}2=d������g��/#�M�������e�2������U��I����?�?|�3w=�����_X������HV��#��kX8����=<{��Kn�d��Iy�	+[�pK��E��M���\�f����RVb��Z���3�x�������gn��\��
����^/�\����._�Xw��������������n�����=���sR��/��o�\y���.dG�c��YKk�w	���=�Q�\U 3��*���V��}�r��cp2�T>��D<�
��������!R�\}�M/y�����@o��!+�]��Vs)/8��x�;= x�z����A*/��_��������nq����y�{��[�����������*�%�~��)���Z�OcM�*���hV�tr^&�FX�<z[�~A�Le��}!����r/���}���^�^V���-!�i�I���Gd�=��I~������&��rN����]�������������������O��)���U�����'�����23�a������0�o^���l����<���<}��#��3����2Of����<'�g��	6������Yy��-�����>�F��,wy�����a���|��}w?���?W�C^��w3��;>��G�n�t��Ut���^M��+�����|�������+>�>���}`������$�������m�����?���G	�&���#�����>�v�������
k��[[�vp/�u����LNZI��wv��g����NW���-xw���zv
3���
��x��+7*��G��G��������E�����|�cp8�4obT��������?�<q>��}���C��=�B!��C�����|����pN/��/CL��'�����������/�ao��|7E�����=|}�����"X�G����o_����=���URio���������}����{��_�z�=��������������e�q?�M�F�,�TZ����o\7�}��h�0�����f�}���1y��������Ws/?.��N�.�����O���A����3Bz���[:�?xg�ew
[������?,}�t�j#�Z~Y����}�;������Mlb�
���������xz�\x��5h���Vg�u�'��J�����T�W���?��
����G7;~\46����o�<�0�l5��L�Y��WHu�@�c�"�*���=_��C�1;��n�������|�9 ���J���R�Cu�|���t������f�w�����S�v��<��k�0�bxd��~��[��[_uGV��������(�Z1*?�?���N����G���+�=:���?�N=�0��Y/n��������������b��
�����~�y/zy=��a��i���������,��)�	�{�7��bx�5��y��~!��w�i�^��{_���ni��[_Y�g8���	��Q���Xm6�g�����7�����98�kX�Q����ob���{��A\d�g}��;��'$_eC��b8�w��!U�}���/���7��Y�om}��C����W��c������������5����S]|����Wl��a��~�_{���W���'��d��(l���\�h���>��/�I�����l1�����q�����q�$# �G���e�Z'?y
�����XLT�R�G��2�wL��_�������nN.,�<zT���V��k6���G���'����/�N�A��p��������7o��^���z_�+�az[�����fm
���+?k�w��N�h=������@����3��*���"M�?��o_���f�{���p9���� �i�X�4��^>AdO����������K����v����N�X�c\0��y1<����w-j�x�,?����O�CG��-�����7G��{~S`���0
����.G��;-0/~��/=.���3v{������I�i2�����/�_Up����Gs��Nc�������i�K�o;�m\7g�GW�a���[���/w�f����~j��1�������@�������q�������4|7�10��X�uR~�p`Z��O�w��/�L�(����9{�?��eP/n�)�`\v/)����v�	��o��-/�G�>�Z�!{~E���s�c�i�/J�>b�������6�7��6�V����An�zR����&�g:��d���t�=�g6cK[��+��T����S�c�4�v�u�������j
���?����C��������+�?o<s��T-����D�D����n��QF#o*�����Sy���x�^W�
�4�n�M9���zE�Y)_������l���T����������,�\�Lh=*�HO.��W�b��aB�e�o��#c`�E����9��nf�Zx�b�gs�����i��&����p�����>��e��F�M5h�w�J���]���t?E��SQ7� Q�)��|�����z�^&o�n��Al��G��T&��b��
>�F�$.�m0��7��O��B��\�7��L���M#�����������M�<���U����J���0���)G�\����Sy����|]�45����Z3���d����9-#���G;�Dm������
�r��+�o�����V{�zn��<F�����G+���rK�n}�o�i�/�����p����_�z��i�	��_BI,t�1����u(7a;����`�kH�up������p�����:�������t��Q��4���I��y�q:8�=/^�'��Zz���Y�c��~��SY��2��I�q,Z�eg��ngk��b����z�V�?Z�h����`X\>��6�����������������Je"��1��Af��?��M:������&�Oe�o��j���^t�;~��*o��<��]`!����b��~x�_�7�������!Jr#���^��b��<�t������_��q����~�������Y�������/e�c��o��[�mY�,�q_��%]'�;�s�Es�:��I��O9?_����L6�s���;�(�?=������~������>{��.?��+&#\��m�Y��������L�}{|�S��Yl��r�=��m�X��"�����������&���:��|�`�z�v�+�����9|6��r�C�x���?�km���&g�p������U&!6^�{�bb��>z�
�����zf6I�|�!?���s�G]l��\�`���w����;,^\�w;�k9��~��xiks�.>�����c����l�e>�yme��J�\>W��hg�P����|��	����s���$}���x8��Q^1lo�O����m�Yu\��2���������,����s����:p	��"��>��Ky��c��?tY~^7�r��
�%����sn�`��m4���u�w3���5���8j'��U�>=��v����/�(.�v@7�i��[����vn�gY����6~X[��s���K
0012-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
#34Thom Brown
thom@linux.com
In reply to: Andres Freund (#33)
Re: logical changeset generation v6.1

On 27 September 2013 16:14, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

Attached you can find an updated version of the series taking in some of
the review comments (the others are queued, not ignored), including:
* split of things from the big "Introduce wal decoding via ..." patch
* fix the bug Steve notice where CreateLogicalDecodingContext was passed
the wrong is_init = false where it should have been true
* A number of smaller bugs I noticed while reviewing
* Renaming of some variables, including guaranteedlyLogged ;)
* Comment improvements in decode.c
* rename pg_receivellog to pg_recvlogical

I'll work more on the other points in the next days, so far they are
clear of other big stuff.

0001 wal_decoding: Allow walsender's to connect to a specific database
- as before

0002 wal_decoding: Log xl_running_xact's at a higher frequency than checkpoints are done
- as before

0003 wal_decoding: Add information about a tables primary key to struct RelationData
- as before

0004 wal_decoding: Add wal_level = logical and log data required for logical decoding
- splitof patch that contains the wal format changes including the
addition of a new wal_level option

0005 wal_decoding: Add option to treat additional tables as catalog tables
- Option to treat user defined table as a catalog table which means it
can be accessed during logical decoding from an output plugin

0006 wal_decoding: Introduce wal decoding via catalog timetravel
- The guts of changeset extraction, without a user interface

0007 wal_decoding: logical changeset extraction walsender interface
- splitof patch containing the walsender changes, which allow to receive
the changeset data in a streaming fashion, supporting sync rep and
such fancy things

0008 wal_decoding: Only peg the xmin horizon for catalog tables during logical decoding
- splitof optimization which reduces the pain 06 introduces by pegging
the xmin horizon to the smallest of the logical decoding slots. Now
it's pegged differently for data tables than from catalog tables

0009 wal_decoding: test_decoding: Add a simple decoding module in contrib
- Example output plugin which is also used in tests

0010 wal_decoding: pg_recvlogical: Introduce pg_receivexlog equivalent for logical changes
- renamed client for the walsender interface

0011 wal_decoding: test_logical_decoding: Add extension for easier testing of logical decoding
- SQL SRF to get data from a decoding slot, also used as a vehicle for
tests

0012 wal_decoding: design document v2.4 and snapshot building design doc v0.5

I'm encountering a make error:

install pg_basebackup '/home/thom/Development/psql/bin/pg_basebackup'
install pg_receivexlog '/home/thom/Development/psql/bin/pg_receivexlog'
install pg_recvlogical(X) '/home/thom/Development/psql/bin/pg_receivellog'
/bin/dash: 1: Syntax error: "(" unexpected
make[3]: *** [install] Error 2
make[3]: Leaving directory
`/home/thom/Development/postgresql/src/bin/pg_basebackup'
make[2]: *** [install-pg_basebackup-recurse] Error 2

Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Andres Freund
andres@2ndquadrant.com
In reply to: Thom Brown (#34)
1 attachment(s)
Re: logical changeset generation v6.1

On 2013-09-27 16:35:53 +0100, Thom Brown wrote:

On 27 September 2013 16:14, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

Attached you can find an updated version of the series taking in some of
the review comments (the others are queued, not ignored), including:
* split of things from the big "Introduce wal decoding via ..." patch
* fix the bug Steve notice where CreateLogicalDecodingContext was passed
the wrong is_init = false where it should have been true
* A number of smaller bugs I noticed while reviewing
* Renaming of some variables, including guaranteedlyLogged ;)
* Comment improvements in decode.c
* rename pg_receivellog to pg_recvlogical

I'll work more on the other points in the next days, so far they are
clear of other big stuff.

0001 wal_decoding: Allow walsender's to connect to a specific database
- as before

0002 wal_decoding: Log xl_running_xact's at a higher frequency than checkpoints are done
- as before

0003 wal_decoding: Add information about a tables primary key to struct RelationData
- as before

0004 wal_decoding: Add wal_level = logical and log data required for logical decoding
- splitof patch that contains the wal format changes including the
addition of a new wal_level option

0005 wal_decoding: Add option to treat additional tables as catalog tables
- Option to treat user defined table as a catalog table which means it
can be accessed during logical decoding from an output plugin

0006 wal_decoding: Introduce wal decoding via catalog timetravel
- The guts of changeset extraction, without a user interface

0007 wal_decoding: logical changeset extraction walsender interface
- splitof patch containing the walsender changes, which allow to receive
the changeset data in a streaming fashion, supporting sync rep and
such fancy things

0008 wal_decoding: Only peg the xmin horizon for catalog tables during logical decoding
- splitof optimization which reduces the pain 06 introduces by pegging
the xmin horizon to the smallest of the logical decoding slots. Now
it's pegged differently for data tables than from catalog tables

0009 wal_decoding: test_decoding: Add a simple decoding module in contrib
- Example output plugin which is also used in tests

0010 wal_decoding: pg_recvlogical: Introduce pg_receivexlog equivalent for logical changes
- renamed client for the walsender interface

0011 wal_decoding: test_logical_decoding: Add extension for easier testing of logical decoding
- SQL SRF to get data from a decoding slot, also used as a vehicle for
tests

0012 wal_decoding: design document v2.4 and snapshot building design doc v0.5

I'm encountering a make error:

Gah. Lastminute changes. Always the same... Updated patch attached.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0010-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
#36Steve Singer
steve@ssinger.info
In reply to: Steve Singer (#32)
Re: logical changeset generation v6

On 09/26/2013 02:47 PM, Steve Singer wrote:

I've determined that when in this test the walsender seems to be
hitting this when it is decode the transactions that are behind the
slonik commands to add tables to replication (set add table, set add
sequence). This is before the SUBSCRIBE SET is submitted.

I've also noticed something else that is strange (but might be
unrelated). If I stop my slon process and restart it I get messages
like:

WARNING: Starting logical replication from 0/a9321360
ERROR: cannot stream from 0/A9321360, minimum is 0/A9320B00

Where 0/A9321360 was sent in the last packet my slon received from the
walsender before the restart.

If force it to restart replication from 0/A9320B00 I see datarows that
I appear to have already seen before the restart.
I think this is happening when I process the data for 0/A9320B00 but
don't get the feedback message my slon was killed. Is this expected?

I've further narrowed this down to something (or the combination of)
what the _disorder_replica.altertableaddTriggers(1);
stored function does. (or @SLONYNAMESPACE@.altertableaddTriggers(int);

Which is essentially
* Get an exclusive lock on sl_config_lock
* Get an exclusive lock on the user table in question
* create a trigger (the deny access trigger)
* create a truncate trigger
* create a deny truncate trigger

I am not yet able to replicate the error by issuing the same SQL
commands from psql, but I must be missing something.

I can replicate this when just using the test_decoding plugin.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#36)
Re: logical changeset generation v6

Hi Steve,

On 2013-09-27 17:06:59 -0400, Steve Singer wrote:

I've determined that when in this test the walsender seems to be hitting
this when it is decode the transactions that are behind the slonik
commands to add tables to replication (set add table, set add sequence).
This is before the SUBSCRIBE SET is submitted.

I've also noticed something else that is strange (but might be unrelated).
If I stop my slon process and restart it I get messages like:

WARNING: Starting logical replication from 0/a9321360
ERROR: cannot stream from 0/A9321360, minimum is 0/A9320B00

Where 0/A9321360 was sent in the last packet my slon received from the
walsender before the restart.

Uh, that looks like I fumbled some comparison. Let me check.

I've further narrowed this down to something (or the combination of) what
the _disorder_replica.altertableaddTriggers(1);
stored function does. (or @SLONYNAMESPACE@.altertableaddTriggers(int);

Which is essentially
* Get an exclusive lock on sl_config_lock
* Get an exclusive lock on the user table in question
* create a trigger (the deny access trigger)
* create a truncate trigger
* create a deny truncate trigger

I am not yet able to replicate the error by issuing the same SQL commands
from psql, but I must be missing something.

I can replicate this when just using the test_decoding plugin.

Thanks. That should get me started with debugging. Unless it's possibly
fixed in the latest version, one bug fixed there might cause something
like this if the moon stands exactly right?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#35)
Re: logical changeset generation v6.1

On 09/27/2013 11:44 AM, Andres Freund wrote:

I'm encountering a make error:
Gah. Lastminute changes. Always the same... Updated patch attached.

Greetings,

Andres Freund

I'm still encountering an error in the make.

make clean
.
.make[3]: Entering directory
`/usr/local/src/postgresql/src/bin/pg_basebackup'
rm -f pg_basebackup pg_receivexlog pg_recvlogical(X) \
pg_basebackup.o pg_receivexlog.o pg_recvlogical.o \
receivelog.o streamutil.o
/bin/sh: 1: Syntax error: "(" unexpected
make[3]: *** [clean] Error 2

I had to add a quotes in to the clean commands to make it work

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#37)
Re: logical changeset generation v6

On 09/27/2013 05:18 PM, Andres Freund wrote:

Hi Steve,

On 2013-09-27 17:06:59 -0400, Steve Singer wrote:

I've determined that when in this test the walsender seems to be hitting
this when it is decode the transactions that are behind the slonik
commands to add tables to replication (set add table, set add sequence).
This is before the SUBSCRIBE SET is submitted.

I've also noticed something else that is strange (but might be unrelated).
If I stop my slon process and restart it I get messages like:

WARNING: Starting logical replication from 0/a9321360
ERROR: cannot stream from 0/A9321360, minimum is 0/A9320B00

Where 0/A9321360 was sent in the last packet my slon received from the
walsender before the restart.

Uh, that looks like I fumbled some comparison. Let me check.

I've further narrowed this down to something (or the combination of) what
the _disorder_replica.altertableaddTriggers(1);
stored function does. (or @SLONYNAMESPACE@.altertableaddTriggers(int);

Which is essentially
* Get an exclusive lock on sl_config_lock
* Get an exclusive lock on the user table in question
* create a trigger (the deny access trigger)
* create a truncate trigger
* create a deny truncate trigger

I am not yet able to replicate the error by issuing the same SQL commands
from psql, but I must be missing something.

I can replicate this when just using the test_decoding plugin.

Thanks. That should get me started with debugging. Unless it's possibly
fixed in the latest version, one bug fixed there might cause something
like this if the moon stands exactly right?

The latest version has NOT fixed the problem.

Also, I was a bit inaccurate in my previous descriptions. To clarify:

1. I sometimes am getting that 'unexpected duplicate' error
2. The 'set add table ' which triggers those functions that create and
configure triggers is actually causing the walsender to hit the
following assertion
2 0x0000000000773d47 in ExceptionalCondition (
conditionName=conditionName@entry=0x8cf400 "!(ent->cmin ==
change->tuplecid.cmin)", errorType=errorType@entry=0x7ab830
"FailedAssertion",
fileName=fileName@entry=0x8cecc3 "reorderbuffer.c",
lineNumber=lineNumber@entry=1162) at assert.c:54
#3 0x0000000000665480 in ReorderBufferBuildTupleCidHash (txn=0x1b6e610,
rb=<optimized out>) at reorderbuffer.c:1162
#4 ReorderBufferCommit (rb=0x1b6e4f8, xid=<optimized out>,
commit_lsn=3461001952, end_lsn=<optimized out>) at reorderbuffer.c:1285
#5 0x000000000065f0f7 in DecodeCommit (xid=<optimized out>,
nsubxacts=<optimized out>, sub_xids=<optimized out>, ninval_msgs=16,
msgs=0x1b637c0, buf=0x7fff54d01530, buf=0x7fff54d01530, ctx=0x1adb928,
ctx=0x1adb928) at decode.c:477

I had added an assert(false) to the code where the 'unknown duplicate'
error was logged to make spotting this easier but yesterday I didn't
double check that I was hitting the assertion I added versus this other
one. I can't yet say if this is two unrelated issues or if I'd get to
the 'unknown duplicate' message immediately after.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Steve Singer (#38)
Re: logical changeset generation v6.1

Steve Singer wrote:

I'm still encountering an error in the make.

make clean
.
.make[3]: Entering directory
`/usr/local/src/postgresql/src/bin/pg_basebackup'
rm -f pg_basebackup pg_receivexlog pg_recvlogical(X) \
pg_basebackup.o pg_receivexlog.o pg_recvlogical.o \
receivelog.o streamutil.o
/bin/sh: 1: Syntax error: "(" unexpected
make[3]: *** [clean] Error 2

I had to add a quotes in to the clean commands to make it work

The proper fix is to add a $ to the pg_recvlogical(X) in "clean" -- should be $(X)

There's another bug in the Makefile: the install target is installing
recvlogical$(X) as receivellog$(X).

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Kevin Grittner
kgrittn@ymail.com
In reply to: Andres Freund (#33)
Re: logical changeset generation v6.1

Andres Freund <andres@2ndquadrant.com> wrote:

Attached you can find an updated version of the series taking in some of
the review comments

I don't know whether this is related to the previously-reported
build problems, but when I apply each patch in turn, with make -j4
world && make check-world for each step, I die during compile of
0004.

make[4]: Entering directory `/home/kgrittn/pg/master/src/backend/access/transam'
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -I../../../../src/include -D_GNU_SOURCE -I/usr/include/libxml2   -c -o xlog.o xlog.c -MMD -MP -MF .deps/xlog.Po
xlog.c:44:33: fatal error: replication/logical.h: No such file or directory
compilation terminated.
make[4]: *** [xlog.o] Error 1

I tried maintainer-clean and a new ./configure to see if that would
get me past it; no joy.  I haven't dug further, but if this is not
a known issue I can poke around.  If it is known -- how do I get
past it?

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#1)
12 attachment(s)
Re: logical changeset generation v6.2

Hi,

The series from friday was a bit too buggy - obviously I was too
tired. So here's a new one:

* fix pg_recvlogical makefile (Thanks Steve)
* fix two commits not compiling properly without later changes (Thanks Kevin)
* keep track of commit timestamps
* fix bugs with option passing in test_logical_decoding
* actually parse option values in test_decoding instead of just using the
option name
* don't use anonymous structs in unions. That's compiler specific (msvc
and gcc) before C11 on which we can't rely. That unfortunately will
break output plugins because ReorderBufferChange need to qualify
old/new tuples now
* improve error handling/cleanup in test_logical_decoding
* some minor cleanups

Patches attached, git tree updated.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Add-information-about-a-tables-primary-.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0005-wal_decoding-Add-option-to-treat-additional-tables-a.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
���IR0006-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch�\�W����,E?�{vQQ��;gQy��3��g��4�5$l:Q��{�����$��������]/]]U]U]���M��U�V�J�R5OOO����q2��U�e����N��qZc����b��5vxx�����a9w4���Z�����k��q��g�r�N��r�<�l�+���|D��G�G�l��r���]��9��]}��a�'�r�7����%L����9k���Y�)�9���G�3����9�S��Q8��`bKf�@�I�(|@�������o	�G#��������6'�\J15��+���+��$��0c������WX��pw,$a�.���X�[#Ue�/�r�`�_<`A8#��-��.3�x�>�<�9��O=_0��OM�����c;M�����$:Kb�$9�>������9��`l�� +��L�18s��2�������Bo��;o:��"-��G>��B3}�9�%�D�[�<h_0l�xx��.�������	huZ�a�{�j���^���7�V����e"l�����C[�
�3(
�L���\�X�f�n���|1s TR&���������H'8����y��A~��C�br�tP�m��w$��R�i�h(!�O�C��y~��h�)�(l��<�xC���@�}�d,d�u!�'S�vy&�� h�����\��=90(����#wl���#t������Eq\��< ��
�*0���\!w!KW�P��������;�s����B*���O��)4��w0[�S�`���K�v����-�3{
���	�?M��f�����$�J,�	�l��q�
�������-��1?��	���Dxc1�n����������<C���\�n���`�����;�GW����n��^�nL�����B����ps��"\8�������)�g��2��!��0�kx�``g�I�r��|M�%�m�z[��Uz
��c�W8��#�=v����rL�f��DY�&�Ri"�L�����2?����R�����mZ%�r��g�F��dc��W�-�n�s����6P��
��,������������n"�>r3�H(����|O��?��
����f��TX��Vz�Hu�����	h���������p�$����5%�G'e�:������q�,E��e|�A��9>-����������
a����d�L��7��Qv/���R���y� OVU/
*�����8K���05sh:(���A����p��,4������,�Cs�W���F���,{���4S���=��.��J#�c����R����Vj�D	���(<���e��[C�vM'�D��'A�]�@��F�cZ�q�1h�uq*����|J��i��|�
0AV�A��'�'�?d$/�j�3�-o#������R�Y�����Z�<���f��gZ^-�=]a81r���`��f=K�yr<�a�"m���G���*R*�[�L��%��m�Fv5dl��a������&�Q�P������d	�Rd��9�,RH�Bt���*��lLQ����LXb$�R�uT���
��@F��U`��Z
��D�JQb~�@s-�����	�tsJAe����Z}u��&�8�xp|�7�Q��&���M8R�F�m^�������W~h�Y~l�]~l�[�r�=���1�!^z1�1^��S	
������b�R;>9�G<Q2�
:9x�����gvP�UkJ]����4a8����7'C-�|��S;l�}O8���Go
�BMe��c;;l�D/����G>��]�������Ni�u������W��ag�
4b^ ����r���;�J��A��<�a@e��O�n���.����S��y�*F���
��s��!1�1;�Q�G[�b����%�{���������jwT����,8b���-����jo�������
Mc"��+k�m`���FQ&�1��A^�)���@����h?2�J�F���mw
�R��Vh�c�����x,1�b���b���`yg���E�WJ���%S���k�{yNdL�uz:2aL�j�lt�_4������G�ttV@@��5R�}9�a6FB'�"w�r	���������<�>��&�/&�u�K(6%�[g�YfNJ�l=Jk>9:����z\(��k^�����GOk���7n��'�a|����3'P!�t\iH�����5 kgc"p��?T4����6����y�{��:lx�B��YTg�������'=_��/_�YCD��i�����y�?s�p�t���J�z*������"��TuvwU�*��}"��.���hL�.�9*S��X�:/0�8��o�����3��1Mzj�N��U����Q����5�a�H��/9|L������^������c�
WU�Av.���L�P9�^�_���=��wws;;�?�����]����Lm7�t-4"���~��)8�vgT����`ky� ����m8�( ��X��������TA�-zI��$�	��ZyM���IM���tI�=��x�P��2��C)�$��K����u����p�I�Do���-��F��8>'�Y,�������@�������r���?z���^P�HA��n>	��X�?��������'������

 g������c�$���s�k�q+�O K��b��fy�D>����W��O�������<����R�"���'j��@z0�8R�%��D���^���:��
�N�t��v���#D����������h���LU�g�H��x��w������V�3]J�/�/�����?�4{M�/�U�\��������;�����_��������u4�3�L'B�
��n6�}��.����n����<�o�V���q�1���<��M�:��s�z��d-c���!q�C�bmu,��B�e"�h�K��r�S�� v�R�^/L����zl�b��G��E\E�FW'�����L�*z�#n�"��:������W���t�����%�xPo���������8�;R���k��q���[D=a�$@_�S��&gR�`��mqF�K���A��cz	w0L���o%1�%
���K}�)	��H�!��	�����$��������m��#m������b��*�g���`��L���P���_�@"S���5t��
�Sa�/�W1����'
:������Q$��:7E5�S��\
���aG
�� ��x����ZC-}&Tm3�\GM;{�f]���#mOT��/kJ]Rm4?X0��Z���S��SXu,E��UI��Es��'�PH�
RM�����
(�e���d������p���������:�)�Vep��b���'��d5Tq��{�Qd��7�L�b�4��C�B��w������02�+�f�/b�Eq�����+*���N`��� �s) M'Z������n������Sb��,qX.#��#Q�m���HV�o�4]��8OIa(��m������v����b����e��.�>�@���������k�<�����$��>���8�}>�g9wM|.zm ��l��@t&������lq��f`�����T���DWw�`xfS&���T LXx�v�t9l������I��=?�?��Q��W�M��%K<���Y n%D{{X�ma��?���;x���?�R(�I����F8Z����uu�Ex^�H������������>�������b)���_��x������n;$��du��]t�����x���������c�&�H�x^V����5�1:3����&�H�,�Z��4J{��Dl�~YBvv.�'�>�����[8_���R����>�0=MHH��pWx�t����0�.�^		�Q���U�*n��@E��c����p���������v��&1���:$�*R�����Zp��a�
���6FQ4����T���������G��z�3�h�/��-��(	J��������Q(�Z��E�c]T���e�����T����G����K���(���h!�;O�`��7���x�����gg'��^�����{�]�f�K�7��k�g/`��������B4r���*�`NO�_ o��	[Y���~�����~��.J�;��J]M��A�[S������#��������\Y;!iHZ7���o����Sb+�0��K�*�+S�]����f���]��y�{�CIL�����^{{�c7��u��O%#eL;:������M�eTKi`��9`{f�\`4���7������������U�%��.�����%����m��5�I���fv��&����Zr�m5�*�|7t���~�T���+n���
�2u����*{�K��=���t6���CU�����(.b\rt��A��*������������S9D_,Z�oe����!��	��Q7�WJ�.��/]M��7�����t��(V��T�����y������=
"�r�8���X�V�qj"��=��)��w�@��������w<��nX��
/f��=_�����,
�/TQ�J���;T�_4w�+:��ln��kj<�K_B����������L+����k[�>��u�beAt��Hr��R7�&�Atw>U�8��wb(���c__9s�Q�|�s���F[���n*����3��T����A�B�
<=�K������Yq����n��H=��	|�FQ��������!u+[��J
K��R��IP7
��A�G
��d��Y�
�������G~qx��M�~W� ��W������ ��+�G��������~��
��sl�4}���1���x��\
��W������G0�u{�G��3~�H�{�����of.���F���}{�Jo<�k�oR��^z��7��M��u�����B���������d�:�/�a$/�|����
�C���,���j��������:tT�g��,Rb��vO����������N����\��3z��]K�C����8oF��2��)��lw�������6�������%��t��q�P%���f�r���4��6{��MK��A��Q��q�l�r�mu��������s�<#�M�Z���5��Cw�D+h�J�}�����6�ZuSB��J���z���R}�b�|��E�;��<���������H�6S�=u�)�4g����1��W-Y��%����
��\~��r�<g6]�Q����V��N��i�ge�z��N�s�<{?>��H*��+Wp�
eRj
������.I��-_��]����(Yc7m��i�sk���<��7~��#����{����������ff������P�%3vu�y=��]+�e���%��G�9W[�x^�'v��H��� �G�� �p��R��c���`�1��X����\���������f!�Y�z��;E�����~�~wq�j_����!}��t��vt{����>�����v����mi��:�w�����K��LD:R�Y
�ULIt�a4iK������"(�a>Kz)�Tm	L�t>��.�!�PB.�u�}r�����g'�%��a��J��~K��{��6�,o�o�i��-���E�=��-��{z��M��X[+%cj����5������w�y�gH����8q���m���	l/;V��
�V��RX=�}#J��� �"���0����J���C����Px�+��yr��eo
y4
+_Z2�	��v`���u�P)*]p�/_(���E�������a����E	�%�{CZ���3�V��D?N��i4��� 0t���N�YZ�+
$-�A'@I�����?���@�I� -]�T�/�w�������;Sy���wk6�����$��u:lJ�36`���i|�YB���XT}'H����v��]Z����y�|���K+���M��4����K��N~��q����f8�y�f��o�F0��������8@t��������<�o�������=z�Tqv�"���+��]0���{���Z���G
wJ�>���?L��k94��Nr�Y�e�O<X��23_�����^��[a����`h��X�ZM��3�CmxI���S'����������0I��T���
-�%N4-��n�2��:��;�_��6���^}���6V��t���/�2XPN7�t�G6j�E���Q1�?�d�-r8������������2��;W�f�������;��7�_�B&���'��/Cg��--���Opwq�%�R�i�h	�g��T�q���=#33�����M���w��n��<�������3�0��Gr����{P>(���]7[�2��>�x������ye��y{����R��qW��'�7$��S�r�%r���W7�d���:�?R�Z2�������Y�7���������������|����Z���g���"Ki~8��9�/��b-t�8H��d�N��Z�/g�����3���Q�3�@|:���F��� �2����Og��+�IW��YA[���I���c	�W����{TK��h��Ar>IF!� ��Y������o��z��9��7�cu%g�C��o�p�;�_Bv�?�';oK���)LBF��E������FSG�C.�<��{�h$H��]T���t�&��|M{�&v������i]��g9��:��+��L6nl�)���g��W��{������������V�7����d4�)��Hn��S��(�>�*��~5�H�:��-N�:b���>�8-:��������`�=���
��|�x����g�x���5�R=����)��8�!#s�sF"�>�g��[��������f��K���4����b�=4S/�@����On|8��Q�7�w�i[(�mIC��Y[4^��{�Z:���l@�	&pb�|��
{�:oL^%�����mo3���R4���n}DX������~-*��Dhk�������7���������1�]9�+�(5���pO�)�>����[~��Y���_#,K�%����k�m��� �=%H	���^�](4A@���7?����?���CA��4��]?r��Z0?����Pe��2%D]1���V�����i����|8�����(�� �q��_h����~/�lc�t����pj�������!�t�iO��=���������k�!�p��Ae����?�?���P)������(�X�y�P����g2wxu���Xoq��]B�G���9����T��\�Y�
���1��
�tR�M_1g?���I�p�K���O.���1'��r���L�[�~oI�3�0�q5Q�wr���N�QD��{��k���j(��(�����Y�{�5��'$\�k��S�	�O`���E���������?�t����'a�6�]T9Z��OAv�]�Z�A.1��xFX"7�	�h_a���Ut��W�d���#%Pg�@Dmp����X%|�?>��?m�����	<�M�zY�5�������_8��q#9������
:I�&�f8�U�%���g��=>:�����������9%���.H��B��w����~��R���K����#+���5�.�lIWXe���r�������EF�Zq��}�M���z�����:�l	�["f<��N����P���d����nk�����`�	s
�����u]+�06 ,.2�=�i�9���Tn��S��e*���!m>|y�.�(�8����k���X��0n��7���a�z���G��f���X��wfe�n��������������7(������x����Jf;����2���0��!���b���<^^J���N_����;�g���q[�:�F�5����������5���^L�{|�������g������������s�B��Z���un�5���C���7�����jQ�)���o\�&��d�2��3e�)����,(��xfH�A�*����<��H
U�L49�	�{�q�"�B<�p(�^8�HL�.��L��D�SX�7f�/�7hveE��@E������������rm�f��v��f����Y�-x��tS%bgs|���_$5V�R�IkAQk��_���E`6wY=����u�����M�7K��=Yuo]aY��V�E�HL��Q+U�f����� y�;�d�c�!0��~�^r7�������.�Y���]*�s`p��e�HZ$��I/9���aNY��h*��)�?/��`�O�k�p�>fmJbJ��go��-�Y$�0�����'|#PY�{��E=K���qO`6u�����_�dS�~�H��A�Y��"}9.kJ��I�	@�-��c"^������ 3��3�>]E!vDV��w���;�o�jNL����
���0�s��=`������.1���HAdb��M;HZ�j��_x�Y�`�]zLY�,��Uj)�Es���%OD��k����#h �Y�#��q�"�#8����^S�49��NV���+X%��f����)���H��27w��{����e�����::�~�=����G���B�g�9������&q-�@����E�D�_��}q��.dDM\�X�oS�~�L�<���'�0������w���h��V�7���TX���t�7{:����P����3���D`�������[�JN7��7R$�Z�ne�
��w�AK�m�
������_&���$��S��N�>�0��^\q^�8'G��qqx
Y���.�z����|������h��bO,#B�P�4���Y�wd�2�*c��F��V����.g2�F�+�q��a�.��@�� ���sA�n>�.�N�VP��M.�1�y�Q`����	��a��h�	�`�l���Q�?�����ps��b ����,Mb�G|�B��y���q�q�����)6�:��(J���~����+�!~�+@/�����U��SJ6�}���)U���<'5_o
.@��n�[�e�_���8c�NF��#�X
70��u���<o�=�2�f�B"�4V ������_��y?K@q�<l����K�_�6�l��l�����I��@�?
!��J]2Z�(L�
�P���S��� ������3��
J���AD�G���&���>P5G�Ss
L���'��,��������!���C�L�R�.��77��s�eT8�_@���-2�"AO��� #9�[7��x�E���k9N���2>!�#�,s���!�!|�,��]<�����;�a:�*W��>�hdUO3��jZj*���28k	aGW�k����(Q&;l��H�������H0fM�dD��%�$����$n/O�'~����I��d�n7�����tPd���P��Y�U��Q���t1��u)�`��=�N��P������ZwwQ���L��V��FyP�������1�����D��h�R�����qa��A;1Mw���z1�����
��L�!�vt��-*��UY�0���]=2�Y���K�����b�S���a�q��L^���b��z�{n �@?}��Y��"k�|�l����\��� ���&ct��y8���&���Wk���V��%�/�D�%3t3�L�J���l���ZS�$�����:fe�j�fT�+qw��V��[F����2����XI�WQl���yik�
���p����3���j������a��H�'	l`:9����]����=I>�
'����E\"�w$%x�������u����T��\7�2R�p�������$��|?W?����OE�����,B
F��=Ws9����������n���_B-�Z��`����6�~f-��Zc	{5�ey�*����S
�����S�o:�a�I^
���O����\�#��;&:���_���m������������0�6w�i��J�7���%*zB��	�'�*�=�T�x��/��Iuwa�;�iXn3:����3���py.��e?����o������l��_:�����(=�|)���lB����G�S&���@J�����f�T�:��\�o}F�D�.�����J���8�f%����V�m���k�V�B�a<�_X��k�)�_�-4��F�bP+��CS�������\Qbj"h�x�`����2��L������(��.F#O�����x��J�����s���^���Y�GEs���1�H�Ar=����g��_T��[k\�� �@��t\U����l��f��F),<M�����3?��F�k�M.���^#x�rmP�V!��/�%������F��A"J��6�I�t��4 B
�2�;XQ�@�����s�nx���0"�Q����Q�+�Q�t�]��"���^jo�zQx|�j'#^�(m���4B������odF��eV/�L-'*O6v�"S��6�Sk�zK�_�����aXh�5g!����p]����L���P��t-O����?�`	W��$T^����)��d������q��jb-{NW��9ci'��
l�g&����\���������p���$w���u=T�!������mi�c����&��J��~����fO*���Y��E6�&
�q������Lfp���0x����b����t�N@k��0��V��Q�q��~t�A\v���������Ots����������F
,`c�������&�g����Z��[+�-?X��%I�����f$
�v'����"�"��8�9��A�1gj.
�fD��x��o�eS}M�D!3.E]��I?1H
M/0�a�D��L�ry�sC������RM�tH�08ec�U|�d����'T$d����)���y��/ ����n�3h5����1I�b2:j�G.�O�>!�I�T��������������F�9��{���<~�����7����G��z��������|���������s���^$f/��������fOb��q�t�x*f���\b�`Q551����xS���ks��rE��!��;��3P[WD���e	�{��3!�H�Y�4Rfm����O��`��6�[������.��
�(�}
�TNN��C�b�����F����r'z}�v�s6�P��h���:����cx�!'a�D4
����z�]�����;�y^|q��
,����A�����~���G������s6�E��	�9������'���T�ab���)�	��
����?W���WO���te�"���]�Nj��4���W��>,��
)�3jQ�_��w��;��������U�FH���)�&8
j�3�b1]+��2Q��E��u���>�M<��Go�~8�`����.�����W��Zj�� Iv	��!�l�q�v��JU^K+^�W5c�qT����H7����vT���P�������mVc9���c�3A�a
L���f���=`�]��'@[^P�N\�c������4�y!��M��5�ORFO���Dw�p5�i�	�Wco
�����U�*���v��������������J����X��������?a�2��t�xv���o�����V��
��B���E4�=;���s������bn��c���� :p�Y:%�{�^7h0�������C�R=6/DJf��4�T���j��Bq�
�Z�zUdj�S���36���Xqt��<1F�#C������,�
d�E^����u�h�I���������"D�+���m5������}t����=;�R�@(B_�xw�5A`�8/�l���SD�%��o�M�����w#�#���y���Y
��;��j}�|�����L�!����yr����0�uW������"�xe���;�U�����j����e�y�:�0����R9�Rm��\���C�V�wA49,����=1Le��\�y�6s������<z���,�~����j����M�H�,,��g�g
�e;���2�-~8��sp����U"��F����h��A��y��z[��qp��pV������G(��{�D��N�T�'��
n!=��B�D3 RX@��#4muyp�;P�P!MY/��F���������!(�#����A'���\-���}V�A�s��@b6�h�0�C���&eac���I��cL/{1`�i����(���k�Q�=�I]�GpW���Hf�R���0T����=hip��w�gd�Kb6�p�z��#����x�ZH�Ud;���?��K����cw6Ex0V�a}F����S7���9k'^z[����C���4�\��r����
��w�v��Y�����������R�s�X��bw9�w��'�.�
�a�P����^F�	�n������j��B�/��s
��>��F����n�9��"�3�l]
02Zd\��W,�7�����1Q�l)r*_�q7��D���T�
��
��B�;W��b�����)��g��p_��/�s9<a�@��l�/���c�(\E)x��q��y���?0�-r#��Eg��80`��+�L#�s�i��ai�����)T��W�� m{5!#D6�>�,�|J��0@�����M���eF�E�#�'�4r���wpJ��5���|$&@LE�j�o�z�1�/�����L-��Jw��
�m8�7�HD�?�����,�>�>8�����|T[^6��K�����V��$���M�N��}n��+C%N����D�&_N+���4Jbj[m�)���'����R6�}�F�VU�9��/f��_�VR��)�^w���
z@��*G�7�~v����^)��n������������;��6�tj������������0�\t}�zc���Q2�y��(�D"�%sHNv�vM����M��xi49���D��z������|#��O���9GK<�D������O)+9��G����~��v��e�h67���C��]���^�3O������C��&
F�D?N��'�O���o�$NO��!��"���n�J��a�P�Qp����-]:�Mgj�i#����l�g�j��Y�����X��6q	|&yG��E�8��e����������^��LJ������,\t^W�5^������2�F�����g�R��u����"���
8�<��\���-�n����y�D���i7����"��S��6�����c����?�������
Ze���5�*Pz�����*�?�N�<��.�Se���<�.��d
�vO��`���m1�uu>�x���;���������2L�cx~�<�~C�
�������o������������� ��pv
_�o�70�sx��_�����<��5l��/��/������h��~Y�s9���hPu~+�2!
�P�`N��o�vgR�g�����#�'�<��yRH��g����7�D�4J����
J��|z0�����A�'�:�o�����8������;m�������`n}�����I	��:p�pA�W8r������������$r`>���i�uy3-��AQ������lEu�Db��'.�%=��=�G=�.A�FO
5�y5���~�o!����o��)���X�
R(�iB��K����D�S�
�[c
�m�h�?�Vo�@l}���5N��~Ivd�����k������U:�\��T�`����bl��_n����7c��>9��%9�Yo��v5'�p����������B	��n��\��B(��`W�@�p���G�R���y�X��3�H{����9�Z��x���"A�#��y�T	��z�<�+I�N���F�D�}��ow�'���FQ[U���y$H���S�8��kq��V�����H �:�]{{�(�������P�e���(�Da�E+&^�dA�eby�D#V���	7w��B}���
����v��I���
�����
��������-=,��
)n4"���N4����L}m=x�����
�Y�C��&���H���/^3���GZ�����'>��G]V�DdG�$��i���k��]8��(PK�gSMc/
TNG��r����V?V�S���r��PsuZE��*}�y���V���3s��mga����:|�&��2�������H��������b$&t�������}�) ��G7V�X������=����d�pa��bT����[�@�x2N�'D}5������ �����Z�%1n 
���d�7��A�{��E:JE����![Ry=M�������A93c�}Mk��A7��l0m�U���)(�p��&v�"ZV��$mU�v�\d�Ml����������W4�|�q0�Nl \+c��p������A�,>�^_�"��A��0:�j���se��(����w��QQA-�5��W1�P��R��7�m�c>M����;������;JRJ���f��(&��q�z#��:��n�
4�$�(^X�Pl�z�����`�P���!���5x&�x�������*�_����mT���xC�b�*�a�Q��������X��,�����9Df&u�U�Y���������4�j"=�<��b��3V�����"(�X����|?�Wf%h���������n�R��G_���y���l�:9��A<��I�h�o�$����G�1�NF�e�����4������i~�=�'d����D���?�K_����e��)���K����>�eci�W;��;q$K+-�!&K;����g�����7;������cj�������'lCR��Q����#�5����	�8��Z��)��$��Q�����a���A
��B�i����X�K��=��Mw~��*'����"���f�M4�V��1��bF1��IT��V����\�*�5q:hT
>njtz9�R?����S�hX^�R��������-�J�^�D�IP=�tn����&0�Gpr�Ps�_��A�3w	�v�V��o�"�����}��1vo��v�	���%�
Kw�3����%�M8$ �8���?<a����~�l������dB��8q����&Y�/p�
�O�tjc�JpwY�h��w��^k*�RU��P����&|���xq���>cP��LA-F��w�����
V��2!��&�T�WX�+������YK�����v�
��
P�5�I��1Y$<�tv�����=�Dzv�s�����bh�����o���*�"������}�x��Y��������9E�=���5���N�����
�Xm
��	��|����F��`���5�~� �S�z�5�����(�]H��+�SJ|��g����wQ5
tZ��$�0�������Gd�j���=C�!��^���e�H�2��S�a3\�ibUbQlO�M���t�D����g���)aY�R9p�K��
4`p1�x�+�
T �
|��z!��A~OG�e�!�S�VQ���/����1�S.���m��t�]
l��q�#m����1�:�2H����o[�1� '�y $Z�4�����^9�Z%�z�j����.uNK�����y��i4��:�3S��]�?S"Ar��!���������R���=�r&�����A�&�`���xe�*�Z��2���� >@�jp�������z~��y/;����^�!�(^�E�B?�����zzI��8=��R<ep���a��tXXW�nA��������~��@��;
-pW��������=g&s#��]d�g��c�u3h��;r�L8$�-�����zR�&���i�g��&����=�znG�+>��Yn]�
��%��k����	���T�NxL�n���5�����#�hk���n�a|5�;%������<���E�d�4w����c`w�,y��M��?/�8u�m����SV`@�'���������%��w�Ht��4f��A�������]#�������xF<�i����{���T�����z����T��stIXQ(m7�6�%��$5k=!�&Y��z��Eh��pU0�X�a����?������c��<^f��"�8r
�����%t�P-T��	>G��W�@��m%�-#8h����r����A�@����M�QnRt�lX�j�0��c*�%�ap1������1\������G��4]:�����)l.��d��O{{�
��������H��'���G��iD�f�u��c�����b%7�N'oa�M���]r�����a�_C:>�+�5�B�n��I�wC�i��G���t����V�(#'	��)E�!:+��A*��yUZ�`v���?�o��X#�KZ�A��fKm�������|�
~w�$�����eEk���'�k��s�s�H�(�]'�������C
���I���LwCY$iT��v���4��r��B�(�t�TBIA��;��uk��?9F����[�JzXC���>R���^�I_�F��yqP�����j�_��|k��|�PRM'}�j;b8?���Xd�,�1��3��B$�a�ilal\���'���Qn������R/� Kkg����8�����a���*���H��#O��\r�������a[�b����f
%����k�� r�<@f*�U��g+?�>�yE���4�oe��E��)p{��d���$#��|.~hQ���}$,�|�R�f��._��*��IT�D��w:~i�q��k�=x����6��f��h
�����)��}[��.t,����I�1���~�IFk*�`S3o1�7����� k���5���:������{����������b�r�J>x��iFr4���zFBv�X��sR�Q1��RqE-�r8����}wQ�����d"z��U�g��)�7��$||��
�0�����w
�w��x�����������5�#�S�������:��P�9����\#����VUG���NI���M�����+��!u�^�>���[��'�[����g����o�g��K��t�������s��}�;����s
R�p�W\����?zX+����|�J�H����uU-�.4��R��e��������/���.B-�*T�������#����������2bi����1���/(�aX_��j�r]u�Et(� *}����z�|G��~�05�9�F7M�x��d� ���T7�Mi�S����I�p�����G�a9	�2a�'DK�A�#�oV��v{��La0�>����G���(��Y��<�)����$�
)C����
iJQ�2��Ws����h����������u���z�ovhj+���XK��$�����2Q�Lu�M� ���Z��5���yt�Pp�E�����>��d����d29����i�qf������'�w����_:�-����krv�P�����=�85$�$��a�f#�#���|�\��>�f7��z�^i0��,���L�u���dM^k���<����I,�3��a
�>.�
n�b7-P���3p�p���6H'�?���N)m����X�%�*k�.*�2�T7�����$�da*g�4E��'�X�����m�M�D���E��\�����L;�`�nV������{f��f�x��$YY��Cl�m�|��x
�j$Q���4z���3`Zk>��I������f:�����2�z2E������|�BI�5=���a���"I����sE"O���y����g/n����Ez������W?wb�\�<�I���\����H��d�g�,��p�&���Xu	s�I�j�_��F��-�4�����+�3������|<��1�A����(O�{	�:VY����z����
�\W�y+1��f�$�������A�9`�����������2��
�4O��F�a���b��u�3� ��^Th���4�������FSxF:s�taN�a��f\���p&��W�/(N1��.��T2������H�v'��n`8�i�5b�6q{���s�����@�+G��fR�m�)���	��.�f�1���e�7����LG����Ck�&�����})V���7��2�)W������I��$j~x\2��y��>��:��]p
��2������t�/D����@��uB|���L�=���(���RO�%�q��~�@�=�N<��W�Ly����	!b����!A�8��(6f���4��h-���#�a
;"�wo���s��
&���b{�	Ii]p��v|W�%��>O�*4���E�������H�}�x��L����^������)[G�-���)��
��
�:5RS�c��3�
F��������&�2M�k,]c��>�`5�W����y�h��t5��Y�31���=@\�.���S������4{cr��D������`�8�5*"H^�U��V��9����Ah$�9
e]P*�2��G������QmS��5�Nz}�5�: �k��`��cPn�;����bW�.y ��<�t4����2���p�0��Vs@�\@����p��eY�6����|��v6���M�����-!������b��Kd���d)7�!?����AbO�yJ_w��
���4�<��SuT��x�V��������e2����u�u8��_���X�6�p�����tjT8	�y1h�a��%��vv1����~�~	�C��U_�:�m�'[TnR���]�/�����jy54�����ay�R��1��H[_�s�
����7x������..�n��	�L���zyq]&�;8:�?=��p��s������O���Td4|t�
�������C9K���5<0h��B5���!�#�jp}��j��hg�t���
���R1Q����4`YX�����?�A{����{�o
��*]�}�ie"i��7��N�������u,�y���_������l=4��&4J���#L
��0�$��cmC����{���������v����of��h���U����r����s4����w���|������������?����-?��j��x[�@������nLr�u��g+A���'��NF���ng�HD���������2�M)k{9k�����dl_:	�,��T_�F�0L�����W���P��~]��
_i��Q&�j DZY�_�����w��Y��Pd�=���y�������y�Y������{�p����B�O���'���)��h��/ua*��,��u������8.����.�L��rvh���J�>�#u��JA[*����0��<t@������g�@fn�4��[�B�5��)I;#�����6^'��is~{|
�H���Y��7���^�7wNOw��|������?*�A#�mc���C��!Gv	���T2�n��F��8�8����M��s;�>��F�5"2)�&Jk�5��j��;�q�������D��r��0>�f�m���A���3����s����	�sZ��K:��rxQ�?�d��LK��D���m���2���%)�Z��m����
�%�A�c��T�:�p<F���g�
�n���0y�<�|�T	�L���%���D��"��P*�MR��A����%0��{�y=�6�������=����6>������=�O�~�<�zh�k�	4����O�0\p�&PU����c��9��	:,>��3�t&8q�~7�}�
6h{����)oG�JN���u��a}�c-/� ����o��fZ4Awg�;m�4aL�E��_��w���.���Z���B�bE+���;qdG/� �n���jM�|'�����E��H�2���i��N@�B�}3���OT{���z�?X�j�i�&���U5e3��7�g�k��`���6,Z2�/B��\Ht�/���=��gIygMrK6T����T7w�N�(a����z�!�e�j�[�������l&���3n�����q9�,�a��QdAj����7�i�?5��]}�<��
of�����Oa��
0;��%�����Q���o�����:�[�O�?�i��IO��q��J��a���������x:b��D��7��c2R4�+���s�n���?����b��t<6���
�
��}����b��[�NG����;���{�Fo�[p!�NIv����?�J���^�p�Pv��=��j�m8�-��������*�A9$�����SM=x�u�������=?\ZB�[�5��#�aM�Kk5 ��d.c'�.OoN&�dE<bT
������~:��be���f���w������v������h�b��X}�W�b�\M&k��g4�P��)�������z����e���y�svC�S���
�L[2\��+���sd��t��&��������pq�v;L���x��-&ew��Ar�&v����&P����]s4���c �O����7�����j���<n���m��
��v����T4p�/�������v����CR����io�scP��=�7�	�"��v����VV��S����c;���&��
�P���:�|�eL���������,|}����emu�p� 3tN!kEk$��&/�Q�x�����kl�oL\"�ouW�m��,�>�<���n���0�*��Yf�`+
g1��u(,Kz�E�IrQ�����S2��3^���DZ"���+h��3�N>3��d�n���{��7)�c���\ Q�/x�-�"w	)C���� <b�h�J_�y���k�|}��Q�����/�X����f�/0�c�:^H[�'#�_� 9��0���\c��&\���=���K��I��*�������]D���	�cfXB��>*5�.#�(��itE����f�P�9���_�c�S���5���;T������}�
W����1��+<�A����z���<����������u���)(� (���~���#�����N=����a�"��}u���u�o����n��ij�R��*�V���\���V��K�VX��n�E_4�����~ct[��m�?���-�D��>�A�3y�P�d�sF�8h+��&�F����9�m�oj��!�&��e�i��������ij�`�2���ZC���3��n>l�p�b�o����&l0.�a��o�y�<�*���1�*j�Xy55�z������<�@q��m��O�xZC��g�.RUA�;t.�;��)-�8�,�|����Y�k�����>t�1���Ls�~�%��I�����f>E����cq������~�Z���b��$��M���(Mf4���;.��l��s���{������6K����k���S!b�������;1�Lf��43��r�T+����I?T9�$�
*�r�TP ��Kw�W�i��e`����Ehg�~M����d��b�lnn�?��6�m��R��u����Uw��\�$��6��t$l3~�"%�+7b��0n^C���r�<����	����I_�>���:�6�_�Q�,��kN��1I}Q��@w��R�>sQ��P�Y!P��E�B��	��n�*}!f�d-��A� 2��������5�Z�R����?�H*����]F����~�����)��0-����#��'V�{�*M/�������b��c� 
�bk�����dFsv�x��Mv��|���6������"dN��B�+�c�)B�`n$��_1��o*+����.8#!v�S��H�|�@u��v��`��������a�������y��q��1X���.f������}F�3[�iI�we_��H&��L/�c��ZW�)l����m��HnP24q79�jr^�Q�Y�eH���O�������L&������������]6��%��k� Z�:!\asJ�M��b�����UP
��W���8���������?[�����F����
�]�S�>�1��DG���H��P�0�T��9��_�K�	s�~��4�����hi�������(���-���q����pX�e(w�����w���������l�^(M��c���4��+��&}y24�
S�9���t���\�s�Y�xp"o�j������e���Q�#qY������w?:d��4b:(�d����}�a��q8�r�%���F�<��&8M���8-�?8Z'���/M�7L��b�m8.5�V&r�
w,o5"���[�B@��jd������J7.�"�0��G@��^�q�K~~������u���?'�Wj���h�4����>2�br����D��-w**���D���i�����g�Pe��z��M�jXuA�m�D��`x��	jf��_IR����z�K�9	0��#�������L�Sv���]��qB^���|����sjGj�	��*��*A�{���!K�W��h������!K���������#��T�UK���#0��b4��y����\���P�f2��KO��G� ��3�@J�=n��(� "M+�5)T��]�
�&�%��I���������W0[>�~���3�nx��/�\��|�poab��H9v
|-\(~+K�,=��
�����kY���1���i�=���)j��B��J���E��XKJ�
��Y��l���.���9(�4[�_��J?j�����WIW�
�5cRd	|��%���+1�,���sp�����6�V���2B�e@�7���&F]��H�X��,NS�eF�!,����9_��q{.�� Z�u<U�\��v���S�m�`E��/���s��"�q���$'�����L�Uvu5&�t�I]+9�g������(a���C��(5jYOW'��]����*�����������M�_����Lu[��A��~f��\�/�%j08��B�2h[o��3A9	TA�|���
�d��������
��BS���0������^1�/)E��JPf�d���U�`�aP}�)���?�e��(�`���9X�;������v�����(/)P�!Wk�xf~��s#�&i=�_�Y�11]�xl$i�d������.Ce{P4S�m8���3�
�-���=����hs�����yTX�����v��-��)�zYn���-T����5O��k��0:O@�L[�'S��&|��9�h��A?���������
<�
��E��DfQ��9Y���J:�n�mM��2I�C�����h�����`�n4�T,�&�@^%����!��-�*�Z#�w�>�k)��^!���w�������wo{V1
���T�oY2k��a��qv%�J5.�E�)xs<,�>}�)����c�3��pH�$��D�x��`xF%&�G�B6�T�L��U#q�-����?ji.��s+-�G6x�F y0�w{]M����uV�B��Q�9�=�%��&�v�G�',��T�����>�S�dA�c��4:mS�;�=�o+���%@�a�C�L,<QS����EP�s����xB�5J|H��}H��XP��^���G�M�BsB�&�y�
Q��n@V�f/=a�C0b�e�s�17�_D�c��K�kG)��|��F	G��#������x������%�(�x���mL��2-�;�~5����U�j�������������
�bK.}��MCi5���^L1>���Aj���Y��n������[��2�U�B�<��eL5��6 �9h�;��=�p�/H���"�	�[)]}�B�
��J����};�#��}��K��5���A�@�b%��XW2�ee��R��B,� ����d�������Sf^��\M0S���_�����7xI���V�����:a���L��h�H2�8�����QU��+�E�����S�������~)���k@j�p����e����w���y{��dq��~��k`@�&��S�nR��:�)^#U�K�k����M��G��J_���{X�D*�k��x���YY�������"=w��Q�s�n��+\t>�.#f'p�O	�b���i��)� k��.Sw�Q_%�3������R���5OB����L�8#��	nc`����
�9<j���U���_��W�����8,��C��=�hX|���j����$�J�k><~�6��f0�^��`q\����D1��-��h���k��� �t�^Q��X������am��@�z��D�[v�T��$��y�vI`���l���1�4��g&"���>��H�]���c�V|�4���)f��3��`1�/�n�����5��Z��jz�kJ}���|�b�P��lH�7-
��]�.��d�� ��!����-=���.�}�<�B����N������������w8O����&��AD��
s������i�l�-�a6�]}W��}�R����{�h�zc�e�zg��&�%�w�e��E�c���9
Y<h�3��l���*)U���R��y�h�.wu������)��
�?K�kg���z��]L��Eg��p�2R�������.I��N�Q^�z�����C*_*����d��<hS"�����o���(�z����'{����m'�:(n_�|&K�.�'����jX�qD���h.�I�b��zFf�W��T2�qmq��#����;he	}�;(2
w@3 ��W<�[x����[E����{�)Wd�d���%����]���+�U2�'�b�"���R�2	z ��QB5��vc�!�����O����P�	9���^��-0���x'�����=P�;7=�q�
��Q9�=-Hd���*��`'=U�J56��%'�%��(���W��+�H�J��)}����i�+w������F'���(
MFE���1�	I=�nd��0SEH�4�h'>	8��p����0�����!�����p����/r��%�
p�0���q���E��U��#Ks��������
��|�9�BaA�`!am��������E�u���>C�$,��j|I�k
	}p�b�|=������>�n;;\�"�v��7�	;�ZV}3L�S�.�E�GU�����zu�����!^���}M�I�)��M�O������N�P����#CQ���TIW�WV�D+�@���N���[�=1�H��ji��~�m&���Z�(��)��\t7�Js��E�dJ�?����������7AZ-���
��QZ���HJUEX�R�4	I1E���I(������k5�k���X5���j+Q���Z	{)��To���P,�-�3�_d�zM���+�D�+�����[\����F��)����o^��]S6�R7��);o���"��H,������L�=���w���Z-�������F�th^{��
�%��]����hH�9l����J�Vf���~�$x��ct�X�v��2��������O���?������,��������5�VB+��f��x3.w����]����S{�I���X����bX%3���|m��?���H��k���/������O�N��`,��\pKB�!J������rm������&s7.TO��Z��piN\��!�o,�Ib%���m�p4��c�� Y=�|s�1fk8��Jq�k�JyR|l 5S)l�n@y�\����]"T������$�]�V�k��R��,"1����`�
�|�5s�tJ�q��w�j�X�R�'�x��f� l��yLm\�5�����5Bo�[}x��l�l�����,)pD�U�_�-�C�=�o��RZ:���[�\i[��.���j��F6��?�9|��� �>�7�����k����Y-��Y>g��P�������#v���M�"��mEDy��J:I�gT�T���T���3��P}��)q�
<�xRSP���n�����"G���Zt=E#��$��f"�~��|�Lr;h�����$����E�Q��Y��\�7'tK���.6!��L����a5��B.��C�E��'6����oU*���88}V��;k���oh�������y��q��`�kC�&^���s������e����O��������(*-(����n	b�:�&w���t�_dY_b���+��F��Od%�������&��tU���T��!_�<�$�_9i��v�7<�M;�;c�lV���GPS��5#��+���5����W�|�
��fu��/M��M\�hI�9�8��:�>�����M�A�,�����5���k��"Y��]�A��u2;���c	�A�������V��!�(�e9���X������S/���~#5?V��3:9�q7��=�d$+��Pz�+���A_�Y�'�Pr����pA)?L�}��`q�(����@\�C.��-�=��f�J�A��'CM��b �o��L��|U����`X*O��m�����>��.i��CP��M!��[���k�!b��m51{WY���:#�2�-N�-&�+�&��$���Opz|T�������e�c	q�A�0�W���T������p�53�<y�
!�6�8<����0���i��vmy=�1�R�qJ^�EKB�ZT��"�(�����T���T����x�`g�~����a����L}'/�
�"�1L��$B~v�V>�����N �J���!�����8�g��]�V�:f,	s�c����i'\^+C�a��/��Ke&����\����U���c��U7r����D�����_�����`+����������������b�k��Q)Kd)�~9�h��#0�l�`;�";�)"v�5�
eyN�ex���G���|��	E@
Fj����c�����w�=u�J�l��%��{�y�C��1���iD���S*?�X�a6�E6����98j�9>�����USUBR����1������_�
f�`��:�I;+Z�|����g�(#/$
����Y�tUG���q�����A���6���7��W�v�@kDxv�m��� ������?��7�`���]�9�������h�Do���p�<�@$�[����7�h��P��h����7��5���lp�F����<��%���A����+�y�F��0�%��bj�fN�8��af�U������g_fc���
�yn�A�/������XU��ky_|���p��4'@7B)�"�,Y����m*l�6S�<�
��.>�C#b�������B���yZZ�{�7��y�!D�7�A�,�����<��Kq��������cu�pH�g	���e�����s&����]��q��&�L��mhNi ��DF��`	�����"j�5�����Ia*2�EWL:�.;��`��t�uY*7�_��f��GQz��g��I;!���M����|������OlXaF��sb��]H��.@��Yy�b����6o�!_[���x�����������3}��;e/�T�t
����L���]� �._�,4�S�J���~kD���Yn�9��U�f
.ioK��f��8;p��j����/lH��"�t�d|��z@��D�J�n���g$�����<�@!�l���/���K�n02������>M�$5�j"����Z5b��~���N�������L����v�GM���M1W��w{&���s�
��~n_*��t3���^Fd�)+���U������.f��?'�D��c��2��K���5�������"(�
�j�/��XC
�����!,�B�]���q1��t����o��(w��4[������Bm5��B��Cx���]��9���lqj�,5�Uq��s�qw�|��gHL����J��-��Q�t6&g���D�Gr�D�l�9��)�q��[(c���Wo��, ^hR��t��V(��^"�ng)���YOs���2L�l��PsHa�O����N�?�\���F���q����f+�!�2��b�o$Gd�f��:u�}���O���Z
����w1�n���6|����;99x0%����O�p>�9x),��*��{sk��Xxi���b���/L�+`�)�?����m�H��!��1��9����3p{�A��(��,�
�{s��?���me/b*���b�S|��;&AC5��$�e����:��#B�n-��45k�qw�$�sC9��2���R.��f'�C�0�0��H�����}7�r:!���H�\�%U��W��55�O��4.�|
����N���j>F)�E��B..Q���p�0w'��#Qg���������'C]����1���E�y���a�0�@��DKM0
�U�5�Pe�����c����s�=�_"�o}��}�[��|����3�2g��
�%��/�uK@B��nt���Nx<g���-�q����W��D����K
Oy��m�	���Dlc���0v�E	��Fl���]K�F%V��2��X��t5�l;��!Z��*S�Z�4���rGUQ�~E~�0/�?�r7��`�X������L=>�����)�B��������*e���kO����������rW�7������l���u����M��j ���fM���=��%�����������1�b[L��	��VG#���A�������qt�2��7�����Fr���u��39o��h\���(����2�����r>��'��(%b��pfD�'?'j�/�d�p'52Y[a=�������t�9�-4�����v��W��X�����g4'k�E�V��~?�}���LU5�AeJDk��<����c����1���R��z��
�z�ya��F���N���"o�1�}�x���T%�t��N[���N�;kg��u�J.���7�|ZTv��i����A�����j�����c���K��*��oN��������C9Rj��G3������z���j�oj�Ad~��y8����aj^����&Z���0���pAMZc����3]��d�E��� \��iq����~�91'��i�$�y'�
�c�����P%���v��;Qv����Q�v�~�H���F�u���7i�/)%0��XM��;�=~TrT��'h�Z+���D��.��dn}uv,*{�^��Y�!h!T4kH����
t����9���~vm?�/�@'�T�LG���f���C�C0!sb1��6���	�]Z�H����H���b$���pY}������]~����{�2�E�v?���v���:�3�i���,p@�?b|/9}���U����tp���)��Qa����b��8W���$��q2�38;BVuY^�AsW]�{Q���-�R�ctKD#���ws��+���J7�Q5pK����)88��X���`B.�Wg�/U�����l>�B�I��<�������K�X�T�Rh�M�E�{8�q����L��*��t�{<v�+	l�iI��f�/�����o�a��ZT`5�E��_{(J���y��a|���:-������E�n5��]��}����T����
��R��m�
�7���F�yED�&:{�uV[5x��Q���9	;�7Pf+;R�b��/���+w�/�7"MUB�lM��+�\�-����5`$�.l�_����z	��+�j�{iO���]���^�
�j��94�[�����Ys���-o�),	����*��>� ���|���B�� �)����������L{e,�4*��2>H�B�����U�W���,��tJ�����v� _
��{�������n�pD�*���Qy��x@���9s�l��i(�]h��t����;�1�Qz�����/��C)p�[��%�������s���\���I��X^���'[�������5�L�Hq��Vg�]y���3�mKI��YuiD�i���<�\���p������B"���{��s���}<=>:���sr�����m�>8�9����Y����"��o���`s�2��U�����d����`M����"^����t�I��m������0�N��������:<���p���Y+w�a�n�vQR�7������T:P��PR^�Ld��t4���c�=��6|[���y���LW���a�EK�ZGf��\�*#���������k�U�6G�6-���k���I6R��I���H�6+~39�_�v���E��/t/��q��?`����|�c�<�6`���)���~��V`����M%C��A����Ey�Q��^�kz���5�������DJ�5���!e�5tD��iu��
���t�VM��
BAh��Z�>�wT{�QU�c��`����w�;+<��r:���������=����-��������/���Bkn�m���oK!���2	�Y�}��W���)x���V@������_=f�	���}8�[�{��1���'�0:2���	�h n�&m�lN�J��X]����t��%�>/hql
�b�9��vm-4��5b$��Q��_z%�#��W�	��#�4���3����BuRx[����a����u����[����"`Ou�����X��WX��%�us���,�3����K���$|c����5��f[��1�3��������y�f�a���������p,��&���)wr�&����8x8��6[U���c��h�y�fa����$vG���>�����=��W�u�PVB���P)k�R�qj�b�:�&����T�dv��?��u���\F���?ld�Ii��j=�sgc`v1�����%G;/^�)���	�%��`6�,������	^�ch-���7eiF8�Hq�%/���o���	�X4<#2����5�di�������?��?�5�5�����p��Mt�*1S�x�Gm����6���i���!�����E}��H
S=�,0���n��������
�����Lk�b`�*�`�>%^�p���;����n��`���be�}����:�0��Q'�#"Q��7�S��T"�ZW�\*o�T��s�=;�K�P�3������>����D4�NsLr�D�=�$�����L'��VKU��v�5J�+��r0�5UH�f�F�8fT:")��C���oZ�2�h2��<"�����Y��9�����H�3f9���|+2�y�fV��|'�8/2��H��|TS��!
Zk_I8mcS�k�ZY]-�$�f����Vo��d�U���+����J�����V����X��A�A�m������R�Qa����9*���J1����p��s����|�HG`[��U$H���S�r��m��9�O�q���'���"4�6=Z�����y�r���r�T����KS���.���_��W*��h�ll���V,�Q�����n�~7���������d��`��Gf���1/[(�d)��c|�q�?i/B���sz$A3�
��x;m� ����:�]��#��rA����J�!=���fX\�A�J2p�p�wj4����$���ur8�������7�?�U�v��yrL7O��r�y=I�����.?h��������5r���h�� ���/V�=�M��p#�>83o�F�e���c?l�4)��7N�*��w{�g�a��5i"x���>������ Y��F��7�������^8TT-���%m��&�A�/G���t����G[�T&-4G���#�/S�l�	A�b����YQ��u���t�'��]�sJY�c����'����o.���L)���pu�w>�T�$6�H)L��� ��[\l��#�L&����I�����������x�D��f��������?�a�i����y�����o�
f��$�3��6L��(yQz�!jJ����']��%A��V�	��{Y�ZB�u�Tu�x��BE/��m�}Rd��1$U�k9���G�����Z
���I��6|�_ -�kx�2#W�H�S�V���	�l����#�m���d��������s�>��5�@�Y.-j����MYQ]+��-^���=�p�_N�D0��Y����.>��?�5�"M}N�|w���?:�8g�e-����X�����I�AM_bE:���5<�nqq�t�X��������`w���b^Q���K��%�:��b��ui��c�w�2��(���w��C��>�'�G��9C����H��zS���w��)�fZ�[k�������@������pH��x��#�l�{g�;{w|z�vF��wA�����B���t�zA���Q{@�k�|##�����?�Q��c����U
%7���i�X��D��9�zQ�������n���9���+�f�E�4�������H�f�g��C-
C���S������z��C�����0^��`>'{��'�����}�2�ug�����v�#�lTC#��e8�v�"��P������b�+{z�D�oN�C�x��qx+���
D����p��/bd�BPQ?����c��FX��f����;F��%����/�<�9� ������%b��Hj���g�3<�����]
�}(3 ],���@�;v��������B�����������c#,���Z���W�w����	�D86���[�������e�����CDY����bP��]L"��@"�����y[�F���Y�=�f�kMNYB�	�w��F0H�FB��~v_��1�Uo����&�/&hb��0��������v\�v��(IG���&k��5�:��"���Zk�YuE6��:�	�N�n��+�1��F��N3�l�����G{.�%���-���q����'&��!��O�����I����{��aw���c�Tu�6g�	��m�P`���E{4�]e��k&5S���=y'�~�l�XWlXt���.�t*��\>�1�2-[in���6-�_���[��������7@�R���Z� �8
<C�j�s�1��V�s[��������*������S7b����s�p��p��

sT��(d#�����Dm[��4$�[�j����#���^rp��kV��r��!�a&�����W������EiQ}0<�z�����U�.y'MDC��f���A�uU�e�)	�+!G6!cM�X$G"Sf�w��[|#��<UEc�U����+f�Q��a?��=����y
��Cl�b��y��6t��U�N�J.=y��8���fh{�E��{A�LJ4)&r��Y(
$�^:��@��?e"��8�Wd�%G���Z�g�X���NSo���3�`5l!U���O�sj�l�6~������
�Yqze��;o-��D>�J9�����s�@����q$�:|l�\e0f9f(c��;lp����*�z�a��0%�����8T��OJk���I���DG��`2N���;1�!����Wd�Y�]�~�+���"k�T��f��^Y����fR���f��^��QcS����9^�I�O���L���'����)q�~sN�O����T��f������G7���LF��Pc�)��3���2T�s���E��510��}���,��^�{������!�T�!�����C�P#0[�n������M�+gD�������Uw���f)t�S��]y�S�������}'&�m����8K��%l9���-h��\]*��Sr�u��L��t��Qw!�t�X�bDr����i|Z�����O�!'�G����H�R$7;�WDZt#^�^����� ]v�H8��vk	P���ec��1gI��(Z�6�WnBb�{�'��D5�����/�n>6��n(�b�D�������\�:m�1���7K�T�B+�a�x�R)&
�q���{��(��
�d��z�K�#���h�4��O�\����%���;`�T�`�:��E��c�����D�h�dP���R���s�")A\��R?��/��Bd}g�����	Pk9��k:"�D�7�����a#��,�\!&(&��y*9�Q�2�q�\X9�,�1sw�X|=���#��$(��H��	�H@�j�L��������@��x�l��Z+"qQ
2,��t���S�`@WC��d���}z�c6R�p�tt��;�&�9��W]��AZTD�L��p�B����X�<P�E�(&���U�ubT���������D��_�KRvZ�B����8�6v���R�],~�6���oP4v�=�yD���J������F��:��e�iC��i��2`VlwJ<�v{f���#����w�;�I��J�F ���]�+hSO����d��-�����}@\	��/�d7HBe�@�w��o��G��E6B��3�Z�����)�H��Y.p
I��
�v�\����]-�T�Im<����k��FE^.�nN�-�(�����%�c�0H)7�/�G3������r3+������=���N>q�xM�Y��P�t����"a�����{B�����Q����/G������8%v`|�v��#�A����4���~#��3\����H9�$F���[������F'�.fm,������5���B���t�U��\:G����Hk`
�@�F1jm�:���h��[tQnHP���_���h%��^��N��C����|���2V���l������G�����$���j(�<X�]��o��f}3y�U�z�|+����r�Z������
�6�L�=��C��v����Z��>��b%Y_�DL��!u�"�����9:>�?�B����E�O�~
�����p�Z��("!aE�\FGmF3�A$Rd
n[���y����/�G^dx��
%�
R\?������uw�>z-�%_�!�
�������V�b�����'��x�B�t'S��d��R�|����y��)�����'�u�}����a��<D����}#3��@}�f����b��g~�������V�9�!�_v���7�����OV�Rr��Gf��x��C��q��&��CAn
�p5jk��w@��Sv�]wp��ef�S]�!�Y����%����j��?��B"J��k��+�*�$`$��eS���: "|}KD?V[�h!�.��x#�R�U��������r�
��k���1���^�^+R��l���<a'�h���N!D^7��K�|���Z��������d�8�0�1�{$*�
��eC|u�3��]�r�K����������T�����^E�1��ai��53F�3������R�W#9�%��{^b>����w����==!��XOI/�<[NT#%X���\d�kD��������!	����2�]�����bS*�k�U]�H]��L��?H!����A�e��M��)�
�,��15��L���wgz�Fa����5�.+{��y/��#K��}�W_�f��4�\���5.4�E���7��*Y����jj{s'E������9�����C��n"��
��������(�<P��9�+)��B���.��Q��eY��"U����Qg�\�������[9o���N�1��N�`����jt�l�Zm$g��uh�J�%���`��xe��q���r�o��8)Q�*�B��[��x�3^M�!p+5}1�2)���>�s�jV��F�>�������%�r�b�J+��0.��$�N��W��fK[�����v�j�����CcZ���L�q�	Z�K��|�����NRPzl���|���\�7��"^�,��^�Pa<*���HbCBa�y�������cvO�,�
|�s�x�pc Q�#*4
����!��{5h�S3)&i��2M��������547���+R�
��li�8�r�S<G�7p9���L�N�y9	�6��Z����������:�%�=����mwp������!��[�����`o�������f�h_��D��������/��%+C�G��Z�����
�(4�^�c�U|>��e����{y�J�p���W��7�����_a��I�s�^�����K=G���i���D���}	�'�[l�J�M�	}�y�/Ew�)�Pp�Z��+����:U_�.�i�w�Nd!���:�r��,�-Ej�����%3�����g:��E�\`�)_��QzS���Q<N	��#J.D`�#��J��������9	���xg:;#�N���5�_���nV]���:���d��7��a�5]'�P�������J`"�q�A�A6�T�����).��o��hodr��{J~|���Y��n��0��z���2�E6�.42
��@D�;��h�nIA�hcQ!nF�
���d�(�.��k�tp[��7	�"6FhW-��T�������nS��&r�b%��$���� �,��oi�U���$~"�\'u����v����k��y��o� �������z,�o��
���+��Y�+k����t=8�`p����l���I|�$$�8���Q��t�c�n�`�j��?�0(���w����h�(��?�Ml����a&����]��b��E���R����hH�a�l��4�����G����!%�p2�M�l��X�(����
�T�[`��V�rs�1��r�d���������v��kL����)(k�^3'��A��t����|������(�*��H���Fc�o$S�`h��e;�������Ss��D��8���:@����Hx4��y�I��v��W�^4/��j
��w~�l&�������U&"Y/������<���>Y�?�'�E����6�]������L|����`7�5�a�V��d��h�nd����D�d�)+B�u����z���Izp_�����c��F����F�6e��W�N������r�@�C���Q���J�@74-��,���1�� �%0�0��{b�RY&'/��Z��m�#�{B�>M�Lk��ad�<jN�M7�5��Y,.��������N��QP�������Y��\O8��`��;)x��}2����Zq��6�*����|�P�.`�{T�p?h�\�|��qr&��6 ���������/=�b��sB�H���%9B�3xo
e)�h$�X���z���@�a�_W��#1"�+�X����*�)6�_�|}���8�)��3uW��!1�OA���/3�{8����I��a���#�}�;�!*�jaApS4��T��ar-��Gl����f��pz���
#@�h���~��k�f�N��vq�������W����9/�o_o8(N���_e�@���� ���C���������p_�aA�ji����1����l����T�3f��������~�4�������=�nv�]�2;�:+�b�(��h1������@v�����:4��rUR��W	� �
"6�i�^D����O�s)�`��W�p1E�CGQvn6Rf����@���{�mK����N*�����&�PI����Te1���m
�Y�
lN6M�,K��mt>z��p�+�"�h\�������]�?����������e��98:8�����'M��'��O-�!�R��R��-�_�
�2A����0��A$L;g���B	���
)����������\�2J:��M�[6Bkx��w+e�w�4�9�u-��2������jh���9+)��"��rt��>����U�|�+��\W+W)�:P���A��40��hc���Rh����ey9���q��{%*G�\�S�><6*�'����_����~	u�I��#O{�{�o�=xMb�1������6��I��c-�^|�a�lbID��h��<B���{������VI����\q�$�������^����g�)��^T��://mG[t#�"�l����Xo�lo\��A�����)D��jz�V����$A�A�����t\��?�fN:H>F��X��/h%����zJ�;E&Dp�e�*�����U�PY������ �e�S��'��5�1U�����1�+��B�n����b9N��w��LWs���������K�������s8��2���u��n��	<�8����p��E���b5���u5������X��4k�Po�D���K�a��gu�e�^����|��,�b�6���!���c��@�.e����O]�%'h������hcj���(��K�g��pm\�Zq9�����K����xlm������.0��������_o8���=Wq����#\_���;\;~��x�k��v���
������n/KQo�$���18
�k��n�.B_�w)~��}���,�P�m������)K
O{�����_�(���4�%����7$S�gY�~�����H��F<�ZsD`�,q:�+Wi|����:QR�R���A\�H��������?�������D�zm#�D;��H(���r�;�y�;���|��G�,4��Db�)�{��]$�P���P.,����eK�������#t`�5���;�����=�W��8e�|%�FK&f*����1�;�N���@������`���<�)�0)8:
�dY�L��Nv�����Q_
���^�g���`8��"G
������}��D7�>o�.�3t`{d�c��K�^���g��\_���]�_=�o�cOu�T�0�C�\�'BC�
����{yv�I�xY���4h�-����B���� j�yHh�7��'��(i�:G�{�z������w>E��@����6�J�h�*��/�HVU�G�=��r�<�����Bj��s���ce����'z����;�1��R���o�*�=�#r�E\�}�D��-�tb>Z�'�MR �E����������Q-������l�~]-q���������4>�+�)*����.n��������vV*��ZQ��)%��.�Yq�a[A��F��1q���o�uSlz�r��l���b0���Q=�� �#��=�h��^j�Tz���
�3dl��?M���S���l0����G�QF�]�� ���!�Q-����	�����fbN���Bc����$�49�N���oh*���=&Pc�F�x^���L,��������A��8��.�CkK�r(q���'��;Gg;���i��7�@�*S�5�9/�g�@%������+=��SVgTx��bX�1��l��]�O6L�aj�;�Dw�����F�p'�j��J���h5j�OI	���
!��P�a�1�8[\&M2�HSE�0l�V��E�>NQ#b:��be��L�KG�Cl�XA���[.�9e��I.f-4�;�fbC��
o<,I���V%'��^����"���G�qX��
%����`d�GV\�n�/�G�G����[����U ���������K4�=?���0�?nbFs	�u���pp<r� ���"�vB�}���pz�L�m����0����
�=�3�q�\�5�G��9�^P2���1<��9I�a$X��s`A;'3=�G����!�/�O�����'�;z���mK3�w������B��8n�0D�=J���%`�s��D���x]������
q�)�����^��y��������$�H&�X�������Z���:j�+����������hT E�R�����*�,����.�'��8�*���V��"�F��JNwP�^��)�>s�_U9"q��
���DR��)��.�=Y�.&��\k��
���E5t�_�x�������I�8���kD���#g�VS��=�:�����1h/�U-�t�����.G�������"�p
��g-A�-��
e���&��w�UB�|���-"�3������C�|8��H�N
,(C����t�/�Z>�F�CdA�������m��T����jb�iz���I7]��$'��������	}�O�U���!J[�k�������~���������#����0�s~
G�z�,�����BVVPC����R����#��+�Yu�����z_�N�3������-����
o7��S��p�	��/xC�n�@���z��1`oY`�#��i$;�AV�q�R��7�w��S'#�����������
1��n��������i�f]��$8��a*�l$�Y�Er���f���j��z�xTnYu�~�&f���0�t��JL�AAa����\tI!�w@R.��-�oPE��k	r���	P���[�?$&�T�KkG�y��m���k�1G�����^)%��KL��q���)2YQ�h^4�vs��$a��O�"$�=��;���-������lL&��d�)�go��^�s��������>�ZO0RAL�h)��Q��(L���b�e�T6��,�{��a��^7;��A���U�qlS�u���w��$�����j;������
t�.-^3	E��;#�Pmm1E�.Y!!�+���6���\��63�	s�@�~o�P����sr����hp����Ual����K�5���/&��goq7����H !���2�p,�~���m!V��4Xg(O(��h�G�,c\
m�\#R|+~������mS]G��*�Ua��p���d�V���{�Y���		75���R)Q�����?{��g��������k���lA�]!��Q�U,O�p���c�;?��HQA3&O��a���U�����.��<lFh�|����X��gc���}��o���XE6�J'�DY97���S�;e����&x�/3�����X+.IZ#( ��1�13� ���i�!>�X�7�������^ ~j�����a��"u1 �`:
�W5o�u%R�B=L2_2J��>##Q��O8/um�f���f�����p�w�DtO�����Nv�1�!�T0`���4C��$fD)�I�R>��3��_�g�O�Y�i,WN����w�������%��	t�/+s����0�`Y`��h����w��8��7v�&���_�>v�g�����
���[$m�7j�x��3R����ePW?{)��h0f �"�Yv<\).�Mq_��r?��]��u~�#�Qof��H��k�1U?���*��<#0D������)������[_�E1�<�;%�Q����#���[� �����Y]���7!�Z�
�
�	�K��eRbCn�6��#ta����=�]�y�EL��|'�J������H�A	=��fd�s��#�L`�5�E�4u�����k�)��n�F`���B���oB�9������oM1e���y�7����kM� `aLx�0�)�D�����8��#�[3��d
�iq}p��VG��p��}��:�G�L�G�c�5e� ����lB����u�b�Z$����7���'#�^&S���ji���N2�_R	�?C[�AY�$@3K2Ex�D��@�@5"{��#/Pm����;�~>���|��#2�v���$\����.+g�#�o0��y��?����(���{1�~:���������.
�=��q�o��~�\>N�~���I��o=�M����������z�����V5�������X���S�k���/.p�,W	R%(���r��O��0��3E���tNcUra�!��&��1+�vV��C��-q��N8�(��=A��`�v���;u��P��sY�Us�J��������Kf!� }�6Q��>qkUz.b���wJ����u�����h�`c��fk?�����
� ��3���r)w���� 2Q+a#9��>��\q���`n��N
�+A��]���-F$8������-���X=���;�����(KL����j��S��\�*@h\���)xwk���<������C{�R|i!�� DqI��u/�G�����b4�x�)Ld.��5�t"]���gE����d�%�B��nb���5���k�6w��;�vt�Q�R�n����'����X�)���S@��K�-���9�����v��X�cfx6T��K&�/?WF��A��i:`�9�N����"���M�l(��1I�X3������m��]f�Kf�C��^�J����MWpP��FK�8}��k�P��U�J ��T����L��O��!Q�b;a�+?��J����������m';��R�:}��(+��|��&�j���N�����9@�M]��vt��4;6M9�c-B%����{9%T��.�|���������<��M��qg}=��1u�6���~���!qhOT+�c_�a��8�q#���r�1��y!���v�9�!^s�d�g��W�������c�	w(�A�Q�3�\���:���I���P{�~���pw0)y/�[J��'}�@�����~���91���}���:����r(�M�=���UxL���G<�l)�"Q��'{�6p,	q"�U��(����z�o�}��W�!'9���D�9��:j����B?��V%�I����~F���+�5��.���TKw�M� ��'$
�6\A���*�/K��S�q�n1�e&�������v.��EN�Y?��c�A���t�.�����Y}	�bUcw���IA�^�`����7L�����pc!@S�l�����O%�Cp�A�'����5T�g�g1T��/
�d����L�#I	{�t������%��_N����O�G����D#��`���
Z7.g�|;�/rI��%���&��o/Do:��8W������������y���/��G�=6�����%B1����o1��o���RKD�Y�9{�|��
\K��(b�l�&d��5m��AM�������Ri��]E�j��������_��������vXj�H�5��������I^\Q'>1S?����0~���ufP�,��x���(��I�4�����`x�����=�>�,�u�u����99A��
1G��(E�^����4`���������(�!i��X�b��7�������G�*n���89:�
��0�����6vX�����e�JQVn�w��<������>�:���+�(�"��A�;o��},uS��eh#��]��)1H�e[����C}=�����'�	g��"���:�l���3�����:p�|SD��(q)U]Y���6�,yaZ
^8b�_%ZX2x�T������YtN�Tp2��lM�G���V���	k�������U���V��1���K����+�5�]o,�*�8��O+��3�J��4Z`�]s�a�?�F��Q�^7K��!�%�>�43?�������j�"Jf�����#HL�m��HUe��N�,��Ac|��U�PmH�p���
��L��Vd]�G;io���-JcP�0fn�VBvkc_9{�MT�o$
`��*���
�%�U�-�.�,%i�)�^C�0�Y!zR0����<���
��G��0h��m�A���Z��#t�cV�;�rx�Q��@���#
��G~P�7[/�%*����<�}������i��L�d1�A�POx_<3��,?���� �W�"3��A�$�%mM&=�}������9].��n�{�"�3���:P����"rR�'������=�3G�5��q�XI�e��.<�^%3�s��d�,hN��%�����^����������M�e
�co�o���_�������'�O���Be~�3�pI�X����q[�Y%^67��;j�v�|��A�1�HR�a����A N	u�.�U���{^���5����'� ����
�A�_����&��{���0m�w���]�a��9���nm5���3W3H_3�,�^��a��a92����f�c�0��K�V@������@�Y�� ���e�M����5�V�)fy�g_�m.d�F�����Q|���&��q��lIS:�	VV��QVp�O)��*�
�h���z�&
c%����\Q������g�H�D3�e?��G�(��{A�}UU���������p�6��)��SX�!Y~1��<;[��Xu��s�^�+��J�$s*��-mZ��SC�L��xc�aD
%��P5S���������d�J�a�S��cU�N�������r[B�~���R/g�Z�?]�k\-�m�v�X�b�����N"r�UiL�:N�� �Y�_+V�����	P��8����S/uRD������46�a''#'��3]��Fv���b���F�����|LE����V��f%���������p#R"���&����o �_��e���(�TS�o�P
\��������2X����z�^X���2FZ���&0�\J���?��5��p�>x�A����i�?2����QW���NJ���	���<�n�~P��������H�9����~�	�;����ak��2�a�� �p�FvqCP�}B����J���]����Jz�U���H�Tx
��*m�@�Y6���s�9� ���P~��3�4Y�70�Y�H��;$z�C�t�^O�=&!E9��� �J��0�/Vtf�L�J]��q����1.��|8�8���k��t�C���$_
OF��k"�g���!��1���JiQ��x�����&h8�rk��64-��_5���.��1D�����@�qg�S9h�x@;�.c5�u���u���*h���g`��a�Ipbl���E6ek�i�����I�Ymm?�
�AU^N5��-�~9����q�~�h9�(�|��&�����Z���]��$4����;i<����}�d��td����2�`p]y�n�z�{�����]������l���QJf���w�������k�����	�5����~�g�u-�,�sd<���HElF���N��8�;�vd}r��<���E*L��/!V�*'��������#9�7s��,WL7��*����X�q����/x��4�x�d�����A_�*@����z'..���?���
:�|�`�Z���N}I����:���Z�$'�i��z�n��-^'7M�����*%�,�z���"��P�@Z���ffkZ)��_��
�Tp����z�����zwi_u���i���aT�pZT)=�KU�#��z/�c���]�����z��)!�&�KV��(�n�"y�Uwe�������2��/a�'[6��4������`UJ���>�(j�ma�#�a����;��~���ZG���Z�2���[�d�x�����9h|��"\'�u9~����E��G���Y{�r�����(Yj:\����}��!�s8M4R���\�������������/�\L������T?j=P�jr\k0T�I�����\���~2������<��5�����������J�S�?�����o��S���a#&�20��2om��xW6��r�{>)0	���*�#JJ%*.De]�z���?��hJ�������u����O��I<�V�>_�F{��_H�.�~���n�����������Cg8���A����
w���P*9*�Q9��V����6�KI[T�e����	l5�A���N��}��+z�q�����T�
�������s,�}��\l/���y�E��������-�5a��>tr��|lj?0�����Y1�W%����e))"/E�� GPx����!>Q����5�s�L�m,�{C�����O'oOv����~��;V���)�?��I�O�3L�\�3�������"2�|�j�,x�JW��rm���7���������2);G�P,}��B'�Nq<4�����IB/c<J��{����������u��*��F?E�]���0*.L!G�A2E^D���#�g��$$�n���#P����h���E��+6��o�:�$�p�����Z�,�� ��9zB��I1/���[(ne��`�T�Z�;5<8�_|��r� �m���~�S�rn�X|.�>�����sb���"��CR��x�\c��,�h�5h�+.k+,��=3N���	�I�"H:�zu�GF���Yc��]������nT\���X
8���*'��.i����q���"`�$}Z��#${�%H_�%4r2�H��P"��*�m|s	G�t���x9�
�\���V�F��Q�f����t9�D����
Xz?\�����h��I�I����3�f�*��������)'�0��
`E�a7��JP^{9�>����TG)��&�S�7�s��X��X����]�~���x�n.���P3���JH_Y�+��.�\�J�8V������CG2�H�Z����d��z���3�zC�5�p�d�h��J�~��Mw'
h97S�c5TNJ�z���?��#4u���,���P"A;6Y��B�D�iA$�
b�6�S�R�&���������W�G�,'�w�m�a.�>�C��l��mT�
k��8�%��_�F������Y|:e�m�����E�YE>,Cc
���~}CW�O��"��3��j�6���H�-���D"'bq��>�@LO�������v��/�@l��8�u��a�����"�CC�������j"Qb^@��ocq�Kt*05EB����o��������c��k�������"&�o��KmD
}6��~�]�C0��<�L#�;��{M�������fj��qs�t�<�~���{H�|<=>:��z�����h��o�#g���g��5��~�ISnY���9����q��+�O��^�H�e5�8��.�����T�����_������)���,�X�M^�k��V5-�<5����-�s�����,9����F1��X1x1^u)���l/�T��)\O(�0�Q���b�������d/
S[%0�tH�(6\���3�Z�0wW��@��nC��i�
�Z�Cs�r������F�>��iSa�G�Kv�r��=���=�]��
���g��7A�DR�I�(��.6k2�CB��&RS
jvi�B�����`��d
Ov�v�a
E��."M�WO��5��9�e����h/�,q���%?O^	z���,�[B�~��q���]s���` �S��tn&\�@��]K?��H��L�j��T?-?(����l6{���Q������$����H`�0k��r�[
��@����*�D�������T�b��Q
�.&Db��dm1����j���h�'�y�l��X�[����]�Xw��K����#�s.�[A���>?��S!������{%A���9>Nk�+����T+�X�������Gc���`n����9�\���C����S����{�DA�'�����~�6���M��=��DLM��{����*����CKl���m��w���MN���f�y;���g����p���^z���m�']U���`c�oI�!�2
]LZ�jD�����$�oF�7�S�+�'����L��:+KC3h�+&*|��))y���x��kc���5��9�������GHF1��A~D��=6�?�ne&�u��=X7J-`�<�������w�9���f1��J.i�|���:�P.�{�d�	�����r\Q{E��i�>���5���}|����Nf���@��h��6`������A�P�?'���)>)@�0.����_������d�KL+�J���Xdz�	�w�%����s��)���i�-�x6�<RR�E%������W*����@7�;��`Y������b�J�i��� ^�
�V��2[��q�Z���!ZC��?	|�'��<��

�OB,�\R#u+$�`���!���U?P�����������5�}�-c
�U�3��������v�K�[+��#v�(FC0a�t�YU5h+F5�+��L��x�P��Q�bpN�g�Ng�9��DV�0�y��38�^��Y�"�*;-<���@V��R�g��q���W����w�K@n/����F�z�f�X���_x�$������(F-�n ��8y7��D���/�gr1_������&��u6/^4�7����>M�67�>~����>��<x0�[�������G�����>B����{��r��;{7+�U�r�������a,��$�l��m���?H
@����M�#}�.�#�#]��`jHF�)pf+�8����<O�J�7���2���-�������������[����������-�tQ��H#�����O�y������e8(�=�
���D�t6j*�A��Z�n��������<�m\�����*���
d�v���������/��>���z���w��@�����~�,��C�9����'����A?#Y/��9m������j8�/a��zY
��U���v�w�|��L��_	g��A���s���,��������90V�-Q��M"�G��;q7V�UZq�U�������
�����>�1��I0(�*�Le'N+�)����O;�F�Q�i��|<��g:cq[�~=����C����
4!'S�_�p	d����h�m�l�(��a��|���_��|�=��o!��z�����1�|�i!����ra�n<��YM"���������mc�d�5H��8�F{����^������N�����X�����	$�E��G����5�����x��4k��'p���
���"pN'�1~.�)�R�D}taC�Y�H"���q#'���9A�h����%oOR��@8�B�����7,f�����wN�����d"1���#.�N��"5�
�!�����w;��{F���p��{�����1S)[3�)���&��b!�H���j~��o�����&�b� .o���*Y'S��� 1��%	��7?�����SB��~m�.c]���R����wY�@��%����wH���|�o>!�������[��i& ���������C�e��Ep����N3�q�_��3�A��)�'<J�7IBfc��Fu9����n[��X`�n�n�������#�RqH�\�-QL��'�I1^vA�����kJ����`M$���\��������-�B0�e�D���k��k�.��HN�cX�����%���7�����v�(.���K��Dda9f�4V���CD��� ��x��)��9&���!$�X1-mN�� ��T�%w��<�<n{��8��6a�!*���p�x�^l�x���eP����W5��Qm�]2�`F]��5��)���}����Y	��S�����mwp���?�]��lw�	��3sl���,(�����p!I��RL}A������_wv?|x��H����:�cB�[D�|05�,�QS\
�l���z�h4ViIHr:��������0A��H/�x0f�D��KV>]Nz��K��d���h"�12�0{
>�QN���k
Q����tb=�4��d��E���dxr��PfS�����T}r����o��D�)<���?L�����-C�=�����'���,� ^����	F�2�0+�3�<<q/����CE��/^
=�z4�"����\��^y�lW�[d��[�z�e����Y��e�]Ij>xq!�I./]0Hm0�B�q���������7��wW��E;�v�������[[���>�����$Ed�a���T����^y���nJ7m@J����v�zv��k��R?�
u�����ya��������7��� ."2�uq��f����6��Z�����GT92+���#Y�0��
��.#.�&����s@�*L�NF��Z��R���"b�a����^fR!�����#�PaD���-��BJ���I:����%'���N�s��:����������|���CR�s3
�/K��B?���.3���t�	��n�m��\����]��}tn�\�R��`�HSG��H	����|e�+���=�{�����'V�C���E���_����x�ak�������+z�l��NC0�����t�r�m���S-��9XAN��W�Y�c���
Vf���$K����B*+�a���z�R�vU{,�����h���*L�#V����G�_��}8�~E�WZ��I�������e����>Gp��j��y������������u+�Ee��d�D |'[�}��:����\R��=�����b��sT��D���$/���y�F��/����t��x�y���(��o{�n�����<��y����H�g���!���8-�����A��^9W�B�'����]	ya���U��r*���)
��-]���H3D���$���q�6��k�8��:a��w���kK��CD����%+-�;��"�zS�s�T�u\A�e!e��9�&/�3v>������N��/��o?�i���I6�������:��J��p<bu*rF��M�L.���F��1�>�������T�������b���'���& �-D?��Q�.q�H���Y���Fc��g�i5��Q��V��C����f��:R ����1��^�x���"��q�A}�F�MZ���NwR\�pF�e{���������I�Y/Y���B�Y�$��	iw�k���3w����;�#�$k��RTd>(.�_��^Q�-����,��8�7�
m]���8�v	���\6���}\"i��H��������5GX��a��\� �t|��H�����.��r����m{>�Z�_�R�m$D��i���\v[/u��\v�L�s�!��O7��L?��':e�Y��Q��oH�W��g.�k�!t+h
�3����Et�nwP�����72���@h6=���5���Z������b����'BA�����v5�z$���������T�y��/,�T�6U 
_i��Ju��"?��jb����4�p�V����[.w������������l�{��Y��s���B���L���C8?�j�$��SL� �e��=�"�W2���>"x�����=����)I���%a�D��_���������sLw���&jT}��]i/^���w����4#q��2:1��l���|��d�o;�x4�f?���y�`����9����Y��5�/�z�%��*-������~v=����3]�`��%�����4n}Jc)Rk�1�����O|�Ztdb;��w ���z�����#���e:���<������v���A~x���<~�QO�_<{T�z���bE�s��P�'���5\��3���R���H�b��l��$Kp
��I���Z8Vhh"���p4���e��7���	l3�/T:#3���;(��,�����aUt�K�?��)7vU��kG�����-��5AXy�eO��Y������I�B��_���hk�Y�E4������f	=NU�����W�uO���!��6��d��45�{��]b���oh/��g����#I^�aAwj
������]�H�(��������s\'�# ����Fx��yu�_)�!W����������������zr�rZ����Q�l�h���-'D^fcL�l��am�,����{��S��������	v
���b�a����D���I�+
�>��'��w>5_���#;K���k�{�'�_�i������WPm�������X�v�������'��N�.8[g,�N4����:�H����."nuE�6���o/G:{����}�h<}�I�g�1�z�z�+�aR�����9|8&�ADfT9Z���; �%^{c�w���6<�D��E��A����u�7����;�P�9�������*� ��.�3ti�n
2��\b�3yQ�H��q�g��*�Fpm�F�[�]��a�G�����_�����_'X�n;���%�\
��?����������E���������]��y�����y��z��]m^C9�S���b������A�j�p�p�
�M��>@2\O�3��e#�:S�9Q&\�hwe���1q�,������kN�&�����&r~�<�~�	W��G���^�:�A'UG6h����4+��xBI(�����U���e���,�ce�3x���Y�����?������<w7����5w'��iR���� ����������e�_q���-����v���h�n�\K��,�����	����U,�$�!���R*CL���d�����lqo��@~&�_�9��k��{)L~<��p�S!e���T���~����E���yn�'s��b�y�FtH8{�!�������fem8R)�����"�ek����v��L�����b�lw@�d��?}��u;�T��"�G���T?	_V>x��S���5���A���~�M�Lj����W�����S�Q�����(��y�������G�l��$O]�� n�n�aS��&��������G���������R�M=)�1t��b��v��Z�,dr���PO8�:����~w�Y����cys��l!�����EA^������I�Y�(�'����O)���~�K��@�
������U����
.,���O��V{���~% 1PG�zAm�
���(n~p@��|��*��	��$�n2�.�U�G�QChS����\��4Hv;���0I�����<	>@1���a�ET�C������%X��6)g��;%�;Z���kq�<�1���C��������_������?���YU�����,�GP9(y��<U�Wy��c�
gm�:F����/J�9a���Dd{�M���A�`���D�
B����#��u��Ana������% L��.H���r������\��w@��Q�!<=�$�8-�M���m-�Am{����NqwT���Y�E(�\��)K���J�p<�\�ii�����XK2�>���e���U�����1���
����f�O,'�����:M�?�w�A�Vq0�z"F�o�����[w�-��_��A����r����A"n��ff�I���0�4���=>g��5�~�9<�3@"2?{����5��b��n���`��*�N9��������I�����3i0���C�m3�
�O!�B����<����6���r6�{���k>�j�tL=#wS���I�"NL����6�o�$p@9G8��[$@�\=P��dH^0�0������`d���2��kOG���e��N��v��M`gv�edh19<SJ����+Uk5:
3�Fk�-�A��w���'C�/�����������%�+��{�������"�
���x|�A�K�;���7�~�������2�����������=d��������cc����G����gna�
��^�k��;e�v]P���Kga[�Q+�y��2'�(�����Rb��@�&�>	}CF��o "x������/�n>�a.���3LBE�4�j���fpJ������{J�!I�\�)W�%"��S�-[�1���ZQ��sR��9*��U�/� �
�K{�#{PO��^����Ne�o�hcq<�G5��X`W������A����������N�Q�w{���7�\v���#��1��"Z�1����W����[F�h��%�EI�N�'
[D���
f
��w9 1| �H�hq���nTe����I��C�u0�S�5�8<?p@g����/���I����8E�X�����z��^�7f���yx���]�j����f�,.��%�y��P�,R�8a�,,;�y{���8U���}U\��03�Kh3��$"#8I���.KS��|���r}V^�����u�T]��pz&i���'��oW8���x��nW�f��b�G/F�
tG{e��������TG+�7� ���b�������v�U���U&�	�`/a6����U���Ko�����ZA��&{Wk�8�������0��_��DoH���f)��
J�G6���%�1L���:�9����� �rd_I��5A��Q gX�&�D	w,���8�r�1��mc*����XU`��fo�Y����KFT��,���p=bFr��������F��r��?\����
������A`��\O;p�x�����Nsq�����@f�� ���;!r��!3�j����
"m-U)�?�NC�����e"����\.b��pM�4t���^�xUL�z:�q�'/���(u��)�������Y��"q!���^�!�E��w^$^�
���2���Q{�a��Z|=^�Y��n�"���o1G�|�i����=�����q$Ji�=6(�R�����+�x�jb��uF�`��g}���SW0�FCac�t&]f����F�`�\	��r��nL 5�u/�]���� ����]��L����L�`�_�a�efR�L�
X3f>e�����L���R�{�U�����vKl<�o��A��(�t���m��C�����4�y~_���{}=6�X��ixm���nR���v���A�DKZ��&t����$d_���a}����[���H��s^�"�j�H�]DT�X�cE��zQ98~��'J������h\Ia���#�"�p[gut54����-��X)����'�<��#��N��*0wSj�C<�?���7������64�����t�>�<y��h���?�|���<��D����G_?(+��c�P*&@�BS�
�cnl�`�-_t�a�����?�X����.`%2����r��`�����
E�%����YV}-���Q�x��n4�?~�0kE�����M(��5=b(X�S�{`�;��[hI�b��WA��O�Kk��#�	�������3p\F�����c�y����)��0<������/[�D��D��a���w�����23�8�u��_�p��`/��������\F��ES_��R"A|��BY[O���@Z�v�y��p&iy�TR����������79�����!�<C���(^�F��{�e�����u�����a�`�f�i�f?���~t�^f���k��c���P�H����Mr;�,?�A����@��J�
q*5F��p���O���?D��p�������g�]6���(���`]tUh2�+l\+������"�nd��J��]������h4Z������i��C���m(���C�Z������s�Sc/c����&�$Q�~���x�����~����$9y�o���a&���o9��?�=����Q`�O�kz�����G����
4JG�1=�|�"����������/���d�������������
�����a�����1t��h�u�/�?	��I"����
��� �7�E��+��I�����~�� ���-��n@2id�?y���3���n���O�[O��G�������]v'��)�w��Q'{��&�au�~|I��^�s�n4��:���(��������&�R���+;����}����]���(J��'599���
H�%���4���dF��S�-�����g@y�����b���N���g�&���N0�/*�6���*F���#�{��vM|����N���g��!|��V�������3�9{��z�_<{F\Us��1c���/4mf[/���l�t���4����{��e�o���A���-/��l�y8�{}�|�hb<�~Y�<��L#���������V4(=a'�d�K��Px�L��S�d��)����`Ras@"}Q���,A!rm<�;�����6}B�D	�W��~�7C��zR���^���~����ap_{7���U�v���f=����D�X�e��)���FIY�����v�Q�a+%Y������H�� ����v��	�����?6���?����=��C��Q�&�%����5f�.�]$���
���8��V�{�x��ps�Q����~�=4��d\{;L�3zz����/!L��\�~�d�QY?OuX�u�Pw�;}T���w������6o?q��"�feF��d�|�������-c���?x5	k!<7WCN�Z��n%�PkK�`(�6�\�yg��c�7��M�����g[[�7/::�
���z�����������<\���HZ�@����*�=��E�������D��T��Z^U�I�('.����.���:G�G���@QBV!0�������6��N>*���xa]\��H28P�+1d>�C�����Ro2�BL�����U.�\�";�@��'�b]&�?-�X���Gd@EAc�����J��������������2���w1h�j������S>{!�Y���k��'��_K��_r���W�E�Os�8S��3\c��d�\���Wu�C�"�`��+WG7h��,�'��j��*�b�I������jr�V�������}��{t4��
�j{����7<��R�i������c
ihp-����	��ZF=N���iCb�@�mS�x��2AY:���,�$h�������m��6bc7/n��&�S�4-#6\F�,0 ��&�E�N�c�X4�9R����A�cc�-�}�xA�\���7�"��Fc��Mv|��_�����]�l�f��j�0�e��	
�;&�����/��$�N.��%�� �%��MS����4�o��a�����4���������9������Q��1=3|�YV��1y�1m�C���[�m#s6u+�Es������~s��t�o����_���#�`2w�\���]?Y���;J���oW=C#_{S���B���i Q���>�k����#�Y���I�
}���i/H���0Vz�uP����/�2/?X��:}��_����A���-��` ��8
������T�a����l.i]l�_��������g��.]���o�h
3�&�K�	W��6R�����v���Y�p���.%�c���Rm�D�m<���?���;<�����8��i;d���J�(4����X���C�����������s��;�|�������"�����f��$E_5]c�,���*�~��_��`�)�AtY���1� ���D*�|!5�M?��F������}�"^��>l�8H�x��a��X��^���N�>�����{������at<�-�������������s)�a������cf*CS���D���S�em<��aE//���!��?�^�#,y�n��f2���g<\9����n�t@9�v�()�P�������q ub�N�F4�6I3D��$��M���8F_�z�)74�/�Z�SM���
��y%I��|*����&�Yda1�C�8�&�S�#��
Z<�1M��g�)a�$��f���I�������Z���6sr�����������:_�mRq��8�%x�6�T#��?�E��zi~U7F{�O8G�f_7��4+fxR�hVl��^ne����b{�4��_����z����S_�z
�~��S_�����9s��=�������Q�|>��^�6�[��N#�q5����0�qPQ��[���x�=��^2��y�����Nk����3g���C�����������A��������m�"`������^`"�r$��s��E��z�^����q������X>|���;�0N
������������������G�i+}1�[m���@e�]f�B](�e�����e'�J�_��l�D�#cd�I�����������5����\����u|��qi��
2�
Z���V!%G�����>�@���V(%b���-N3����0������T���]��
���4�F`��mC�}�d�a���eII-=���������zG�@rOa����|�`���k�U3N�K��!\��q��(Y����.p��K�&�V
�����p�������y9��k������~F��#2���1T&&�9�L-}���u�(�Q���B�
�,2�2��~�
s�����Z&�v��i�B))��h+�%�o�K�	��)rG�r.����N�����)��8����/D�K��
2�������P*��h�]%�d�)�����������o�����G��H�f���1�QD��t#���^��D0p-��k�K�b��(�2����/;�%������M��snJ��^�����?�1�^���3
�~���A����I7�H�m��������Td���+� XMr�Q����&}ye���C����O��������v���a
�n���*�����}��������S�z9��x����Bn��v���7���O����8��9���~o�p�|&��c�c�������Kk������KX�[�L�����c��GA#���[�����L)�6E�i�W�G�H�r8�d��{a����;*��U�,�7�����^1:��{���Zb��<Np�"
 �M���HA��5�k)�A2�n��f�	=/xU��yeg$`@TF�{j�c����B/'�B�/��RP�!��ni2��a���`��o#x�u_xk��LB�pF�����e�&d�S�L�2C��e��*���cXC �t^
K�����h@����=��p�]�������3���L	z�9��,����d<�6�L�R>��A�X��K�u���J�3E�^Z(���*���%�����7@�
C��(��:�v����03iz��7&E��>K��>�3^��%c9)t���L���3�OA���!�vfHt<���s*e|�P8i����]����1���<��8�s�	���d��o���������p�m�"<���?	(������np8�M�Z���s�7pb�4=���5c�p��Ii�M�T�1ngG��80R�,��
e��3N���e&�bTI�z��0*��7�pe2�p��Y����#������eSBq�r�4�GL�)��(�_GC���M��a�X�24b�]�A*��l��n��#�@"%e�J(�F}!���Z�L����-:��c��3�%�1O��[U�����P[P��}���f�Jk��j�vm����^�dq}1�N�:w���d+�o���Y�(@��#x������Q�~]�MPc��Z��.�L��I�b��,�V�s~,�3�*hPo�sA���:#o���-��wT�8�mImy/�x��f}`���	(��K_��;`9=,{a��7����p����0�]aw9z�.<����*D�j��:J	�����E��q�$ ����,�+U���5@L�:����E� Y�q7�_s��`�w���U@����b�/���
r$+X$�C��R]�6��oDT���w���fk�N��Sx�	K�
�#����t����S*�]�\R��|ia ��_t�`En~`z����,X�d56U:��@(g������;*�����c�Y0-}��`�Q)�� ����a�H8�x���p06j4j�������7����?�o��c���%�&��U8$o�X�K���"C���Wy6B{��]!��2��9��r��.��h���.BY�����o6��C�,��3�3>����X��f$��+�%��)s��:*���	^��rN/
�k��H����YQ����m�D��>q�1"��q��Oz�Vm�;���`M�����.P��iT�b�N'}�Q����yO��d�=1E/t>�-����Mc"6a�BJ��GO�1VB�ii��
"����U:�w`��z��7U��P#M�F���y,���w��d��������6���������fx�TJ����\+%gItT����._UD{�<K���+�tX��7�]���y��2���g���_������,I�Y�P}�jdQ���7aX%�x�:W���W�1�@<�F�+��:#6wo�_�%�>wd�J$@SE_�E�Y�J3����}���Yr��}a�]Dac�[�|%�7���b��Xi�HR�K��R� 3 m�hf[�����'��`�o$,�9#��n�D<z�������j�l9����F>x��nz�t�
��D�����U8@�<[$_|���U����B3�U�?���I���������?�V#���F��M���q���/pl�����������_��u��,������p�@�5��W�d����c5��+%!>1�����6�%�O���W�5�y�Z�Ld7�h���������q���dL�q:�����e.��=���8q�L��?���t�WF�y���m6�ob�D���}���wL��2*:�c��Q��zT�������l�Uu�]^x%�W�)�)�0�( �R�r�7 1PU��f�0S1�R�w��n{��T/�/�T.�m^�HR�/N�dQ�\p�+��,�y���]����6S������n��w%���2�H���3[f�G����.�&�y����BOU=	�TC�����,R~�"�oqPU�U��a�R��W\�d�)�X���������A�{�0PL� 3�([J��?Go�c!��.-�}jo�B�+�AQ�/��n�(|���A����cq�h8U���MN��"��x�)+vj���j���g����������w8g�H6�9o�������"u�#y� [�M�Z0�v�s�������}X�M��X���F��]C^|�F�c�i�s���1�kz�_b]$�dFtv�C�QNO3����=d�����s�����$����bG�K7EIQ2FA����#�{�XF�u�O�����^t�o��H#G�Y|thuK�����(w�
eR6�_g�$B�|4���3������^�"��px��?�����L���,Kp�s%rU4�i\q�C���j�xe�\2K�@lyqE^j��^�K�]q:�3P����W�&�{|tvpv�t��`
@�Uj��*�H]r��i�+).L�#�$v��$1|'pd���?�]2
[h�����C���;o�@r���n>�����^��3��] ��
J�|����\h^�p�����.��k6~��Z�Y��;��T|5�����q��F<!$�<�j�&q���K�>�9����>�|��AkZ}���k���|}8.M��]\��R�J{MwsI�k�,�&M���_��n���z�X�;��w*�������/��bJ�5mS�{��U
��f�U��x��wE���|{�+����=����o����#Z�bq���d[,DK6�%$�dM��|�Y6#��d��g�����DSs<4�?�N\I�.}/Bt��]l=B�����������v�
��z�x���
�����k���I��?�V(�r����%������q
�}���R���wFA���i�bN0^���XD-.����e�~����r������M����X|�n����r��n0�*�7��W,���������F������e��x��$}�h�����:���tP���	n��M�w������~�������1����?rv��;u��1�r������s6��
v����;��C(��<S��u0�N�N'Ko�_��{��g�c���%��P�[&���5��t��;?���',�A�a+��i!��z�y���������9v���z��V�gOp�<�=�&,���b����x�U
IL����B����� ��!(��c�������@u	���\���{���_��d�s��;�����!Ucw&U�1��������#}.~�C��R{Y7�p�����1�eJ��_��4��0E��^�k!�g������F������o=�M���j�4M��x���n���!I�mU�� ��Q����J.x��=IWl��
<�����
��I�}��Yu�L.��C�����&�,2�$� �d���_"y�@�&��^z��F>���f[n���/��T���.���=�X{�;�>	7pW�4�Dp����f�����K"����������{� ���p�8���c
l,���������
���
���1LEW�|+;�|+}��i��h���[SJ���W��mA����G�q"rG�]Z�j����R{��{�J���x������	I�}���j��3GTe��e��������\�3����#�i���'O3��[�G���7g��>]�3�=��zH����k���|da=����Z�<a
�����H�f�������R�^8��rS���T&�J���J,��s��[����P@-WU��h�e��&��~�'Up�<�$]G�k_��R��`
�& ��/�������U��1Lu�g0q�2&>��W]��!M���W�����������Y�?gP0�[�g��;�����S����5���[�Vo[��[O���K��q���n��
�fa,�jf<�<�*I&��V�uLT�[��Z�r��f��
-X��\(��M��[pz6��d��YA>��r�A�Y����s���tgI�"_N����%9���{���H[���&��Q�=3q����iV�_�]Pv{�����L���D n�npB��lt�Z>�F�-�����?T
2c�K6F)jZ�{c4�<\��yZ�`�y���d��h?~�=~�5���^��Z�����2<���G�����w��/yv�@���9?����{�v�/������}�E�?�J�;n�&}�<���A���u�t�t�bD�A�l7����3�I����f
?���<����m��h�)�]���3Y���'�Td�(����D�[�<*��m���?n:?��s�hL$�����C���-2iS��G�G�����������o�Is����-2�XP����o�I+���Q��tk�W�g�����B��b^��
%WJ����?fy����/w�I�7���Y�?�Ic4���
b������a[���~�`o���N}�������2���[6.G�y�AG���b�/Nz{y���q2\��jO�����YL-�D��r/I�C1�%��{0����]l/Q���7��\48;~f�p}����Y���I��hE"S;qT�i���_<z�j*��":G����l��Do^�5�������_����!F�.?�A��j~f��"���K�f�1�+���1k}����VU�)�\��E
W��������Wd��t��q�����;ov�`��d��O�
�GXSh9
��c:p��/~B0S���J��d�Q���#��I:�r>C��dgw��n�_<�O�����g���z�u�6t	?���d[��O�� ����a�?�����O/�7��U����@����������s���1�B��D��X�0y����Z�Z�x7oW<��&��3����9�N�MI[����@#�9�C@���{M���q���j��7?���d��YO?eO���^�h��~���>�����~��)����2�(8Z~�(<r������)��?O�������G/d�������1J/{N��1��+�_^���_��H�p��v{������k�3l�x�e/�<�s��X��:~�
�[v�������-���@~LF-��z�4��n�dy������p�q�������O�|4�Y^��J�_���
0007-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0011-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
0012-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
���IR0012-wal_decoding-design-document-v2.4-and-snapshot-build.patch�[iw����_���'K�Iy��9�%���,�I�$9>�$5��F7if4�}n�^h����)�Mu�B-��?��B&����??y�{���a0}��'5}���d>
�&���Z��S����b���?c�#1��T�s��8/$����e*�VQ'T?6Nd���M�Z�/.���CZ�px4|.�`����'� ;�.G7�������k}U��:��P=�E��B��X
:�$�������!&��hpe�X�:��v��&
���8��j�@f:��Q2���{2�>}}��>f���������������[5�����O�v:��s����7�����������
7wbp��CA��e<SaK<;�	����Qs�!�TAb��J�{�'��_!�o��k�F��S�n�t&d�+1����X�Y$U~:�GX�t:��'�prH!��Zu�<�������'���z��o����S�����L�HH2=��h����\��������i��3���*�@w�21����e�y$�M�������g�u�L$���%���4���Z��y6O�-�l�A�3�a*1�,;�RGG��me@'H�&�)N�,M�<`_���7smJ?�zaD�@�������$���o�Z�l��[�����

�%�\��fE��$Q����s�a.�*����L�*%Q%y*�����#n�*n4a]�Ve���<�!�4}
�XF�Xf���i,���K�X(�F��Tj�l2��Y�u0��n���-54{�<�43����#b���������X�<�_�@+f����,��G�e�!�#f-~q��SP�@��#j���cm��|iS���Zi$�Pe0!���5�{��-�2lPlB�h��FN��d�R�5�2%�h���@UV�0�5l�%�>��<$��xR����+��e���Kq���NrX�6Anhg�"o�*UB�O�N����d:#���,����RxX�5��,�H{��l�T����d�u�(�����,[u��f�L�o�������B#g���.yn�I��v����N��U������:�u�����V�����<�e��d���M������N�`#�<%Q:STp3�"S�A~������j:��2D�,`8�.�y'tk:�!�.�������N�u�IL{���q F��)I��f*6�`������T�]H�h2�$f{0�Y����yB����
5��G?��B�{��O�9�q��4���L��0��
����2`�>����n�a��I����V���\-T�|6���p�N����B�E�~�����?Bm�e��2��
H�M�S�S��Fy��Y H�n�V��['�@����f��t�hbj���;E��%, �o�D�Vay�q��%	*I���B�f�hR���+�_�9�itP�'�6G`�c8HB��w��Pn��	�Y�)�'V?�^�7��s�$xUG�.�p�@�����HoK��]�,����������-�v�8,n4��|O`jfw�xj4���0n'���q���r`��������h	X���{P�\��+7��"�th��\Gkm��6�;G�%�"��3����KDI��cC�W����bZ���d�"*�<��^ST-�zY�`V2���T�X�Q�3�Z�pFO1�BZA��?���vR�����@�
	���������L�k����O@�S=�SZ�f)�����]�U�����S�c�'���!�X�#��������k���I���CvKl���6�j���:m���3���J�Z-+2��� ,X�!���xd��"�L�*j�o��`�����-�W���
j@��"����F���g����#�%�-2�T��>M7�8��IL~R��Jx?~U�2�t�XB��o�����s�i����'XN{�����I����s>K�I�|MSR�2���RV��9j	��m,�����7_�&9����YD�56=+���r<���"�`��$m�����Oc�/����������9�`�=�OLLb��N����0r�2�&l9�x�DG:�������pB�skY�����bA�2��R2���������U��j�$�Q;
��C0K��2]$�19����6W���PT 1%�X��������KtJ���I�jJ�%��4���$:���t"�:����������}��h2�	*f)�4��Q%1��+&6h���XM!M��.�e�I7P?!B��"#���X�$?ArB���O�j�w�D���3�T��9�`���c�����rET-��8�R�Uq�v1�">��/�e������E+;cd�eu���2l�Bj���t�D�")AD#m�M������i�{[;��1��(�*4-��Fs����vJ�mG7��������E��m�1ubg��"tj���[b�t�)�NB�^�����f2�CT����m���Iyx�h��_��|Io9���t�fO���m��5���b�q����\��/.���45L�T��(m����<��T�_�D�Rr������t���.�� ���}d�c��-}��B*n�t�6���N���E���g�a;��y�q�M��Z5�K�W�&�uq� �|1i4��[�0�p9���G6����� �TR��\Vl���6Ehv�-�6���a���;;�p<sY�
�qBB�%i�K�O�m���zs�g5�__��>� �=�]�T6n��3�;M���
�4��i�d�c��.+�Y���Um�Z�$��x��������)!����\84�����-��'G������"��m��(�;UN,�.�Ul�I���D��]���.w�g-��B
�]z�Q�Z�����P.81o�PE?����n�J%�����Kl�7���M|m��wc,���$K��Z���Y�A)c_��*D��c�z�;�����|����ZE�M����K8m�(I�F��5|�jOny�~c�$\�=��3k��<'@fB�#�.�l	m\F�L�{H�q���Z�� /��(Q����4���*�s��}f�:$YH[DI�S&��L:�+���pL��T.��{����p�p�BvhC�O���0lc�:.�8��N9��m�)�5�uB�?Y���~"���J��O��-�{
5mia��J'�I����\�<ej�2�pM�S��;v9��^55��9Wt��br���Q�+��F��;�c���{�`�0���~@����\�KMw��"�������%�/�D��+{\t��Q�%+����S(�j�k���jZ
��a�C���t�G;>�����=�����]u��<7����o�������F7w���1����{������]��o�-�y]m����RE�#��?�Q�.E�-*�r�?�=e��������k�;���1����\u+�D'�����������^�v���B�[��/~��"��]��n���O][��8�m�kvR��Wa��*���Q��'$�����^^HdzH���+-$z�?x�j�W�9;�d�4��9���QP��rI���'rw��iN���E�S�a:o*��4������A!7k��[�����>O�3r����]tn~/��"�Q�`�bm���1E]�6��r�-�c.�u������?T�%7��}������0�Y$;���KOg{9/�����|�Ow,��Y�s��OSl�H��TSZD��O�B����^[KLi�cR�jaK
��o���-��������5�c[Cp1��:�m��K���9�=����i�����,�R_�����i�;*��]�j��]��kXpA"��\hS�����ZP�H�G]OT�QM)<o5���u6��+j�(�H�������F��a�i����w7��'�G�vB�"��K�����v�m���*���M��1TN��������k
E�����8�`�	�Fr���
����#����+�t����_j�)!1-������-��"6���VU��ae/�+V�+�p��2��0����^
w�j�(��a��E�f
rB�Lg�
}slN9xJ'��"m\]����6!JT3�(u�K�E��6w2B�f�����L����/nB���e��&L4*5�6;�������Ay�J�e��R-K���yq�m��1�N�os��M�x��,���+��YV�|����d�Z���0|��h�Y)V�9Q��� �y��m��+���[B�OcY��	7b���_��[��)r<�sn�)�mh����NI&�������QTQ)�{�O��9�O��!��������)z��FY��q���n� IA�O�]� ���-)���O�`��(I��Io�z�A�`������|����O�<��q�8|�o?���mJh�|+�}��#��l��xb���R�yK���t����y�� kc���,�����%7��|�B��r$�,I-��7+}S��'��r����
�%r:��ji!J�RYm��C��w��P�}	��V'�����cY<���)�l
m��2X���v�d'����f���J�!����t�j�m�`�p��\Y$bOh4��@��.\sBb�u,���p�PG�1�L��5�T����KvO���;(����ds��~���B���^{�������2�����_���}�t�a%]�Z���Jr-/�	w��m�M��P����Q����'����r�Co���
T?�=�l�V�m�}��H��t��Gg��b�u�>#���/�e�isHO�A����~3���[��l�w�$��@z{�_\!���j��#{��C��H�ziS�Y*�tB��-�<�J*7am\�����d>�%
���x�"��.k�,��}S�����DN�
�s���q�}��v��
�'{���Y����������	���J�����+�q���[���Z�6���+�/I��}��.FaE�g����!��-|� �:��s�o��g_����;$��^]i$���i�^������2�,��K��r	�t������|8*�I�a�)�����''gG�k�
�v2m{��y~�t0���f���y��u���=>�����#�U��T�
~�������������j|9������6����O�����xt37�Wgc^
8�R�����7W�����������h�\]\�I�?�����7���nS<�z|6>���g4>B�w]>���i�2>������f���?�����)�����������{�����j4Wdz2}�����D�t�O�������$�Zv�}�_����k�������?����	nzv3���btr"�/���9�g�<��xh����6��������]q�V��u���u[����[���7�7�[����D�Q�(RJwTj������6\������!����*��:��V%�V|��b�p�t���|�f��w&A����+m��*�9,$�=�O��/��.@���$ZN��&�����nk�9q������P��>=�h>ro��������q���W��\��h	r��/m�|���5�����Q���0�G{�n������g{-����?��d]�C
GB��VXw���,'1ks$t������_`|�����Cy�����|%�����6��}j�w��O��!z����X��C�
�b�'-vXY�D��������6g������x���<h���EV��6���]^���'��`���E�����ZeH�<i��j-��_tM<
mG����F������7��!�)-/���A<�*�F����m������_����:
������Zm���mk�O���
(oCs��6��K��8��V�������h�+P|=������ft��e��?��B�,�G��	��v=�Q�~��]iWY��<�����JH�]5�x�>��`�ai�fN
�H	��P�J	�n���7"�"	o�t�����������
si��0�|�����lHg}[��L��$FQsL���y�U���x�����oS����k*��9Y;>�::^�����=|��V�{�������y�z�Gg�H�����X��. hS����(�����a�
i&��^d)Eyp��F�8���2���y�}N�lk[L�W1'�;�����������*��j��%���OD�w����s��~����$`25')&@��2���OJ�|��>�'V��D5�%b�v��������4��5��d�N�UYA�g���X��xZ5��a�>��y�E��������I���n&����.�LN���/���A�2)5)&&�O$.�9�sp8c7�(�s����d����4����0��V(�Q�5��Z�z���������T���/�S������I�G�Q\w	=L~��HX�<���e��K�����p�_�Y�������B@�P��U�*d=���t��\#���Q8�0�����_���H�Y��Yg9�vht�D�Y���L������xC�B^te&3�T�#�D�����9��=��W���V�� ��f{�V����t�\����#F���s��x^��S�A���qb�����HE�yB��p��>.����=�5��p�w��	F����!U0��0��NQ4�@#�M��g-�_����vo���p�R���+bE�H�q���0��������}���5�L2F�FpPD�S��j����d� `�����%�G�4G�G0��pN���)������=���6�x���-_���3w���3)ZO�U��W�f�qO�/�d	��b�p���\FZ�� +3d������C��:M���S�1���k���<Mr��V�0��^xb����s���#^eiM��4���^�h�8������8��E��B1��������:�^�L@@c	��T���0��%���g�V��T�cJ)�&�L�;%��y����[�IR*�q��6��P����)�����T_�g'�o4��/9T���%G��j�Q�U��Am*<�Yi�l��GU����/����C�P�l�����-%I	����[T.��e9��a�p�H���0�u�M��.��I�r��*S �?E[�|^b?�&���@�`d��"��_��N��<WhN+�f}.S:�l&�F����0�����8����+��+N�+�JY&���(6��
�R��H� ����/���j�j*�(q�m���r*��3�+���	����7lC�����F�S������tO��
�V����I��&M�3�Q&�a�r��$1����(��B�I�9VR2Uo�!��v
�������3���=���fhq�?�)�oz�H�"+BV�m(�tN��~'("O!����CK��U�(���K�P@�~c4������,�tq�)��O�s��bg��aC�9}�MY�'�M>���C�;�� �������Dzw������*�qm���w/-�|	�����bu
�+[&\q,�qC�A�X�T\�J�?$��\N��FCN��M�H��gd]O��W�~�mz�=�	$DD]p�9�7j@��C��_��n�Q����{��_���P����K���xY�K�z�6�a���2=�I���V&��|����#�@��c~�vI�s�����KJ_�c������M�n <�����g��]�0�����'A��59����w��>{�OF�����1�C��j�5R�m(�m��87�c�������`pz��b����sU���p��|�f��������j(�z�7pn��*�Ykr�7��}8\z�S���K�af7�J��\'��d�O��YU%�X���C^K0/���X��W
�����b�)�4��V��CX�����@j%���l[P�/A���K�?���\{=:�D�+V�ED�GM�M��-GUnv_�<8<��?^�tY:�UC
+q��U�������uR��Hc'j��
���o0p�?r�
�*��@P&�deF��x�,gW�
�))F������%���K����*Fu��������j�1'�,��6��!���!�*�Z�|���Q��c�#��0��d����8�
�hr�<��(�.��$6��^`�!�(�t��Q��H� ��%�x���y����l4����tx��(�Ycf>��O���96����eiiN��F����l��Se�����q������]����lR=]u���^6� ���\��j=P�%l���dN&�*WD�=E�v�|���Hq,��9���9zW�[�1�F�&
������r��/��q0��6�&�B"�B���m=y��8�����-�3��g����:���
/� ��X]U+���������(\<��������6������Uz���g\�[Q���*�����8�N����$�d��l6����mA�-w����f�����+Q#/'W�z�O@�QL�G���G?�o??<����-|�s����3o����s��8��z#����|��c�MO5���M�N���� sCC���������q��s�o2(�@�c��*�]�>9��=�ds�lq��I�0��D�����GOW�I�������hE��������[�b",�}���3O��,+��J(H�U��<��X��aw�������N�~::~�"{$��������Bs��xMO~������e�j�Q�
y�Wa��t�����L�/�N�����l�O�|����������������������_���*t�U=���+;q�8��BWA�����E�Gl�d�3�b�|yg�r�?y�D~{���|������r���`��ND��@�Q���z�g�@��������;[�[�w����w�GJ����B�jq���x���~>�������O(���"����o��}p��?�
�oE�����<$r�}������L�s-1��H4��6g�`�(B6]8.�9d�$6�����D#fL�`�7�Q�f��/<������������:q�9��g ����?�d������V���{_���g����������������~d!��8���~���#����_���3�������gG���Ew��5isP���/�����������5���������%>�l�h���m�J�l�/�&����*lm�����5��������Q����o���,�ys]t�y���H��%B�����Ohb�����������R���ni?�p��w�������*���I	)�B	�t��Ag�W�)��d���.o�
(��K�}�E��R~n&��]�&�n�7(�����m�������^�\���` �b^gu����AI?6x_�N5��`X��/V.��E��%�?fF��^Q�� �(ak9�3��@�m@[
��b�����tD�rC�v�.�A�>w��%(1���]�5�(i�t�h�/V�
���B�+21��hZy����=+��P�
J�|������L�t8���HV,�b�,�`-�K���S� ��?�'��/���7tl��h���	Kk(L��'n�F������Z�
�\O����o���R������y�"c��pA5��������u�I������.������R7�v#�6�k.b���wx-�8pb�#�J>+����N����V]����FCZ������ �$�R�5t����Vh�
�56����W���������n�J�%4����i�������B�1D���(�	�����G7C�N<��`w����J��(�R_���9���W�H�7��������0����VW�]��PMp�`+K��J����1���\M�N|mgA��r���?�#���`ij��bH�8��"��]�y���;�lF����/�ch@�q�z����1�R�J��W	�������2Ly[�:j���U��Z7vK��'_�@�0 ������2���d_SDX�5����>��%���A�������s��^^�v��JHA�i�� ����#�Hz/�����C+a��%{�^�@T�/(|a�@{���g�]��q��z����V�%&V�����a��{a�@�D|���V�PEi5�"�b3+�L�>�[pH�;�eJl`�~�}]X�W�4�1pB��A�C�ZnN�gS��0���Hb������V��F��A��G7���LH|}�"Y�����QN�w�;������8g�X����!������4�%����"p��4�.��3��/����O�0�r�'b�@����i�=T��ww�h����A,c��9��*o2��Z��9P|c�����j�-�Cum;z�F�S�N��Y�S#V|_+�:p?h6������hnI-%���u5D� ! r�����:������u'N�\����;kc�s-A��F<� O�����S�Lg��2���U��<�[��\D���s�9��)�����A
�^����r\��	k�:�9	���yexU�(Zt=����8	y���%j�'��=/#��Z����vP-u@���zt_��,X_l�������]���r ����W�[U��L�	��CR�$�\g����N�%��v.�6��4���B(�j��.�)�El�[(�B	�>pN8\����6R�����{
��x��u������
Iznh�p���	�=3����A3�5��R'�i��'��V�q��
6o��������}d�����>>�,�R�G�3����W��[�B��.����,�4��C�F����K�ZR�JJ�%�`R���G��6�M�(�+t���b,�3��+�?�l�GP]�!W'S]4����Ej��v�Wl�~���>���Y�)� O�a�VvH}��+pA)���Fs��a�g�vV9�(I�2�_���2y�]����V�H7�;s�wW_�g�s:<f
�=m>b���S$�~�oW���j[�����*"�6���W�y=�bdP�.��i��S�Q�l:=-up�Z�q~�����i�I
��/�J`k!�����[MSN�Q3�-fE^���aq��������)R���#�
����~���^��?B��|����p������F�������{GuCd����J����`OF�=3�����
@����S��K;n�z�1��^����c/�����
��e�H:��do�4�d3�a��?z@ES�Hz9��Hf�
M	�?�	�:-��NqR�
�d>�z�"t/�W��H�i���8��:]�p>��Ey�n���U���f�(�+npK�����G������iR�M�p�>Fb.Eu������u���~0��dp�D���9I�������������,OiF)�G���u��Ije����	w�D�-A���E���e�����C����DH��K�rg{(���|l��~����bU�����YK�*���c'�&~���`n)}�[�+�j���oE���[�Z��7q!�==]�����_6�L��z[3�_������t�+������]�w��>z�Lr�CY5��K�GJ���nm�f��6�ysyr�W��
��#7�W��|iA�������=��+�M6�<���d,����lNkuO�<
����Q5y,r�G�n&m5��(-`����+NFC��Z~�g�H0?2�rr_A�����YyJ���3���X�{��8K]{{7��5��C�5��T���
�$�<�=)r9uyqbW:k�|n
����P�Q]_�0����%�oc�C�P�(����S�e��"�C��t&uU}4Vz�_-��jxN�M�
�Z����������L�� �� 3e��g0_�&�7�����u($����,�j��;n~1l��>n a�2	<^���aNK����^��!�������Y��0����.c�� ��b�����kJXv�;y*cY���S�u�\����P�����a�?��.� S���}�s'���,�r�d���@���L�h����s��s��}�,
,�����A&�R�WUr8����3�[L��	�+���le��Eam�r��#�jc�~"��
N[&6�Fa��}����$�XZ�GaU�
C��zaFP�RG�4}1��676j��o����l���������������rok��-����	Xh�+Z��U�V�����y�W,�?���2�Ot-�D:�R��|�������P�t"��P$N#?��kSy�RX~=k��Ak�8�����3��1�e�p�s�i������eS�l���:|��e��P���"q��G�����9�����M���#�C��N�I��}�9����@�Q�8]�-5�<�|���s3��C(��es&��>@�e-�po�/���!���)����t��o�	z��L%Dd�j�I�����0~	�PP,����4~����d;G!�k���@�1���8�/�����~��y�I����C����}CI�>����X�6����
�8���qe�)py����l$�,x~�+��p���[�����J0� �*�7'-�D��������'!�C�_ �P���qQ.�����
�����mv�P��!:�5��(Bn��
T�3I��hu������i��9�� e����^�	���S������|z�h��������s%��/��D~�LK:��Q������D���p�^�~�@�����v�?���@���c�PA��[}���rX�h`��
��ds��@k��7�A����������!>Wy����x��z���&�n.�n�e�%e��C���Ez��{��R!�������1�P8tf���
�oD��d��7���@9�q~=E������e�}h|����m%x��5������3j�������$�b�����k��^���a5�"�7m��6Te	�f<B6������6����sB��r��wc�����7\�X�'JC$�t3��1��V�V��=���f��E	�
�_4� ���Lo�1i�{Y��P����g�"8�Lp�Y�V�d��L�2U(�|����w �W����]\-^��	��x�v������o{���.~��~����
���`���bK��
#43Robert Haas
robertmhaas@gmail.com
In reply to: Kevin Grittner (#41)
Re: logical changeset generation v6.1

Review comments on 0004:

- In heap_insert and heap_multi_insert, please rewrite the following
comment for clarity: "add record for the buffer without actual content
thats removed if fpw is done for that buffer".
- In heap_delete, the assignment to need_tuple_data() need not
separately check RelationNeedsWAL(), as RelationIsLogicallyLogged()
does that.
- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.
- MarkCurrentTransactionIdLoggedIfAny has superfluous braces.
- AssignTransactionId changes "Mustn't" to "May not", which seems like
an entirely pointless change.
- You've removed a blank line just before IsSystemRelation; this is an
unnecessary whitespace change.
- Do none of the callers of IsSystemRelation() care about the fact
that you've considerably changed the semantics?
- RelationIsDoingTimetravel is still a crappy name. How about
RelationRequiredForLogicalDecoding? And maybe the reloption
treat_as_catalog_table can become required_for_logical_decoding.
- I don't understand the comment in xl_heap_new_cid to the effect that
the combocid isn't needed for decoding. How not?
- xlogreader.h includes an additional header with no other changes.
Doesn't seem right.
- relcache.h has a cuddled curly brace.

Review comments on 0003:

I have no problem with caching the primary key in the relcache, or
with using that as the default key for logical decoding, but I'm
extremely uncomfortable with the fallback strategy when no primary key
exists. Choosing any old unique index that happens to present itself
as the primary key feels wrong to me. The choice of key is
user-visible. If we say, update the row with a = 1 to
(a,b,c)=(2,2,2), that's different than saying update the row with b =
1 to (a,b,c)=(2,2,2). Suppose the previous contents of the target
table are (a,b,c)=(1,2,3) and (a,b,c)=(2,1,4). You get different
answers depending on which you choose. I think multi-master
replication just isn't going to work unless the two sides agree on the
key, and I think you'll get strange conflicts unless that key is
chosen by the user according to their business logic.

In single-master replication, being able to pick the key is clearly
not essential for correctness, but it's still desirable, because if
the system picks the "wrong" key, the change stream will in the end
get the database to the right state, but it may do so by "turning one
record into a different one" from the user's perspective.

All in all, it seems to me that we shouldn't try to punt. Maybe we
should have something that works like ALTER TABLE name CLUSTER ON
index_name to configure which index should be used for logical
replication. Possibly this same syntax could be used as ALTER
MATERIALIZED VIEW to set the candidate key for that case.

What happens if new unique indexes are created or old ones dropped
while logical replication is running?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#43)
Re: logical changeset generation v6.1

Hi,

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- AssignTransactionId changes "Mustn't" to "May not", which seems like
an entirely pointless change.

It was "Musn't" before ;). I am not sure why I changed it to "May not"
instead of "Mustn't".

- Do none of the callers of IsSystemRelation() care about the fact
that you've considerably changed the semantics?

Afaics no. I think the semantics are actually consistent until somebody
manually creates a relation in pg_catalog (using allow_...). And in that
case the new semantics actually seem more useful.

- RelationIsDoingTimetravel is still a crappy name. How about
RelationRequiredForLogicalDecoding? And maybe the reloption
treat_as_catalog_table can become required_for_logical_decoding.

Fine with me.

- I don't understand the comment in xl_heap_new_cid to the effect that
the combocid isn't needed for decoding. How not?

We don't use the combocid for antything - since we have the original
cmin/cmax, we can just use those and ignore the value of the combocid itself.

- xlogreader.h includes an additional header with no other changes.
Doesn't seem right.

Hm. I seem to remember having a reason for that, but for the heck can't
see it anymore...

I have no problem with caching the primary key in the relcache, or
with using that as the default key for logical decoding, but I'm
extremely uncomfortable with the fallback strategy when no primary key
exists. Choosing any old unique index that happens to present itself
as the primary key feels wrong to me.
[stuff I don't disagree with]

People lobbied vigorously to allow candidate keys before. I personally
would never want to use anything but an actual primary key for
replication, but there's other usecases than replication.

I think it's going to be the domain of the replication solution to
enforce the presence of primary keys. I.e. they should (be able to) use
event triggers or somesuch to enforce it...

All in all, it seems to me that we shouldn't try to punt. Maybe we
should have something that works like ALTER TABLE name CLUSTER ON
index_name to configure which index should be used for logical
replication. Possibly this same syntax could be used as ALTER
MATERIALIZED VIEW to set the candidate key for that case.

I'd be fine with that, but I am also not particularly interested in it
because I personally don't see much of a usecase.
For replication ISTM the only case where there would be no primary key
is a) initial load b) replacing the primary key by another index.

What happens if new unique indexes are created or old ones dropped
while logical replication is running?

Should just work, but I'll make sure the tests cover this.

The output plugin needs to lookup the current index used, and it will
use a consistent syscache state and thus will find the same index.
In bdr the output plugin simply includes the name of the index used in
the replication stream to make sure things are somewhat consistent.

Will fix or think about the rest.

Thanks,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#44)
Re: logical changeset generation v6.1

On Tue, Oct 1, 2013 at 10:31 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I have no problem with caching the primary key in the relcache, or
with using that as the default key for logical decoding, but I'm
extremely uncomfortable with the fallback strategy when no primary key
exists. Choosing any old unique index that happens to present itself
as the primary key feels wrong to me.
[stuff I don't disagree with]

People lobbied vigorously to allow candidate keys before. I personally
would never want to use anything but an actual primary key for
replication, but there's other usecases than replication.

I like allowing candidate keys; I just don't like assuming that any
old one we select will be as good as any other.

All in all, it seems to me that we shouldn't try to punt. Maybe we
should have something that works like ALTER TABLE name CLUSTER ON
index_name to configure which index should be used for logical
replication. Possibly this same syntax could be used as ALTER
MATERIALIZED VIEW to set the candidate key for that case.

I'd be fine with that, but I am also not particularly interested in it
because I personally don't see much of a usecase.
For replication ISTM the only case where there would be no primary key
is a) initial load b) replacing the primary key by another index.

The latter is the case I'd be principally concerned about. I once had
to change the columns that formed the key for a table being used in a
production web application; fortunately, it has traditionally not
mattered much whether a unique index is the primary key, so creating a
new unique index and dropping the old primary key was good enough.
But I would have wanted to control the point at which we changed our
notion of what the candidate key was, I think.

One other thought: you could just log the whole old tuple if there's
no key available. That would let this work on tables that don't have
indexes. Replaying the changes might be horribly complex and slow,
but extracting them would work. If a replication plugin got <old
tuple, new tuple> with no information on keys, it could find *a* tuple
(not all tuples) that match the old tuple exactly and update each
column to the value from new tuple. From a correctness point of view,
there's no issue there; it's all about efficiency. But the user can
solve that problem whenever they like by indexing the destination
table. It need not even be a unique index, so long as it's reasonably
selective.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#43)
Re: logical changeset generation v6.1

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.

Ok, I had to think a bit, but now I remember why I think these changes
are not really problem: Neither the addition of keys nor candidate keys
will add any additional comparisons since the columns compared for
candidate keys are a subset of the set of key columns which in turn are a
subset of the columns checked for HOT. Right?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#42)
Re: logical changeset generation v6.2

On 09/30/2013 06:44 PM, Andres Freund wrote:

Hi,

The series from friday was a bit too buggy - obviously I was too
tired. So here's a new one:

With this series I've also noticed
#2 0x00000000007741a7 in ExceptionalCondition (
conditionName=conditionName@entry=0x7c2908 "!(!(tuple->t_infomask &
0x1000))", errorType=errorType@entry=0x7acc70 "FailedAssertion",
fileName=fileName@entry=0x91767e "tqual.c",
lineNumber=lineNumber@entry=1608) at assert.c:54
54 abort();

0x00000000007a4432 in HeapTupleSatisfiesMVCCDuringDecoding (
htup=0x10bfe48, snapshot=0x108b3d8, buffer=310) at tqual.c:1608
#4 0x000000000049d6b7 in heap_hot_search_buffer (tid=tid@entry=0x10bfe4c,
relation=0x7fbebbcd89c0, buffer=310, snapshot=0x10bfda0,
heapTuple=heapTuple@entry=0x10bfe48,
all_dead=all_dead@entry=0x7fff4aa3866f "\001\370\375\v\001",
first_call=1 '\001') at heapam.c:1756
#5 0x00000000004a8174 in index_fetch_heap (scan=scan@entry=0x10bfdf8)
at indexam.c:539
#6 0x00000000004a82a8 in index_getnext (scan=0x10bfdf8,
direction=direction@entry=ForwardScanDirection) at indexam.c:622
#7 0x00000000004a6fa9 in systable_getnext (sysscan=sysscan@entry=0x10bfd48)
at genam.c:343
#8 0x000000000076df40 in RelidByRelfilenode (reltablespace=0,
relfilenode=529775) at relfilenodemap.c:214
---Type <return> to continue, or q <return> to quit---
#9 0x0000000000664ad7 in ReorderBufferCommit (rb=0x1082d98,
xid=<optimized out>, commit_lsn=4638756800, end_lsn=<optimized out>,
commit_time=commit_time@entry=433970378426176) at reorderbuffer.c:1320

In addition to some of the other ones I've posted about.

* fix pg_recvlogical makefile (Thanks Steve)
* fix two commits not compiling properly without later changes (Thanks Kevin)
* keep track of commit timestamps
* fix bugs with option passing in test_logical_decoding
* actually parse option values in test_decoding instead of just using the
option name
* don't use anonymous structs in unions. That's compiler specific (msvc
and gcc) before C11 on which we can't rely. That unfortunately will
break output plugins because ReorderBufferChange need to qualify
old/new tuples now
* improve error handling/cleanup in test_logical_decoding
* some minor cleanups

Patches attached, git tree updated.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#46)
Re: logical changeset generation v6.1

On Tue, Oct 1, 2013 at 1:56 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.

Ok, I had to think a bit, but now I remember why I think these changes
are not really problem: Neither the addition of keys nor candidate keys
will add any additional comparisons since the columns compared for
candidate keys are a subset of the set of key columns which in turn are a
subset of the columns checked for HOT. Right?

TBH, my primary concern was with maintainability more than performance.

On performance, I think any time you add code it's going to cost
somehow. However, it might not be enough to care about.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#48)
Re: logical changeset generation v6.1

On 2013-10-02 10:56:38 -0400, Robert Haas wrote:

On Tue, Oct 1, 2013 at 1:56 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.

Ok, I had to think a bit, but now I remember why I think these changes
are not really problem: Neither the addition of keys nor candidate keys
will add any additional comparisons since the columns compared for
candidate keys are a subset of the set of key columns which in turn are a
subset of the columns checked for HOT. Right?

TBH, my primary concern was with maintainability more than performance.

On performance, I think any time you add code it's going to cost
somehow. However, it might not be enough to care about.

The easy alternative seems to be to call such a function multiple times
- which I think is prohibitive from a performance POV. More radically we
could simply compute the overall set/bitmap of differening columns and
then use bms_is_subset() to determine whether any index columns/key/ckey
columns changed. But that will do comparisons we don't do today...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#49)
Re: logical changeset generation v6.1

On Wed, Oct 2, 2013 at 11:05 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-02 10:56:38 -0400, Robert Haas wrote:

On Tue, Oct 1, 2013 at 1:56 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.

Ok, I had to think a bit, but now I remember why I think these changes
are not really problem: Neither the addition of keys nor candidate keys
will add any additional comparisons since the columns compared for
candidate keys are a subset of the set of key columns which in turn are a
subset of the columns checked for HOT. Right?

TBH, my primary concern was with maintainability more than performance.

On performance, I think any time you add code it's going to cost
somehow. However, it might not be enough to care about.

The easy alternative seems to be to call such a function multiple times
- which I think is prohibitive from a performance POV. More radically we
could simply compute the overall set/bitmap of differening columns and
then use bms_is_subset() to determine whether any index columns/key/ckey
columns changed. But that will do comparisons we don't do today...

Yeah, there may be no better alternative to doing things as you've
done them here. It just looks grotty, so I was hoping we had a better
idea. Maybe not.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#50)
Re: logical changeset generation v6.1

On 2013-10-02 11:06:59 -0400, Robert Haas wrote:

On Wed, Oct 2, 2013 at 11:05 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-02 10:56:38 -0400, Robert Haas wrote:

On Tue, Oct 1, 2013 at 1:56 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.

Ok, I had to think a bit, but now I remember why I think these changes
are not really problem: Neither the addition of keys nor candidate keys
will add any additional comparisons since the columns compared for
candidate keys are a subset of the set of key columns which in turn are a
subset of the columns checked for HOT. Right?

TBH, my primary concern was with maintainability more than performance.

On performance, I think any time you add code it's going to cost
somehow. However, it might not be enough to care about.

The easy alternative seems to be to call such a function multiple times
- which I think is prohibitive from a performance POV. More radically we
could simply compute the overall set/bitmap of differening columns and
then use bms_is_subset() to determine whether any index columns/key/ckey
columns changed. But that will do comparisons we don't do today...

Yeah, there may be no better alternative to doing things as you've
done them here. It just looks grotty, so I was hoping we had a better
idea. Maybe not.

Imo the code now looks easier to understand - which is not saying much -
than in 9.3/HEAD...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#47)
Re: logical changeset generation v6.2

On 2013-10-01 16:11:47 -0400, Steve Singer wrote:

On 09/30/2013 06:44 PM, Andres Freund wrote:

Hi,

The series from friday was a bit too buggy - obviously I was too
tired. So here's a new one:

With this series I've also noticed
#2 0x00000000007741a7 in ExceptionalCondition (
conditionName=conditionName@entry=0x7c2908 "!(!(tuple->t_infomask &
0x1000))", errorType=errorType@entry=0x7acc70 "FailedAssertion",
fileName=fileName@entry=0x91767e "tqual.c",
lineNumber=lineNumber@entry=1608) at assert.c:54
54 abort();

0x00000000007a4432 in HeapTupleSatisfiesMVCCDuringDecoding (
htup=0x10bfe48, snapshot=0x108b3d8, buffer=310) at tqual.c:1608
#4 0x000000000049d6b7 in heap_hot_search_buffer (tid=tid@entry=0x10bfe4c,
relation=0x7fbebbcd89c0, buffer=310, snapshot=0x10bfda0,
heapTuple=heapTuple@entry=0x10bfe48,
all_dead=all_dead@entry=0x7fff4aa3866f "\001\370\375\v\001",
first_call=1 '\001') at heapam.c:1756
#5 0x00000000004a8174 in index_fetch_heap (scan=scan@entry=0x10bfdf8)
at indexam.c:539
#6 0x00000000004a82a8 in index_getnext (scan=0x10bfdf8,
direction=direction@entry=ForwardScanDirection) at indexam.c:622
#7 0x00000000004a6fa9 in systable_getnext (sysscan=sysscan@entry=0x10bfd48)
at genam.c:343
#8 0x000000000076df40 in RelidByRelfilenode (reltablespace=0,
relfilenode=529775) at relfilenodemap.c:214
---Type <return> to continue, or q <return> to quit---
#9 0x0000000000664ad7 in ReorderBufferCommit (rb=0x1082d98,
xid=<optimized out>, commit_lsn=4638756800, end_lsn=<optimized out>,
commit_time=commit_time@entry=433970378426176) at reorderbuffer.c:1320

Does your code use SELECT FOR UPDATE/SHARE on system or treat_as_catalog
tables?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#52)
Re: logical changeset generation v6.2

On 10/03/2013 12:38 PM, Andres Freund wrote:

Does your code use SELECT FOR UPDATE/SHARE on system or
treat_as_catalog tables? Greetings, Andres Freund

Yes.
It declares sl_table and sl_sequence and sl_set as catalog.

It does a
SELECT ......
from @NAMESPACE@.sl_table T, @NAMESPACE@.sl_set S,
"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,
"pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC
where ... for update

in the code being executed by the 'set add table'.

(We also do select for update commands in many other places during
cluster configuration commands)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#53)
Re: logical changeset generation v6.2

On 2013-10-03 13:03:07 -0400, Steve Singer wrote:

On 10/03/2013 12:38 PM, Andres Freund wrote:

Does your code use SELECT FOR UPDATE/SHARE on system or treat_as_catalog
tables? Greetings, Andres Freund

Yes.
It declares sl_table and sl_sequence and sl_set as catalog.

It does a
SELECT ......
from @NAMESPACE@.sl_table T, @NAMESPACE@.sl_set S,
"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,
"pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC
where ... for update

in the code being executed by the 'set add table'.

(We also do select for update commands in many other places during cluster
configuration commands)

Ok, there were a couple of bugs because I thought mxacts wouldn't need
to be supported. So far your testcase doesn't crash the database
anymore - it spews some internal errors though, so I am not sure if it's
entirely fixed for you.

Thanks for testing and helping!

I've pushed the changes to the git tree, they aren't squashed yet and
there's some further outstanding stuff, so I won't repost the series yet.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#43)
13 attachment(s)
Re: logical changeset generation v6.1

Hi,

On 2013-10-01 10:07:19 -0400, Robert Haas wrote:

- It seems that HeapSatisfiesHOTandKeyUpdate is now
HeapSatisfiesHOTandKeyandCandidateKeyUpdate. Considering I think this
was merely HeapSatisfiesHOTUpdate a year ago, it's hard not to be
afraid that something unscalable is happening to this function. On a
related node, any overhead added here costs broadly; I'm not sure if
there's enough to worry about.

I haven't changed anything here - ISTM so far nobody had a better
suggestion.

- RelationIsDoingTimetravel is still a crappy name. How about
RelationRequiredForLogicalDecoding? And maybe the reloption
treat_as_catalog_table can become required_for_logical_decoding.

Hm. I don't really like the name, required seems to imply that it's
necessary to turn this on to get data replicated in that relation. How
about "accessible_during_logical_decoding" or "user_catalog_table"? The
latter would allow us to use it to add checks for user relations used in
indexes which need a treatment similar to enums.

All in all, it seems to me that we shouldn't try to punt. Maybe we
should have something that works like ALTER TABLE name CLUSTER ON
index_name to configure which index should be used for logical
replication. Possibly this same syntax could be used as ALTER
MATERIALIZED VIEW to set the candidate key for that case.

How about using the current logic by default but allow to tune it
additionally with an option like that?

So, attached is the new version:
Changes:
* Fix issues you noticed except the above
* Handle multixacts on system tables
* Logical slots now are checksummed and contain a version and length
* Improve logic for increasing the "restart lsn", the point where we
start to read WAL to decode from next time round
* Wait for xids in snapbuild, during the initial build
* s/RelationIsDoingTimetravel/RelationRequiredForLogicalDecoding/
* test_logical_decoding: confirm reception of changes at the end
* prohibit rewriting schema changes for treat_as_catalog_table relations
* add tests for dropping/adding primary/candidate keys
* PROCESS_INTERRUPTS whene reading wal for SQL SRF
* cleanup old serialized snapshots at check/restart points
* Add more isolationtester changes

Todo:
* rename treat_as_catalog_table, after agreeing on the new name
* rename remaining timetravel function names
* restrict SuspendDecodingSnapshots usage to RelationInitPhysicalAddr,
that ought to be enough.
* add InLogicalDecoding() function.
* throw away older data when reading xl_running_xacts records, to deal
with immediate shutdowns/crashes

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Fix-pg_isolation_regress-to-work-outside-its-build-d.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Add-information-about-a-tables-primary-.patch.gzapplication/x-patch-gzipDownload
0005-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-Add-option-to-treat-additional-tables-a.patch.gzapplication/x-patch-gzipDownload
0007-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0011-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0012-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
0013-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
#56Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#54)
Re: logical changeset generation v6.2

On 10/03/2013 04:00 PM, Andres Freund wrote:

Ok, there were a couple of bugs because I thought mxacts wouldn't need
to be supported. So far your testcase doesn't crash the database
anymore - it spews some internal errors though, so I am not sure if
it's entirely fixed for you. Thanks for testing and helping! I've
pushed the changes to the git tree, they aren't squashed yet and
there's some further outstanding stuff, so I won't repost the series
yet. Greetings, Andres Freund

When I run your updated version (from friday, not what you posted today)
against a more recent version of my slony changes I can get the test
case to pass 2/3 'rd of the time. The failures are due to an issue in
slon itself that I need to fix.

I see lots of
0LOG: tx with subtxn 58836

but they seem harmless.

Thanks

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#56)
Re: logical changeset generation v6.2

On 2013-10-07 09:56:11 -0400, Steve Singer wrote:

On 10/03/2013 04:00 PM, Andres Freund wrote:

Ok, there were a couple of bugs because I thought mxacts wouldn't need to
be supported. So far your testcase doesn't crash the database anymore - it
spews some internal errors though, so I am not sure if it's entirely fixed
for you. Thanks for testing and helping! I've pushed the changes to the
git tree, they aren't squashed yet and there's some further outstanding
stuff, so I won't repost the series yet. Greetings, Andres Freund

When I run your updated version (from friday, not what you posted today)
against a more recent version of my slony changes I can get the test case to
pass 2/3 'rd of the time. The failures are due to an issue in slon itself
that I need to fix.

Cool.

I see lots of
0LOG: tx with subtxn 58836

Yes, those are completely harmless. And should, in fact, be removed. I
guess I should add the todo entry:
* make a pass over all elog/ereport an make sure they have the correct
log level et al.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#55)
Re: logical changeset generation v6.1

On Mon, Oct 7, 2013 at 9:32 AM, Andres Freund <andres@2ndquadrant.com> wrote:

- RelationIsDoingTimetravel is still a crappy name. How about
RelationRequiredForLogicalDecoding? And maybe the reloption
treat_as_catalog_table can become required_for_logical_decoding.

Hm. I don't really like the name, required seems to imply that it's
necessary to turn this on to get data replicated in that relation. How
about "accessible_during_logical_decoding" or "user_catalog_table"? The
latter would allow us to use it to add checks for user relations used in
indexes which need a treatment similar to enums.

user_catalog_table is a pretty good description, but should we worry
about the fact that logical replication isn't mentioned in there
anywhere?

In what way do you feel that it's more clear to say *accessible
during* rather than *required for* logical decoding?

I was trying to make the naming consistent; i.e. if we have
RelationRequiredForLogicalDecoding then name the option to match.

All in all, it seems to me that we shouldn't try to punt. Maybe we
should have something that works like ALTER TABLE name CLUSTER ON
index_name to configure which index should be used for logical
replication. Possibly this same syntax could be used as ALTER
MATERIALIZED VIEW to set the candidate key for that case.

How about using the current logic by default but allow to tune it
additionally with an option like that?

I'm OK with defaulting to the primary key if there is one, but I think
that no other candidate key should be entertained unless the user
configures it. I think the behavior we get without that will be just
too weird. We could use the same logic you're proposing here for
CLUSTER, too, but we don't; that's because we've (IMHO, rightly)
decided that the choice of index is too important to be left to
chance.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#55)
Re: logical changeset generation v6.1

On 10/07/2013 09:32 AM, Andres Freund wrote:

Todo:
* rename treat_as_catalog_table, after agreeing on the new name
* rename remaining timetravel function names
* restrict SuspendDecodingSnapshots usage to RelationInitPhysicalAddr,
that ought to be enough.
* add InLogicalDecoding() function.
* throw away older data when reading xl_running_xacts records, to deal
with immediate shutdowns/crashes

What is your current plan for decoding sequence updates? Is this
something that you were going to hold-off on supporting till a future
version? ( know this was discussed a while ago but I don't remember
where it stands now)

From a Slony point of view this isn't a big deal, I can continue to
capture sequence changes in sl_seqlog when I create each SYNC event and
then just replicate the INSERT statements in sl_seqlog via logical
decoding. I can see why someone building a replication system not based
on the concept of a SYNC would have a harder time with this.

I am guessing we would want to pass sequence operations to the plugins
as we encounter the WAL for them out-of-band of any transaction. This
would mean that a set of operations like

begin;
insert into a (id) values(4);
insert into a (id) values(nextval('some_seq'));
commit

would be replayed on the replicas as
setval('some_seq',100);
begin;
insert into a (id) values (4);
insert into a (id) values (100);
commit;

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#59)
Re: logical changeset generation v6.1

On 2013-10-08 15:02:39 -0400, Steve Singer wrote:

On 10/07/2013 09:32 AM, Andres Freund wrote:

Todo:
* rename treat_as_catalog_table, after agreeing on the new name
* rename remaining timetravel function names
* restrict SuspendDecodingSnapshots usage to RelationInitPhysicalAddr,
that ought to be enough.
* add InLogicalDecoding() function.
* throw away older data when reading xl_running_xacts records, to deal
with immediate shutdowns/crashes

What is your current plan for decoding sequence updates? Is this something
that you were going to hold-off on supporting till a future version? ( know
this was discussed a while ago but I don't remember where it stands now)

I don't plan to implement it as part of this - the optimizations in
sequences make it really unsuitable for that (nontransaction, allocated
in bulk, ...).
Simon had previously posted about "sequence AMs", and I have a prototype
patch that implements that concept (which needs considerable cleanup). I
plan to post about it whenever this is finished.

I think many replication solutions that care about sequences in a
nontrivial will want to implement their own sequence logic anyway, so I
think that's not a bad path.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#58)
Re: logical changeset generation v6.1

On 2013-10-08 12:20:22 -0400, Robert Haas wrote:

On Mon, Oct 7, 2013 at 9:32 AM, Andres Freund <andres@2ndquadrant.com> wrote:

- RelationIsDoingTimetravel is still a crappy name. How about
RelationRequiredForLogicalDecoding? And maybe the reloption
treat_as_catalog_table can become required_for_logical_decoding.

Hm. I don't really like the name, required seems to imply that it's
necessary to turn this on to get data replicated in that relation. How
about "accessible_during_logical_decoding" or "user_catalog_table"? The
latter would allow us to use it to add checks for user relations used in
indexes which need a treatment similar to enums.

user_catalog_table is a pretty good description, but should we worry
about the fact that logical replication isn't mentioned in there
anywhere?

I personally don't worry about it, although I see why somebody could.

In what way do you feel that it's more clear to say *accessible
during* rather than *required for* logical decoding?

Because "required for" can easily be understood that you need to set it
if you want a table's changes to be replicated. Which is not the case...

I was trying to make the naming consistent; i.e. if we have
RelationRequiredForLogicalDecoding then name the option to match.

Maybe this should be RelationAccessibleInLogicalDecoding() then - that
seems like a better description anyway?

All in all, it seems to me that we shouldn't try to punt. Maybe we
should have something that works like ALTER TABLE name CLUSTER ON
index_name to configure which index should be used for logical
replication. Possibly this same syntax could be used as ALTER
MATERIALIZED VIEW to set the candidate key for that case.

How about using the current logic by default but allow to tune it
additionally with an option like that?

I'm OK with defaulting to the primary key if there is one, but I think
that no other candidate key should be entertained unless the user
configures it. I think the behavior we get without that will be just
too weird. We could use the same logic you're proposing here for
CLUSTER, too, but we don't; that's because we've (IMHO, rightly)
decided that the choice of index is too important to be left to
chance.

I don't understand why this would be a good path. If you DELETE/UPDATE
and you don't have a primary key you get something that definitely
identifies the row with the current behaviour. It might not be the best
thing, but it sure is better than nothing. E.g. for auditing it's
probably quite sufficient to just use any of the candidate keys if
there (temporarily) is no primary key.
If you implement a replication solution and don't want that behaviour
there, you are free to guard against it there - which is a good thing
to do.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#42)
Re: logical changeset generation v6.2

On Mon, Sep 30, 2013 at 6:44 PM, Andres Freund <andres@2ndquadrant.com> wrote:

The series from friday was a bit too buggy - obviously I was too
tired. So here's a new one:

* fix pg_recvlogical makefile (Thanks Steve)
* fix two commits not compiling properly without later changes (Thanks Kevin)
* keep track of commit timestamps
* fix bugs with option passing in test_logical_decoding
* actually parse option values in test_decoding instead of just using the
option name
* don't use anonymous structs in unions. That's compiler specific (msvc
and gcc) before C11 on which we can't rely. That unfortunately will
break output plugins because ReorderBufferChange need to qualify
old/new tuples now
* improve error handling/cleanup in test_logical_decoding
* some minor cleanups

Patches attached, git tree updated.

I spent some time looking at the sample plugin (patch 9/12). Here are
some review comments:

- I think that the decoding plugin interface should work more like the
foreign data wrapper interface. Instead of using pg_dlsym to look up
fixed names, I think there should be a struct of function pointers
that gets filled in and registered somehow.

- pg_decode_init() only warns when it encounters an unknown option.
An error seems more appropriate.

- Still wondering how we'll use this from a bgworker.

- The output format doesn't look very machine-parseable. I really
think we ought to provide something that is. Maybe a CSV-like format,
or maybe something else, but I don't see why someone who wants to do
change logging should be forced to write and install C code. If
something like Bucardo can run on an unmodified system and extract
change-sets this way without needing a .so file, that's going to be a
huge win for usability.

Other than that, I don't have too many concerns about the plugin
interface. I think it provides useful flexibility and it generally
seems well-designed. I hope in the future we'll be able to decode
transactions on the fly instead of waiting until commit time, but I've
resigned myself to the fact that we may not get that in version one.

More generally on this patch set, if I'm going to be committing any of
this, I'd prefer to start with what is currently patches 3 and 4, once
we reach agreement on those.

Are we hoping to get any of this committed for this CF? If so, let's
make a plan to get that done; time is short. If not, let's update the
CF app accordingly.

Thanks,

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#62)
Re: logical changeset generation v6.2

Hi Robert,

On 2013-10-09 14:49:46 -0400, Robert Haas wrote:

I spent some time looking at the sample plugin (patch 9/12). Here are
some review comments:

- I think that the decoding plugin interface should work more like the
foreign data wrapper interface. Instead of using pg_dlsym to look up
fixed names, I think there should be a struct of function pointers
that gets filled in and registered somehow.

You mean something like CREATE OUTPUT PLUGIN registering a function with
an INTERNAL return value returning a filled struct? I thought about
that, but it seemed more complex. Happy to change it though if it's
preferred.

- pg_decode_init() only warns when it encounters an unknown option.
An error seems more appropriate.

Fine with me. I think I just made it a warning because I wanted to
experiment with options.

- Still wondering how we'll use this from a bgworker.

Simplified code to consume data:

LogicalDecodingReAcquireSlot(NameStr(*name));
ctx = CreateLogicalDecodingContext(MyLogicalDecodingSlot, false /* not initial call */,
MyLogicalDecodingSlot->confirmed_flush,
options,
logical_read_local_xlog_page,
LogicalOutputPrepareWrite,
LogicalOutputWrite);
...
while (true)
{
XLogRecord *record;
char *errm = NULL;

record = XLogReadRecord(ctx->reader, startptr, &errm);
...
DecodeRecordIntoReorderBuffer(ctx, &buf);
}

/* at the end or better ever commit or such */
LogicalConfirmReceivedLocation(/* whatever you consumed */);

LogicalDecodingReleaseSlot();

- The output format doesn't look very machine-parseable. I really
think we ought to provide something that is. Maybe a CSV-like format,
or maybe something else, but I don't see why someone who wants to do
change logging should be forced to write and install C code. If
something like Bucardo can run on an unmodified system and extract
change-sets this way without needing a .so file, that's going to be a
huge win for usability.

We can change the current format but I really see little to no chance of
agreeing on a replication format that's serviceable to several solutions
short term. Once we've gained some experience - maybe even this cycle -
that might be different.

More generally on this patch set, if I'm going to be committing any of
this, I'd prefer to start with what is currently patches 3 and 4, once
we reach agreement on those.

Sounds like a reasonable start.

Are we hoping to get any of this committed for this CF? If so, let's
make a plan to get that done; time is short. If not, let's update the
CF app accordingly.

I'd really like to do so. I am travelling atm, but I will be back
tomorrow evening and will push an updated patch this weekend. The issue
I know of in the latest patches at
/messages/by-id/20131007133232.GA15202@awork2.anarazel.de
is renaming from /messages/by-id/20131008194758.GB3718183@alap2.anarazel.de

Do you know of anything else in the patches you're referring to?

Thanks,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#63)
Re: logical changeset generation v6.2

On Thu, Oct 10, 2013 at 7:11 PM, Andres Freund <andres@2ndquadrant.com> wrote:

Hi Robert,

On 2013-10-09 14:49:46 -0400, Robert Haas wrote:

I spent some time looking at the sample plugin (patch 9/12). Here are
some review comments:

- I think that the decoding plugin interface should work more like the
foreign data wrapper interface. Instead of using pg_dlsym to look up
fixed names, I think there should be a struct of function pointers
that gets filled in and registered somehow.

You mean something like CREATE OUTPUT PLUGIN registering a function with
an INTERNAL return value returning a filled struct? I thought about
that, but it seemed more complex. Happy to change it though if it's
preferred.

I don't see any need for SQL syntax. I was just thinking that the
_PG_init function could fill in a structure and then call
RegisterLogicalReplicationOutputPlugin(&mystruct).

- Still wondering how we'll use this from a bgworker.

Simplified code to consume data:

Cool. As long as that use case is supported, I'm happy; I just want
to make sure we're not presuming that there must be an external
client.

- The output format doesn't look very machine-parseable. I really
think we ought to provide something that is. Maybe a CSV-like format,
or maybe something else, but I don't see why someone who wants to do
change logging should be forced to write and install C code. If
something like Bucardo can run on an unmodified system and extract
change-sets this way without needing a .so file, that's going to be a
huge win for usability.

We can change the current format but I really see little to no chance of
agreeing on a replication format that's serviceable to several solutions
short term. Once we've gained some experience - maybe even this cycle -
that might be different.

I don't see why you're so pessimistic about that. I know you haven't
worked it out yet, but what makes this harder than sitting down and
designing something?

More generally on this patch set, if I'm going to be committing any of
this, I'd prefer to start with what is currently patches 3 and 4, once
we reach agreement on those.

Sounds like a reasonable start.

Perhaps you could reshuffle the order of the series, if it's not too much work.

Are we hoping to get any of this committed for this CF? If so, let's
make a plan to get that done; time is short. If not, let's update the
CF app accordingly.

I'd really like to do so. I am travelling atm, but I will be back
tomorrow evening and will push an updated patch this weekend. The issue
I know of in the latest patches at
/messages/by-id/20131007133232.GA15202@awork2.anarazel.de
is renaming from /messages/by-id/20131008194758.GB3718183@alap2.anarazel.de

I'm a bit nervous about the way the combo CID logging. I would have
thought that you would emit one record per combo CID, but what you're
apparently doing is emitting one record per heap tuple that uses a
combo CID. For some reason that feels like an abuse (and maybe kinda
inefficient, too).

Either way, I also wonder what happens if a (logical?) checkpoint
occurs between the combo CID record and the heap record to which it
refers, or how you prevent that from happening. What if the combo CID
record is written and the transaction aborts before writing the heap
record (maybe without writing an abort record to WAL)?

What are the performance implications of this additional WAL logging?
What's the worst case? What's the typical case? Does it have a
noticeable overhead when wal_level < logical?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#64)
Re: logical changeset generation v6.2

Hi,

On 2013-10-11 09:08:43 -0400, Robert Haas wrote:

On Thu, Oct 10, 2013 at 7:11 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-09 14:49:46 -0400, Robert Haas wrote:

I spent some time looking at the sample plugin (patch 9/12). Here are
some review comments:

- I think that the decoding plugin interface should work more like the
foreign data wrapper interface. Instead of using pg_dlsym to look up
fixed names, I think there should be a struct of function pointers
that gets filled in and registered somehow.

You mean something like CREATE OUTPUT PLUGIN registering a function with
an INTERNAL return value returning a filled struct? I thought about
that, but it seemed more complex. Happy to change it though if it's
preferred.

I don't see any need for SQL syntax. I was just thinking that the
_PG_init function could fill in a structure and then call
RegisterLogicalReplicationOutputPlugin(&mystruct).

Hm. We can do that, but what'd be the advantage of that? The current
model will correctly handle things like a'shared_preload_libraries'ed
output plugin, because its _PG_init() will not register it. With the
handling done in _PG_init() there would be two.
Being able to use the same .so for output plugin handling and some other
replication solution specific stuff is imo useful.

- Still wondering how we'll use this from a bgworker.

Simplified code to consume data:

Cool. As long as that use case is supported, I'm happy; I just want
to make sure we're not presuming that there must be an external
client.

The included testcases are written using the SQL SRF interface, which in
turn is a usecase that doesn't use walsenders and such, so I hope we
won't break it accidentally ;)

- The output format doesn't look very machine-parseable. I really
think we ought to provide something that is. Maybe a CSV-like format,
or maybe something else, but I don't see why someone who wants to do
change logging should be forced to write and install C code. If
something like Bucardo can run on an unmodified system and extract
change-sets this way without needing a .so file, that's going to be a
huge win for usability.

We can change the current format but I really see little to no chance of
agreeing on a replication format that's serviceable to several solutions
short term. Once we've gained some experience - maybe even this cycle -
that might be different.

I don't see why you're so pessimistic about that. I know you haven't
worked it out yet, but what makes this harder than sitting down and
designing something?

Because every replication solution has different requirements for the
format and they will want filter the output stream with regard to their
own configuration.
E.g. bucardo will want to include the transaction timestamp for conflict
resolution and such.

More generally on this patch set, if I'm going to be committing any of
this, I'd prefer to start with what is currently patches 3 and 4, once
we reach agreement on those.

Sounds like a reasonable start.

Perhaps you could reshuffle the order of the series, if it's not too much work.

Sure, that's no problem. Do I understand correctly that you'd like
wal_decoding: Add information about a tables primary key to struct RelationData
wal_decoding: Add wal_level = logical and log data required for logical decoding

earlier?

I'd really like to do so. I am travelling atm, but I will be back
tomorrow evening and will push an updated patch this weekend. The issue
I know of in the latest patches at
/messages/by-id/20131007133232.GA15202@awork2.anarazel.de
is renaming from /messages/by-id/20131008194758.GB3718183@alap2.anarazel.de

I'm a bit nervous about the way the combo CID logging. I would have
thought that you would emit one record per combo CID, but what you're
apparently doing is emitting one record per heap tuple that uses a
combo CID.

I thought and implemented that in the beginning. Unfortunately it's not
enough :(. That's probably the issue that took me longest to understand
in this patchseries...

Combocids can only fix the case where a transaction actually has create
a combocid:

1) TX1: INSERT id = 1 at 0/1: (xmin = 1, xmax=Invalid, cmin = 55, cmax = Invalid)
2) TX2: DELETE id = 1 at 0/1: (xmin = 1, xmax=2, cmin = Invalid, cmax = 1)

So, if we're decoding data that needs to lookup those rows in TX1 or TX2
we both times need access to cmin and cmax, but neither transaction will
have created a multixact. That can only be an issue in transaction with
catalog modifications.

A slightly more complex variant also requires this if combocids are
involved:

1) TX1: INSERT id = 1 at 0/1: (xmin = 1, xmax=Invalid, cmin = 55, cmax = Invalid)
2) TX1: SAVEPOINT foo;
3) TX1-2: UPDATE id = 1 at 0/1: (xmin = 1, xmax=2, cmin = 55, cmax = 56, combo=123)
new at 0/1: (xmin = 2, xmax=Invalid, cmin = 57, cmax = Invalid)
4) TX1-2: ROLLBACK TO foo;
5) TX3: DELETE id = 1 at 0/1: (xmin = 1, xmax=3, cmin = Invalid, cmax = 1)

If you're decoding data that's been inserted after 1) you still need to
see cmin = 55. At 5) you need to see cmin = Invalid.

So just remembering the correct value for each tuple that's needed for a
specific transaction seems like the simplest way here.

Either way, I also wonder what happens if a (logical?) checkpoint
occurs between the combo CID record and the heap record to which it
refers, or how you prevent that from happening.

Logical checkpoints contain the so called 'restart_decoding' LSN, which
is defined to be the LSN at which we can restart reading WAL and are
guaranteed to be able to decode all transactions that haven't been
confirmed as received.
Normal checkpoints shouldn't play any role here.

What if the combo CID record is written and the transaction aborts
before writing the heap record (maybe without writing an abort record
to WAL)?

In the currently implemented model where we log (relfilenode, ctid,
cmin, cmax) we only ever need access to those rows when decoding data
changes from *within* the catalog modifying toplevel transaction. Never
in other toplevle transactions.

What are the performance implications of this additional WAL logging?
What's the worst case?

I haven't been able to notice any difference above jitter for anything
that touches actual relations. The overhead there is far bigger than
that single XLogInsert().
Maybe there's something that doesn't interact with much of the system
where the effort is noticeable and which is actually relvant for
performance? I couldn't really think of something.

noticeable overhead when wal_level < logical?

Couldn't measure anything either, which is not surprising that I
couldn't measure the overhead in the first place.

I've done some parallel INSERT/DELETE pgbenching around the
wal_level=logical and I couldn't measure any overhead with it
disabled. With wal_level = logical, UPDATEs and DELETEs do get a bit
slower, but that's to be expected.

It'd probably not hurt to redo those benchmarks to make sure...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#1)
13 attachment(s)
Re: logical changeset generation v6.4

Hi,

Attached you can find version 6.4 of the patchset:

* reordered so the patches Robert wants to apply first are first
* renamed treat_as_catalog_table to user_catalog_table
* renamed RelationRequiredForLogicalDecoding to RelationIsAccessibleInLogicalDecoding
* moved two hunks to better fitting patches

I am working on the longer TODOs from the last version now, but they
don't affect the first patches.

Greetings,

Andres Freund

PS: git rebase -i -x /path/to/testscript is cool

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0013-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
���[R0013-wal_decoding-design-document-v2.4-and-snapshot-build.patch�[�w����������Y��$������>��lg���LDBj�T	R�:��}~����q�7�m�p�W?��\L�{��*|~�T��J���GG����|:~����8|~�?�<���Z��3��?�����~��3��a����S���x)���d,S���:���q"35�sZ�w(.��{}|��
�����7��Od��jx{�������{����PI���@���i,�$��*����s(S�X.�,�b���W���~�i��n���Aw,�;��T-"�L'q7J��uOF7�o.:��L��{��E_4��<~���S)!*�	q$����l�s=���:7���oO�N*���������:��`&��
[������Q)i�4w"HD �I�Do����+��M��Q�h�z2��TgBv�B��X����YR����P}���O�>;���C��
���Q�?��O?��~k_4{-���~j4���^�A�$�����+������Z�H�X��$�8�g��d'��M��^5�� Y�S��e��7�<o�d,�,��,���I� �:�.�g�$�0��8���2L%��ea+�\�h ��Vt�d�h�W�4��$�F��+zz;���C��Fd	��L���H���l-VJd3���'*V*4��d3�6��D9 �^�d����xl�N2���X���H:���������v�;�3�J���,��U(�|)B�v���X	��G�X(�F�/Ujl2�����`&2��`9&XXj0h��q�if��m�����,
KfRLU�R^��n!�H��\�-����"Z��#d�?0��8�'����G�2aAc��3���$'V"�x�*#�`�&Q�f�&���Aw���vOn�I/�J�,SB�(�
P��������{@.�.����I�C�B:�L�����U��:�����!�`E^cU����l�R?�tJ
���C�Jaa�����LKx
�S�d��bb��i0��X�*h[��l1�v��R��'�������\#����.Y��Q���<���8��F�W�(�mq��'���U�����A��?�u��D����=�N������L�`-�<%V:UT�`f��L(	�mx������,�<HM2���wI6���;�i
	u���UEdU�Th������=�{b�8��dik�b3�.��_�NU���&UNb�c����lNG]$��*��PS�|����K6�v)0g2��?�v���	�F�����u����0��
eD�Z�����I����KM�B��q>��HD�9l'OII�����"h�ApQ���>BlD�\���H
MlS���Zy�Yx�������?�o#0Kx�m� oF���F[��
���Y���[�f"}�p<hD����IA�J�A��eu�p3m4)�P��_��citP�����3�1$�\����P����	�<�^F$,~����
�$I2���.'00�@����@I�%~E7P�p�n�\���P����B��a�'�5��;���G5!��������&��d��@��i_������d1\�wG�A(3�29�ol6dE��!
��:�Xj�<�!�:�}�)���\�w�(I}l����~�t�����d�"*�xc�	��a��d0K�jJ��+R��(��
�N~FO��BZ���������f!Eo�M��P�%s�7?�3'3v���YOk6�O�4O�L���7E�v�R��:��[9Q>fZ�d��1����`����-��U�3���$n{�!�%�x-a���Z~8�F[$4���9�k�d��Q�� ,X�C�}
���#E����Q�$��A��n]��D@_*'"�*���G��*�����'���o����?vi�Y��,Mb����T��x
�.��4��b_�-��W{��SL<��>��D3�u{�L��pl��YJ��dk����}��,T`�@��PK��l�`N5K��27�]��hN#�^���Y��'��q����d"����~+�?
(_�+W9�#�+�
c��g�P�?�1���v�
�������iB�3�G�u��54�,�t8NHy�,#K��^���d>��Z�[�R"�������U����$�Q;
��C K��2]$�19���~6W���PTxb
JDX���H�2Z��:%��j��Z��yE�t$
R�DG�r�N�P��|:�����r���M�f��Y���tl�����������XM�M�#�]����nx���������c!���	�:�C>���E	%,�"��P�����D��t��*WX����������R������H9�(\�*2��8�����
�#������F��E��t"i�o�yeW�O��A9��u��YT]�\l4�p�����@������q����BU6 ���9����}'��:���|�[b�t�)�NB�^��=(��bf2��"�Qm�fd�N�<zK�%��1{������z��6�����=��G�k��h����A�x�P\�7�ij��IS=���E���Z�@ZSM|qK�������-�.���M���2��!�|m�fx/��&O�j�u��d�}��QV��aX�h�d\hSc��CM����:����t.�l1i4��[�0�0Z>���G6+������TR��\Vt���:E��A]bi�����Gd�l������T0��R.aH�\�}"n���������n����Q�������q�g���@������4��xZ&Y-�1�Ds�@-.�J��j5��%8��x���e�C�jS�$���S0�8�T�B{P�Zz�D�*���r��:�dDv�.;|�d�T91cl��W�-vxH�U'�6�"6|8��Z<k�R(���������o��r��qC�*�t�%.��"��'/��/������&����L���V�w�,<�k�Z[lfe��}-��<W�9t������������'k9���*m	��p��P��� sDk�"�����������A&�YKN4`92j�s�gH�*��:�AJ��;�jy8�����{���G�����P)~�30��C����A���>e����������t	�����pO�����WHm��)]�F�mlV��G���)g��G��E�r�|D�P�O�����������Sk~A�%M�&��6��tr�����9�S���&E���d>���c�3����GN�sE=�"f.��g��\�u5���1�m�G���Q�A�/X����?P��_��h�{��T��M�O&��(�}�'�p/^���>
�,Y�����=���f��k����P��?T?�O�~����+Pn>���.@�W�o�s�+x����l�^���`us��W��?��0�/?x����8z��������'U�\Q^��=5�R$���2!(��c��C���������<����,��[i&:V�V~����b{���"+�oQ;��y��?����-�j?uiQ�������I�f/���un�O���g��l����q!Q��!��[����C�����_V�le����xh��,*WA�B�%����"�=�6��N����W:y58����_g���so���3|�<���m���g:�����@���FI�Q�q������u��7����n�s�(n�6���N���6��T�k������]���d'T�����<���s���&��-�m{V������'��8��Q&��E���k����S�F���F-lI����<���>�U1�ulk.�>'�
;�A�.����,{	�&+N
�2/�e��	A�������rT�G��u\��9w��\��xlm'��jFQ4"��
��$Rx�j4���6��+j�(�H�������]F��\3�4
;�������p`�RI�\������l;�uT�M�m���rZW.��6OV�5��F�f����%`�A���PE%aW�I�hW.N����o��jS*Bb:�+=�/P[�3D���V���e/�+�+�r�M��;����M�����!�(}/?�A��B��b��4W�o��(O��R�e@�����l
j�D51��R7���1WTli3w7#dn���/�H�qP�j���&O��,�<�0����`��H�V��o��*��UZr�,�����U��Df�,;	8��h
����mQH�uW��YV�<
~��Y2e)��rv0<OQ4�������@�F��<P��f��1�rJ�i,��5��X4�^��n�����"�#=�� ���V.���xBT�U�o�IE�B�������f��K����!���4E�����^��`\7h��VHK��q#��a�<%������qy���%�3�Ib�����<Y�[�|���?|vt�������a�Y�}D��mJh�<�~x���>+57���Pd�g��D�,��g�`���9���+�"��F���Y���U�Z[���%�ut���J�T��	��"���5aCq���"�ZX����6Z���k���e�i^;��Ia�#.�B<h
����u\�����6������������W�l����������4�4�K����9ef� ���kNHl����KtZ��M��D��Lw�5�T���_�K6O�%�;(9)v����:��B�������IFB���8���0a>�����k�p��I]�Q�U�E�iU�k9'�R�@���(�b��Ny�/��M�O4�kM�^������~[j���^^��������i��Gw�v��f�@gD�I��i�
�6��P�K�}�7������4�6y�JR-Oo���?^W�e��iRv�oE�K�����B9�L7$�^��K��2	k�b���}&���(���x�2��.k�,��}Sg�nto,�nF���y~:N6'kj�/�� y��
G[�+�N��:�FY�y���6�_�P)!W���D�n���E*��T�N#�L�R�o��1��bzP��s��A^R�n��9W�@���������QwpvU]j$���i�^�����U�Y����Y.]3�_R<t�G��$]F�"��^~�����l`���j'������'A�k/.oG�o�7�]7�fG��B��;��0W1KR�7���6���gg����������zt"v&;��h��9��������H�_���4���z��Wn���^��oO//��F�����m:�Y�~?�����~pD�������V,���|�a|��u��k7�y����������M���DCcG�������������v���Oh5�KR=�>���������^��[��[A���9����goG7�I�%z�������1&f���vt�0�bxr"�/���_����}��u8�ho��i��]>�k���x�N��s���s[����k�����-SS2��pb;���]�Z�}�� ���K�Y�y=&1��Rd��0}�p�*��I������P���p=���<#xf@	@���R������3���D��r�@�9��\��D����$V�_"b������\��>�����7���f0����K�����p�� ���c����m�|���5�����Q���0�';������g�wZ�y=��x�c��6��B��VXs���,'6k3:|G�{?�>�x@J��yy����p��H~�M46� ���~?����<o�aO;���=v�a�0�}�s��"�g���?�k���c09��c��-���o�q�}����Q��G<-Pq+�y$����VR*�_Z��ZK���������_n������/�Ni9��&�x)U���-����5��v�~��&� T��z ������m+�O���
(���fu���Q�-�����y��,�w��g�iy���mF7}/�����f�?��L�)��x>�v���4����G���_sW��F�d?�"��j�eWO�2m���s0���]3��N��X(i��L���O�o�^j��C�A��o�����,�YE��/:*�Q��$�@}|��~�x{U��m��zMER='k�[G��9\�������}O��:��>O^���`�l����w����mj��V�0�:�0,�!���v��,E�(�.��(|�1@f�����"/��I�mm���*�$r'���1CX���_��";X-��$<��6���(���z�t`�]��L��$��Q�l���A)7��Y����JW���F�Dl�:�c�y��<��������*+�!�$]���C��2���GX�/o����S�6}�1)�}~������%��i0��eU^;HV&�F"��������:tg��#y�|���a�r�����f�p���E<��f�^�Q� �Q�>�������~9�Y~������Xr:i��5��.�G��:"	�����lCb?`|���p�.�k>�tx�5�S�b����Y����������k�A�8
�f�r5����tI5+�9�,'��.�2K]��)@�7?��o�_�����a�Jt��h�R�5q���������������l��*Z������.�+�rr����y!����"c
3y<� N�X�v��=O� B�N������'�����>6��s�"2<�
�&����)��h�y�i����K��"���m��._jt8w�@��#�8nbB��3���r��� ���)@@�(��([c��X�?`�\���2��S���������n�I�`3�"�v��������c^��������Sw�n�#��s&E�	������L:����2�,A��S,����H��de����[�Zs��U��`<@�w�2&;0�2 4��I�c]�j���O���V`n��y��, �i�������+Mg�<>w\S"�GP���4C(�S��2PB��@����	h,�<����V0�D�]��jT��tL)%�p�Ix'�D5�1>�A��u���f���M�2��+#w��z���>��@'���	�M8��_D�e{�����y}�;q�D�
�c�@�$C��Q��ta������6�([r���mKI�C���b����yY���b2�"R@�3&�nd��Kdj���$����O�V>���O#����4�Y ���y������!���
�Y_���*�I��#�"`DL#{@�62N"!�����J��S�J�A��9�4����A7���T� R(�����K�b����J%J�|�B�a��J��������dB3+'�
�������������{�����������(j��I�C��y��n���f1I�(r�<�b1��h�e���L��kFw(�]�he|(q+��n��h�F��Z\�OJ��?V��J��p
!����	��S���������gU�`�.e��>T����Mt����'K.]�c�e(�S�\<��@�Y�z���bN�cS���n����~���A���8C�j)G�D�:���F��js��
8E\[��C��KK9_�$#h��X]C����	E��|�����@�W��R��u&�S����1�n��(R��YW������y��n�l�Q�C��q���8�P��W���G�W�6r��?�W�a>�v�x�
�6(^��R���e��m��D�g�������{�@>�k��?��)������]���t��%����l���=�xu���O�'5~�9`q�:������I��h
B��:����������C#�go�a��q�Tn
p�3*����lm����>�^����5�\�<;(�~3��j��mk����A��
�[����j��\��M�b�^�@hc�Rt��������	u�2�����vVU	 V4��������*�s@���3E7�6��w�+
��Uk�m3��(�ZA�c�/[�T�K��o�R�O��/��E��$�C��s�Dgor�Q���������<]��q�P�J\zm��|i�<�m�T#4����z����\������
�$��/Y���5^=���@�vJ�Q�-;e:nI8`�R���$��Q],�k=3u!�g�	8��M�$D�(2rH���� _@9:dr���< $��b!Gj�=��2O�7J)��6"����Xz�0
2�kuF��1�#Hqh�5�~�l���h=�M����0�a=�n�����=��:"u���kl{YZ�S����n�1���T��5�a�'F�s��vW��vk�9�TO�E�����2�x$-����Gz	[��q/��	��@}O�=���R��5CNc(u��U�Vs�Q��Fn*c����-��}L�������H���o��oD[O�3�;��;��r����=�Y`�w�N#1���=��8VW��$���27�20f�)
�����������M#A�>��g��/���V��b�h��$��C�/N��Spm=�!��?���m~�F[c�j��������J�����������y������������/�w�g��T�<$��{���==�`��H�3�(�t�Xv�S�~�d��*''(���P{m�&�kb u���\��J"�(�Jr��O���!Y�\;�E kR.�m0Q7�e������@R93b���16Z�@��G�����������t����{�i�e%�����Z�j���Y��vw���>��������/�G��i�A���,40�,����g�L>�o`Y���� ��g5�q��7�|�w�O���n��~�j��������g���S��-�N�wN����w��_�����V���B���H�]����Iz��
��Hn�}.R>b#%� ��������;X����s�$������w4�|���_���:]a.�Gq�N��
��9G��O�^��lo-�����a)�~��
������������P~���d>�4��<���>X�
���]Tw��+���2���e��W�BGtw3�����n"�p�������t�������<�����RS��1����F)�u���:GLWr6^'>����� ����*���I.���Z��o|]��?���������y�����G
����o7�):������1<���^=��z~T�����~�&m��z�����R<���������c8�_�������M���c��Y)V�
�����>�We���-6��[�F�������")j�������{#�u�\��B�(�;�l{����l ��X(������f?i�,�����[���)u�i�~�Y����4)!��^(!����8���}%���NF����!��2����]$-��f���5kb�&{�����J��6�)�
����%��	��+�uV�Ky?J����#�Jp����<�x�r�0/��-����(03��\��b7�G	[���9��m��jX�}S��M�#*�b��w��"��+�,A�i��=����GI���D��"UXL�^�A���G��#����Y��UP��Ll��g�}����?F�b���gkq]�t|��10�'��=)_n���c�����@��8$LXZCa��=q�5J�� �v�U�:nP�z���(|C�m���>0wX��;����M=nF�7$�L�`�����pqG��e�������DXs{�t���h�����C�V�Y�'X�@�pvr��<���6��������L	'A�����\<`��BCU��y������u��.t�V�-�1g���L+����� ����!�lF�O��l��<�
u����;���OV�dG9�"��@e��Q�/�*G��1pe���D6��i/-$�`�Z��R4�j�k[Y�T���o��7���h"t�k�8�'���N��� ��KSk|XC��,����������e3"h����}YC��C�k�4���q ��W"��J�VV�Hf-��a��b�Q�~��P�����XJG=�J����4W����$��"���14_����<.D2T|�p��zy/@�-p(!	���D�N����"����G���;��]nx5Q=���������N�qv%W�������[���,X�F^_�����r���
@yl���ZC�������3)�8�cl�!����Q(��5���uaq�^�2� ��	�^��k�9E�M������#�0co���Z���*�:�T�s�2!����d9c
�*G9e�]8���;2�����ce?���`�����4�xo8�b`H��O����w��
��kZ�?M$�$�i���a��b4���P�3����(&���i���r��i�<CGhY$�@���&�Z������u�0�i�O!;a[f�N�X�}��������[����%��J6���������N��������8MsA6F<��q���@�=�x&�9@0���

%����VQeT�����y������
!W��s�)SH!6I���F�T/
h��&��t�s������BQ��z��q�J'9K�FO�A{^F��z����Z��@9����&�MY*�4��6L3��UY��ni��@J!\���w��e�
^�%���I�������4K8E�\�m(�i$�+�4P�T�]�Rt����P���}��p���-�Um�KQ���=��c�8:	y!Ei����@����{fZ����f�k�_�N��->O�I���S2l��������'��T?}��}|�)XX���"g��;�;�8$$����0e]L5�/Ypit;���8,�������bK���pWS�dMm���QpW�����X�gWb����6�@C�N��h��q��By�z�����A�}�;G��S4A�d������$�W��RZA�����"����r2Q�Je#�BYe�|��k3���n�w&���������tx��{�|�67X�HH�x��f�a���'E)��UD�m0�[�.��zb����]2-^��������tzZ�,�f����@~��K�X�>$�_����B��7���[MSN�q3�-fE����aq��������)R���c�
����~���/����F�Q�s��?0�(u��1���s'855��Q�Y������r�~#��cE��9�`�$��`wk��������jL5���<� ��K����)'sf/�B5�FY9�N=;��;
@&��v������@��9�^&0�sCSB��OeB�N�r�S��rC9��������U.*�p�`�)�Gc�N�D;�OA�=`Q�.�['��{U�m��,J��\��b�B����f��~?z��zS0\����KD]b��E�jz�����'\'���dN�&p��2���'C:�k�*��_�Q
�����hu�)f�Z�����e�]�:hKPd�-A��tb���#��??'������Y���c��+c��_�#��X;?E��V����J+�_��I��_y�8�[�������Zz��[��������M\���BOOWp�-�����<<�^�����7�-����<���g�%��e��}8���/���PVM�cE�R��Ri��[�����
o�\���'z�����:�U�6_ZP�!����xO��
u�M=�p,K*��/��Z�!�@�����0TM������I[�A��(J�n������<��_!��:�����W�~�x*EV������@t�8V���.�R���
�@zMs�Pa
�=�|�n�i@�"O8BO�\N]^����Z6�[��?��<�rT��7L$�vs	��A�P.�5�Dq����z��������.�I]U�
���WK���SkSz���0��j�-3-��;�Gn6�Lo��W�	�M�1�kkE
I$s��%�����N��_�3��D�L��5�u��R��b���n��f4�-�m�p+L�:-���<�yy��Y{��5%,;���<��,y��)�:p��c��v(AjV����DG�G��F��>���_�A�z�B2�By O�P�s��z����O�9{��\��i��A� �v���*9������-&
����HxB�����0�6K9Q��U�1���Y�-e#��l�>
FcsSC,���������e�0#�H�#f����5��7	��O�^=<���t{w���_�x|�����w����,�������~'����m���+��SdM��'�H"�A)�}���R�DIB(V:�_�(��a��)�<Q),��5���5SO�����b����2q8�9�4���lC�2�)c��ln>D�2	�S���F�������B���sOc��&���b���M�@'��$u��>����Bm��([������e[��M��c��!i��9�d_����U��������o�����LE:|��7��sF�"2e����`Q�r��a((��Vy�
�?���G����5��B ��U�u���ZT��r�l�<l�$�����w�������$o�{P�_�j������
O�`��2��<d��v6�P�8��`��G��sO��i%r�^����h��W��AY�l��������/�	�s(GF��(��h����V�B���6�h(c���p!7������Ga����P��Y��R����c�2rT�T����d�)��T�b}>�T4I��@ql�����������"�n�%���j����d��hW�xo���A�����v�?���@���c�PA�^X}���rX�h`��
��ds��@k��7����[�''/{�a3C|����
"H�4u���mMl�\��d�K�(���3�;��0��u�B"++2Q��=c(�>p��qAX���	����o`1��r�*�z�^M3��$ J���v=�?t�J��!k(�=�	������O/�IR��������_�!a�jE�?n���m����x�l����]m)j�s���������ja�o���"O(��H��f<c�y�����{��3�:i��j�h�A|����rc�(���L��<!�/��Ep�����$Z������*e�P..�L��S�@��91���Z�"F!�<����v����z�=|�������o��coP��+�O%n;��
0001-wal_decoding-Add-information-about-a-tables-primary-.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Add-option-to-use-user-defined-tables-a.patch.gzapplication/x-patch-gzipDownload
0005-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
���[R0005-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch�\�s����l���I*�6�����T0�6��3skw�jI
(QK���������!�L�a]c�>�>�>}�0�a0a�����y�|X9)��S�����c����J�eU����|�SV��R�B�cG�R9w
4��;���:���p�����^�?��x$.O��OX��_>���������/�����]��������tzX>�����#��q��k�Q8�-�9K��'�3�G�F9���Ix�\�J�&@�I�$B@Fc�����1A�����C��&������,\J1�<��/���/��D���0c��!����W8�s$$a�>�Z��9�[#Ue�/�rD`._<bQ<%��-��>��|�>
�9��O�P0���@M������F�;Mt����$:Kb�$9�!���V���hGl�� +����)8�<���2��������Bo���`2��"-���!��b;�C�9�%���-�F�/6���L\�zf����4������i�k�A�q���~��f�k�?��G��Ao6t��H��\��B���P�+�bfa.��zm� SB%S`�#X����i�l���h ^�A�w���*!GL�������{O�m-�������hf��#��
�4����	��-EJ��0�X�H�BJ'S�u6Ta�e�>�����g�V���������k�/�O^GE=+�P�����<,�r���P&���<�����g>M�X-��x���}��b+�"ps��P����u�X*��th�T:c�u!��M�(��q��8�3���!������t��+��5������X#1�~�W��&t����tK���\�n�������������w�ew\� W����n��`��AO��;D.<v�����z���uG#��EP��#���#�%�Y����'r_��	Q�Z�Vq�J�rw�
g�{�a�n!{D����A���>�0Qr��t8|���I�f��?�O�����v29|��V@	{�~�����.T��JoA�h#��!L�@Y�x+TKY~)�A����7�"���D4}�v��P���T�9���b�"�l����
+�J���6���Y=��y��������Y�U8��oKB��e�6��
������4�_��|�7K��*�J�f<]&@�K������|$��M��F�;�j3
IA��!�e�	��j|iP���,�1��,QO���1�i�X���O���So��z�b�r�J�p����bwd���XDl�*�Q��o`��Pe�Q�!nZ�������@��dV	��R��X�eC�[C��m/vD&��A�Z�@R�F�oZ�|�B4������R]�'����(�����
2�?YP�/N8�����3�-o#�����R�Y���r�Z�<6Qk3�"4-�����d�����4Z��ld�Bz�^`?n1�!%��x���R�0*uX�L�(KT�����j�����n���{M�#�P���N�A��	���Re��������"�MG���a4�
Ge���g�c�����@��S`��R��D�Jyb~�@s�	����������r�T99ys��M�I�M�����@����Pd��o��J96�o��_4��eb�W�f��W�f��W���e.���!;8�@�_=�X�N��#
��rZvJ�b�����>2<�q�
tr��o���/��\=9/��E��0<�	�q
���=h1�����:�����w����yS`�j*����a{���Y�?��;��O��������x��x�d�����7T�
���(Q�dQ�czE��P�x��(�C����B����n����.i�i����U���Ov?���Cb�9dn��dO�t-�tS�J�4���+u=��jwT����s�a���+�o���o���J��G��>��u�5�.0~�`��,�u�(��R�_�T�������R����hCN���C�BRt"��b}{O$F*V)C���e�t�C�3�WF4�k��9�����9��*�WNJG��I�Z�N�Ug�`��M�y�N��$��{G���La�#������C�&\B4���9DFn���B�����3R�k������Y������[�4W��\*��'$�V
H����������?�j>��Q=�~���g�iL{3���}�2��d�lL�������J�����M����~�0�����?#�P�qj����T��"f�"���� �1DZ�Fw!�|�A�Z`b�c��������S!!��kY����'����Tg,�e�;�d��1��]��tM�C��:+0�y�I������3��1MJ}g���B������j�����/z|Du���{��n������c�
_���LDE5�b�� ����*�����vv�!2L���;��e��~>
c�oH6��c��RpLi�L�9���������2n<u(W ��X��������LAm���2I@SP�w���G#��3����R��t.��8�y2l��>
S������`w�����������N��$;�����C=��K
Y����f�#	>)L���P����Y�&}t p�u��������T�\B���O��
����sp{�e�$��T��V�N���*��7��n-a�X�o���O��m�t�����~���m[s��nm5��Y�j+V�h��v����g�W7��R�Q��B�7�x��Qe����l����+%5���T�2��������?S)�<�n�I�#�$��/3�[���p'�.T�
�/�4�jL=�9t�^�R��"����<����R�"���'����Xz0�Hh�%��"Ya]�Z��=�z�e��8������X<�6���n+�6��T����U^�}��w�~���l|�KA�����n��S��O��n���t�:�*�x("�a��Un�r�+N��'zU��:&�73�}�h5�}�>�i�^y@7����>�<NnPW���i�1���<u�39����\w;w�����z�1�s���T��N
	�s1��/	�J��Gy�[�r�
�ze�1����S�� U=� �9�b��(����$u�.��)T�W<���<�a�r����z��f�"V��-��F�L�INY��:����^lm�;G>G8���	:q�9��KH��#���p�w���^��)e���������M��$�S"
�^��$p.$�<���nw+�����k:�r������S>����i�a�1�����9��~�=�sz4xq���N\��J{B!��D>�I����Hz�1 ���c���_��^f��R�m7��$8g�R��{87Pk��O��,��;���`g/�lZ�]����|��eK�I�L��&�NS���r/���s)f��D��/Z��?Q��!���!���u��D�+�2��R2A�B����Y�R~����q��9�V�p���9:�'vd)��4�q�RZ>�	n�����4�nE�B��w���G�ui�\�o��u?�,���
I#\�L6?��@�!�R@�^,�	=�� '��sk�W�$��8�T.#��cqT��yk��:��i��Jy�R���)6t�������M�]�������q�]�}>��t.���/j*�S/������#|�]��������q6�|��t���b@��W�.0{2m�I���|L���0w�<���SPJ}�=���K'!:���<;g�m�\�2�,��!�'�*����O�x\.��S[)��O�~��K�"��
T / 
�����w��\��Z�%8s���m;�G����7�M�����E*������-Xl�U���TMR�/���e+Mfi;M�/��O"�U^�����}W����]�z��E[�Q��5��U�1��z�`���o�����U�:��sB)���.r���w4"�G�lNvv��'�>�Z��:�_���R�	�����0=�IH��q_��f���3���^		�R�7tU�/i���@E��m-��8����7.����m��Mb-�Ru�
U<��e����`�����K�l,�m����#��Oe!���H��g�2_ KZ��(	J��R������R*�Z�9��tQ�6o��=��[R>S"P�d;����R
�JP>���4n��BV���`�`:��8by{����+���f�f��?[�F7{_�����H��A<}�	][�0[��Dd#>����f������]>g+Z���Ck�j��]��0}��4��k����qgB�����m05t}�86PW[k'����
� ��0���G|����3���������u���,�)����%��
H�!R�S�����n�����>�@��t�`:��%@g�7wHW��R���K�������E�4��{���Utt�gtG������T����vN��ub�� �Q��E�o�]S5D��j�����]Qs���S��w^q]��]�n���T`/���w'T��L�_�1~�*[�P�G�K�N2H�0@EYP}���U+�`"G����E3���;�>���0a�<jV�F����'}M��7��������Q6�
��lGms���
�e{�D`�T��O���Zqj"��=�L(����D���ffy������kz��s
�vu�M"�`~#���w�n�h��W����33
x@���&�U9u����L+����k��>��Uy�� �X�$������t%�DD�!����@��$����(����q�����nd�Y>��h����kDeV�S|����Y��;K�JP���p���S_��siVE�i����A@���8F�T�(��VdS��{7�>�A��BIai�S�m��2A=��>R��z?���7�������2����
(n����ds|#�����!]�>q_k�|��
�7����]��k	��TjD>C�`>%��+�Z��4'�c�s���2�d���zJ�����Z��}��l�����w�~3��d��h�-��<��w+�ok�����f{�Qo�J����G���s�-&���8����n��-����o33���J����J�E�:*�3�=�#1L`{$�M�V*n'����N���#���o7S[V�r�srn�������	H�?��?�`6�������5�t��K}'���
[l���k@��[���T{�L��Y��Q����l�����U�S�byF���|���L��YbV���:+�Q�k�6kY&:�V���0����.UX���n�oT��%{�3}[Y���������0M������ ��`�&��>��]���������6{�������f^���>��3��n���ot��Z����6z�N�1Pb��]�Zt}��8�6��A��\���.+.���-n���@�Q51i����MS~H��G��M�)?!/r��������I	�:�����u|jy�Q������z����C�u5�~h���w������^�e�~~��jV��w����2L;�PP�:�9<�{���q��f�*��$���L�����=5=�
x���^��TW�&E:���y[I���
�"M��%[��{Qvls����~�&R9��J�@W������e�l�,����!32�8q��	zUO���J��>�9�&���n/�.L���KmJ��.m�T��n�.�����~N�;?��e;c�����\���)i$�n$Rh�qDzb^��v����,���XN����Z
�g�bW��O�����N��!;���6�����A�I�7W�D��G���B	~�/�N�l6�^/
���[t#�[����������E�7���M�����,����n��T2d�O!G?����cgP�D&r�A�D�����~st��u%��/)=�gQ��N����wjY��3k����uZm�fv�i�N$i��i����y�y����	^��y�]��o^Ft��������A4
)���X}��7�~l�OO��~�;�8;E�_�����.p�����O�R-V����\�� 9DI��E�%�G[���y�Z�����p����Gu.+��������+�������`h���������@�����T���2+	~���g����jf��*s=�X*��������v��Nv�N����'�6�N"�7�k��x�O.����	�������N&}����X����"�7�oZ��E����4�N�}<�-���<m����===a�h������"�@�u�AWV���
��k�/��D� �������y$OC]6�lI@[_ �y�Q0���ih=�W������>��u��$5?��������k5�D�	���[��l
��������J?�XU)��4�w�0������;�o0�������H�x�

rhh�H�}t��GX2N���[�9����;�8�Q�5#�5��=�]�>u�!���d{7J�� \�k�������O���HaA������o�pYd�T%���0���;S�%�k������ZyA��8�*g�$��jxu[H�/�:��#�~��Ca�W+�-��z{������_��m��c���c�-�~�?�s�IJ������^��0i����?MV��<���R�����x���)�tbpga�@�5��M��A�`{��>�j.&]�Ge�ye�;B��
���n�P4t����������C�5F[���C��]����*G-r�7�cv��g�q����w����������x�]�4E	���A�(L�����(���Tq�r��[��E3�G��>��Z����|0)(��&�=`������g{'��g9a����pd9�$�L��/��9*Vr���y�sv|��cfBu�9������Q2������X��p�5i����_QeZ�������@E���� <��@��c�8q�<�����%������>��x�?����k|��E@G��C`�8^&#s�st#�Q����-3��~GV��2F;&�h�����`�����w/�S��5��T��9�K��_�7�w��[�'nI���t[����{�j�����B�|&�i�}��

{�:-&��usi����������P���Rw>"��Z�����_�r&%�ja}}��M]�u�pl>a���+%sYb����������!��\��a�71���}�9����:t����C�U��<�	n�C/�>���x0���NJ�~�g�����>�4��]���t0��&\�Py��%��1��[+q|������K>�g���c�(!�<@Og��6#J���$�h��
��F(��|��~��|�"UF������=������J�H�e�[8�����~����JsD��;A�}EZ�O�w,�>;��2E:>����k���z�[�U�?�Z�)9r��B��������L!��<Mx5I����q����m6�Y�*��?NG�n>�t���^��=����6,la#�X`��9�J������ww������x��b|YG�-�Z�9��W����*��O�����!�Y����a#�qM����!���S�Aw0^}�_�Dz�)Av8��#-���	h��������{G'�{'�7��5~t���A/��������x����03��p�f�y�w3L]�3bB���.;G�g��,��>��S"�l;�?��Hh���B�u���G�)�K�]r�{��bd�4v�4gXE� �uV3z�s��a{�Z](��z����I6�"���Q�7�>���n��vL����,&�P����H�����a3����`S
�j�u
�J<�VS�Gy3}�n���;-FTD[e�����}H�/�:e�����0"C��(%�
�����[��F����o���sV�?D�x�~gRF�S�d�>M��k��I�x�"�� ��H�GqWJ�d�c�^�+3��6�g�f�lc
����T�����U��xe\}���������`������e�q����� ]������t��8�1np�����g�_s���{�[��]K��b���cb�u(1B�M��V������m|������'�S�M�N�yg������,��rfp�A�*����<�*�	��s<�	s�q�"�Bl�p(�Z8�J��.�����������o�{_�n���������K��A�������bsu5��n��g~�����G�s�#7�T��Y_���!(��G�h�5]k��i��N��D`�5W�=�n�	t�����M�W��5�����,�wuhH'�7c\K���<-�-����m1��b�l0���~�^�~7�����
��.�Y���&*3�q��I����H�y�Zr2��	����S0n�$W=G��IT��\���T@@��%�1�!���-��fE���(Cj����m�������09�	��!���s�t�H��� ���;H4K�U�.���G/�A_�o\vr,G���j��������+F�t���'���N������[e��*����3�=`�a�����*1}��/qQ4.lz�[���Vt�8!S���I��EJ���]�J%e`:i�X�����a�8=p
�03@�
2n��c1���4>^3
�s���~�T�
A^5�������A���%dF�m\m�o�^
�������/6c�l���BHg�8������&v-t�@t����E�D�X�~~��'�DM\�X��P�|�D�l�����0����,�w��qf�R<0���d�z����W{:���
���9�6!�@>���������R�N7��WR$�Z�neDw
����BK�-��Z���/��������T}��=�N/L��U��&�I�g�V�Bf ���>B��C�#!����h-;_��%D��J�F^�3	��T:[�������Jz����Lb�h�4J�4����`ZFp�VSv>��������ZmK.�1f��QP��Ey��{�f1V{�l0~6�����������������;K�X����6���b�i^�v��g~{��ta4���9ve><��e�2�+�8iz��/�(��Q;���w�O�����������9Z�U�\��0��dd�8��V�n��iw0>�������tT8��x���n�8B���$XUo�(��Y!�l+8�:<��5�� �B��Qi9�Z�Y�th�y��r|���ar�����>�:�~���^�\id�T� ����-A$fLlDv3����aN�&�O�Xy?�ne.qt�>�V�n�,��f�sT�{�O{y���k��l���zH_[>z����a�]Z��X����A���~��\���/�;9������N�&��T�����rJ@:�Q?*�2��]2�����&P��[^S�f8��k���;i�v�����~{�_Q-�yJ0��r�`�2��KoL6"JBDZ3���7����17���������+��_�n����I��5��K�9��@�����,�e�p�F��:����0�S	�9��/�sH;=��4��]>���
�;�a:�*#���7wodVO2����*��b8Rd&�aEWP�����A��7���c����%"`F3�����z�~���&�����*�$���x���&_��N��_j�Y�����B������!>��b,��nF��d�������e��Z5����Y�b0�w�Q.�AS
�~n�pJ�vmE�R-�@��Ft��~�+�}���w����sL%Wnb�}���'�W�V��ZV�}����i�%���g��*����s%��9��W����7y��L^����~x5�-7l���KS,~�r��f~p:��������g���U���`��/tX�>��iTi�L��kI�3�=`xH�O��5����������|��.�Z
��25�qv�=���N���f�fr�v7���,V��X�}LH�J�1��5G�?�=s�,��*>�{�pm���O�v�b���p��l���" 1���qb���A�@}���s
v��$���	�b2��U�"�w$��#�����u��7V��
4:��d8K��)�;��v�_
jC����?�vq������{���F#Y���
���`&��>�����d�Dez���zR��^�N���}���8+{B	l�XY���&�L��Ad#
%h��t�kL��b��8���uy���N/*������6|c�pU�IA�d��`-�#��6i��}@�6��N���C�r���+B���(���G��0�*����7�GL�}q��s[x��]X:n������,}��4�q9t��~_��?3)��+
��uz���*��.�����G�v�e��u-\�7�����w������r)�3�_���	����5�O����x�]�6�c�9���vM� VBj��]n�X����#��?�Nt,z�X�bkG�w���8�"� ��:�\�]kE%�\ �����m4B�pOwKK��)bC|��x�n+JW��m�
0�����2��M�B�AA���cs�'�!�?bN��E5�S,����;�K��_^��T���j||3���Q>f��3KL g��~x�A�36�R����U;Bw�{o���&�Q
�_S.V�����RI:�K�����{�����d���)!��^�`�lJ�|����� ;HD
D�C�x�.%�o�NMx��PF8��T����[�c�jx���P���m/|$\�(0Gl]����h�#Z�
�V*/���+q���p���lY�$ ������l}Hb�a6a�:����!���+smN���f���U���.;UL���V~V&�f%�����A#������aLIC2	��O��x��O,�v���'A=Z�P�"�J�k�A�H_�o������j��B6R�@��EL�������Q��QY/P��,�<�%���)���hK8-�X�%�|M�-��ng���u�U��,��_�6���69�����x�~����g*�A��YE�y6�&�dI1<z�*E ��b�*��aG�;�H��Y�t���?~Y�5�XW����U����|r�Q����������gD���u���Z����::-�������K	�L��\���7�-?Z����@����fkA��1�':Ym��U����]� ��X�2������x��o�eQ}�h���	��*]86!�4���������ypz�$�zL&d}����#������&s&����W���	�x���_���@����z�Q&!T���mpx�*�,
A�{�m�%�/&�q�U��|���_�`���6�3�-�)�y>}���w;�3���|�����$<���IT'����U��8W��BiA�V��{�F���8D�Y�TH�ER��;�h�G�b\c���������Ri������k���N���&�G�l��`�7 �J$8c'#�hcA���|4`x�����u��]E^Lz�{����������ha"�S\Ds�/H�Z{�����C3���O6����U��]DCz���d<�X�|�g�����F�k�9��k���������Z�������O����l���������x�'5Q;��+)�RwaW>��J����>�\�����3}};_����;?���};^�����W���d�^�Y:j_��@|�����s�
�e����d������(����I����X!=o���P�MC�j�$LcE��5�����,a-�E�xY�y�f��~��\�zL4s��\C&���O=�1���~��]5�����{�y��7��s�/�;\0:��{���!�}:a	���c��h��Bz�����r���zU��c������J3tz^u����?W~*�sxO��$b�5��4��uR�E���^��Hv�`��
����(��Z�����]�N���* �j����h����FL��t-�B)o���Dw0����������[�������.p��E�V��n	���Fj�#�.�6v����[������������n����o��)�P-�
��~�H���c�"�-�0x��*�O 8�Vj���p�����T`���X=G�r#J����I)��M�A �/*}���f��s*��n�� �;��K����XkM���Sx�u��<��%}��
�?6������;��wHz�0����b�>�D:���@c n�%��c��!���\�
I-t|���������!�=Z�oN�d5��;����?��l��'S�YmP�)-�<l�!��w�}R	c*�|�'s �N�~�@G��PR��p
1\ U"'L���qLG�}9R���c��S�7�&S�\~q��%�
����b�L8�2��a�dWK�~(��;P����E��C&BX�+c;\�:mN�G�M
�H����Wu9Ia���^�������t&m�7�E�
&s]����i��Q����.>�'7����d'�T�E	�[$���+�w0G�A��d� �)B�-�w��o�������9b�~���[��Y�N��R�99"u�n�B4����t�������J���CJ������z�eq��U����D��L�/���x����K�(cZ�Oqe:b-���F���c��KZ �]�y��v������<b�'�- m�#����WH9��r��g�o�r�3���~^{|���~?����?���E�*�������l�����|��g��/���?l}<-+������=~]����[Sc-e	z�5�c+i
�����~ m�X�]JK��^�7�lL
��,��Qk�����|^�4��,�e�����?H��O�T��:�cM�r^0,p�&��5f2`a.+R���,��>t!4t2�K�^�a��t�wo����Zx����K��� �5��r���2���\BW1^j4���u��9
��.��D����o�(�K7���������s���1��k	���:vg�}46��Q?�W}�V*mg��CK��������j��|1�������^���XdI�&guV�
efa����������|w9����yO�]�UM�h%!u0";,*��"�,��
�ZY���%[�`�"��������uO�w�3�@V�'�H�q�b^��[<%�Y�Xi�2�0=��_z4�	?{#k��+4u����
���������;OaU����_��vh"�q�d��L���>�R�����t�8 �Y7�cZ����En������Qk�CGC5�� ��y�������P���^	����e! 5�3jV0�>��&RZN��5q�����6CZO�W���cw��\�*M��#Q"�j���������A|�����$�>�t�������F�x]���Cpl9������3*�u���j+���z�)����jv��v��SI#���M��T��|���Eo����\��H}/����U�(d�� a3���p0�5=���h��bFCHf����
�M���c�/!g/��:�|�|]�g�L
P����N��K�������o����������K��S�b�l�����Qow�|J�;��Y�`��!���������g�^-ir��������4�*����s�S���%�\����� 3��@��@uTqyN/4sF����Q��B��L���"z���tu9�z���D1������IW@�����2�
�Y��4�_��-��qoX���vp���;[�t67��-�Hl���egi��R��O[�'�>����s���gn���n�<����
C��k]`�mU�^������R�,\t^U��p�
}�^����F�[�3}����G�n�<H]�����tD��������:�i���f,����=��0�z�v���q�/RBC�������c����>�����~Q���P_I{hT�uh���v/G(�\3���������u�+i�O/�5����q�
k)[b*������a�'�T|&���V�e�$G����Y�;����������_���w�7���'u?AZ���^�O��'�0�sh����yF~ij�6	�!\�6�5i2wNvo��������w�b{�l���c���J;�s�)��b:0�� }Y:�i"���h
�ePE *�2>Q_�`�w��CwGR���s��������ig� ���b,�_�LX���`B��H��@�(���	�;G�����f\����%���Jj���8�C�B��4��Bf��:�����H�tE������r�V��q�w&I�:B\��Iv��*p��+���&�F����n��U�Q�	��!��j�i�
i�����(4��AF��%@��q\)����1��,����8��x-t63,'*����d������?p�����V����#���_sLx�9���*�yo����K\�����9t(������.7�,8��vg����7*j���Zi�sE�R~�Y��k�������:J��S*�*���������n���L/9N.��U�V:O�0�(
n�w���2`��K
�N������Y�������L�!:�G>�oN����X�T'M��I�m�*q�G������x�.�weg@;�o�9����U�B�`B=ju�[������v���Q��_���V�:���G@�������H����<�j�8�T�mlW)����S���N�0��cY���C��l=���V���^k�~��w�+���j��2qq����J�,����O\���9	�3/�Nhp��g5��f?��^ O�b��\�	
`�s}?M������m�4�}������W�9=����y�/&C�v�����r�9bc�Q� ������i[��w�0��@���V����t�1b$��;�|_��^2#������� ]�F���OCWt�%$�K�&G!�j�\���������o���+�-
� ���I//z��
k|�O�H��������E3������
�wH`�hrn�qCj�w&����#,�����,t����J3��2�N:W�����^@��'��
,_S��"�W����N�]I�Z�P�
u�S+�zZ�z�g%�i�Xu��i�QuJET�UzR�����B�~37L]��T��������>����/�(�*'Wz��ev�����a�g%lEj���a�U����-j�Si�"�X���r�H�%d����;�k8���C��0�����OBA�h2N���qk�/;i�{N����c#�s��	�_&������E�X���":?.?�����T����[��0yD�Iv���v�������-�d�4�EO�pA��WD��p���o����T��|�p���ME�������:�iA�	W�p7�^|Iv�����
�g�ro���d��Z�3�]y��p[�1���sr��
U���`�����Yi ����h� s�AUU�m�=�<_	�L;����Q�Q��Z����Q�:�-������Q�����Q:f�������-����0y���o:�������EG�W��6K=e�5+����pa��X(4��vG���^*���S���CdF�p������]FD�
��'YW�v���?����R/g��6U1�����e,x?���J���v��������������C�b�T�M@���7$�����u�����'44��G���/���������k��3�/d�>��\����n�^��������|��?��d���8�fc)�W;�����o/�n�K�G;�{g������Z�O��cojsg������LFa��<�`��AM�����a�#]�����{���9J;B���2����v�1/ #���+�d��{��v�����s �*'���f<!.����hd���BYL�QR��[)�G������`#A��$Y4rm��+��)#�+9�������!M��.�~�6$wf�;9�������r[�:��(W+~��������_	��)h�e��;2_w������}�Ts��
��z�P�8_bX�t;�����Q��	�dxg�<��'������J������F��7'��x0l�����t�u�N-����&�{�$���YO��m��R�������"|����q���>)'@�������>����d�X&~��l��S_aJ��$�yL��%��5Mv9H�p@�	T�H$n��L�_:�;�Os�����/N���|�?�E�����������&4G��X$��|0Wj�t#{����[R�ksN~���7~tzsm��q~�� GZCajB1�7�����g�8Fp��3R�c�0���<L���x����9�����=%��}v	�n��8�-�'Q�9������~<$�Wk���)R��h���/[HEZ��mNw���_��bg�-e�d���/[�	;��@��nJ�{LJ�f�,�+C#v����'��0A���@=?0\$�D���w�S�.N,��=Lm��
��&'��V�qZ�aM���� ��g���H�����t�CdH&[DV�j����b-`����������R�T���K�������|EvO��O:2���V��_(�.����v�}(Z�ov*��F��^9��������������w���D\j�}��1Qy���A�����?;���7��U^���� �g�����M�&-�6�����S�O/P_���sTZ����ct�����u�*��P�7��'�zq��-�N]�~*��~�a���3�����Pd���c������_���g���X����ek$5(��Mq�={{i��]/���#p�-X1��������A���0e�#<�i�����LU��G����\���Y:������_Q��1���P�d����|��3^�=t�:�]�o�����U�;K��1����e�`�;-{t�g��*�
r5���a�ar���
Yi�����3���e������)����
F�^i����{��l������j�|X*�iyr4IXV(��P7�%��$5�=!�&yH���N��~�YA� �Y?R�Qb{2E�]j`O����~9������Cg�������9
�n��G��-�Al����}��	
������6���/�&d�>�? �L�������I�B��W�e��'��G�!U����X��V��n��9�I�P����{;i�=:i���)�x|v*#�sRt�lXp� plg���O�	���b5*�p�?�
�����(���"SfkY�c��X�/�A���Z����!�9��P���R�d�6���H��.X�����'O��F��k��E��	�7��m�u:��e��N���;#�F6t�*�;��nI�?M]��X�vf��x�D*q�Bw%��� R��V�z�$&$-�Jf�TZ3d>��y�wo[��\#nQJ���TfCU�t�������[|w�w(�f������x2���X��,��%J���D�3	W�g���&H��I��O�[�,Pni�$Q���FZ��aV�h��M����t�c��z����B��#�,k�e�`UI3s#�APbz�*�5�l����6�B�B��#�e�E�a�j��7N�`�8 ���� :iB/�qU��)�����������N|�Z]��'}���8
�����<gt��6H{^���rSZ��;[F��6�#�i�1F����	N������/�;[��s����j����xu8b
Nx��[a� ,5�!	�ZR�p~��9��;�t�h.4������<O���@�:?�M-�g|53oY���~t���r�`�`�����
��y_�"KVm��GG�3��E���2r��D�/���9�6�7Zs,`
G�t�F���
���X)I�����;�i����\4���Y��IoMV,j��M�}�V1i�;�{u:��}��a�?x�=�������S����&��0����Oi3���6�]KQ����x�7�8�D&7P���� ���^����/1\x���l����������>h�IW�~>����"��>�y����{����9�k��?9�Ts+B/I���D.�u��a;%��3X!��EV�c�2,mTUD�m
�1.�=���X��
�m{-zR�!���W�P�4t�������R����i:�;�xr���>��A2����j�9�o�?*��x@,z�Y+!�r�K~F�!K�W����� �zJ{e�729w��4���]�T����8K�ywo�hw����cxp�<[FX/��i6F;���J��^��7��������a�\2=$�fVW<�w�+���������I6�m�!��|��y��h+��+�P���B8�cfs�hl���	��*����<n$�,��G�}���"r�~<>>:9��E���Jg��+��US=��g�O
��&+�:9�K�W_���u�������oO>�> r5yd�}=[7���1���|�I�V��T~lE:���R������\W-��5���xt��q�E�Y��>Q������dp����e�q�:�����ne^����n~�lm�G�_s�x4��dz_�a���f�J���,P'w�{���H�����4��$�/��
`Z ji)���$���+�/�!k��{�u��)�M��%������q�s+�i���?��s�=8�;p ��"|8P��R�-�a�K�����UD1�����3���4��%3	S!�8�%l��Qg2���(�z)^��������Y�[��)��q�f6J��F�����	�Q��deJ���X�[,y�_���j8Q7��z���3`Jk&tS�iU�����b:������F2�������}�B��05�����N��������D����/���Z?�����w��
�s\�6�>y��|��v��������m�E���������W�(�!�;�o����7�E��|�MB�U'��w&�#"��=(&�������[�*���8�l�X5B"�#?]l��XQd=��*�	�	W6rFo�U"R
bJ>]QZ9�1�h�tR�m��&����<��-)�n��F��E��
l��u�323�<��m���hQ/TY�^g�)�Se�$��3��,���=��'�%��
/��1Gk�*<�d�k:~�g�*���swe��������H������:9��C�v� ����H
#)����Ee��?B�g�����e���s���OGn�0
]k�"�����u)����6���le����^��mMKH��(��q��I:m���P������9�Z�6:���+}.��y���T��	
���!��{�+��#%��h7���������{���"�.�W4My��J�B���rpzH�ki<p�X�
S���mEs���0yK���@9�Z0�&?�7�����{H���8������`(Y�`(��]4���kUN��$��i��p��n�e"����+9VY��i�@�]�)��V���:�iI������&�2���s,Uc��:�`6W�^���y:^�
��j����g` �
��c���YS�������M��aC���m���Pq�%j����Nu������d�<��4F�Y���94���r�RW��;�c����I�o�����t�����R���X�R�S�5$�@;�nq�t�4p��
C{lg5���T	���W�i�����C������eya���$�}��8�H����Wly����-�o����{�>�����T�u7?_c�KDc���9U;����j%Y{��jwpC��
�a7k�d���O]�kz��:Sv����/`�~�T:���_�l��yX1����'�U/���KX
��z��D���,Q�H1���V�_��11��L��P�+�}��:���1��H���K�

���7�j���&������cq'�cx�������x�wr���xw�lomw�`�l�j3l_)�H�h>�8
�	��|�`
P3�1(�~M5b�9�2mGt��T��z���d��;������g�E�y0�$_�Q���@{����{�o
7�#�^�}b�if"��g���^�������)��y�
�{7����h$*�o��z�"��Up���0�������������o��?;���������A��1� /�y��(�1[����i|������������>����.?��j�5���D�[�
;\!�������=	�B�0�8�+OF���fg$_U��v����2�M�elk9���[����ttYr�_HG����;���k����Ko�8��"cM�=�"@��m���n��l��b8��q���X�h�|i\a�me��y6�VU�22
��U��$�	���"?s��������3�_V�5��2;.�& �L	�sVh���L�:���,����T�.>�����!��#tT'��?�;2s��0�4s�-�<���M��a@3�Mw�:A3L�a�Cp{B�6����=�����`��}r������o�N�^9��.��xvX�<�vd���-�FTm��HS������Hq�o��[I�x�q���$d/��,��Us�B��I�k/�?��9�J�L�l��q8���v�bg<9:��3����r�IBe`��	�;�Y�I����6��^���������DFR�0B�3
@�������H������m�@gwV�/
2S=$�6P��z��-K4Qhw���
�W�����tc��t�r4�.�`!Ej�T�v����+ku�}�C[��onU%��h,�����'������)�{���������$&�H��~<��P������ED5J�L��'h��|��������{����B��*4��y;D����ZvrR�!o0����_�EXS�U���|�J���lAq���&����aw�3�����p��\�r"&���@l�Q|x'���e�a��y�W����d�xz*�H�]�^j�;lM��	���B��o�pz�����r����Z�;K\��#�����������
���d�	[��b�)3��"������sqf�}��GIy��MrG2T�����n�J�X\Bn�L�K�]��4 ����/����F�=�����qC8�.�*Brsl^8����e��^Y+yo��h}�"��;��<��
og����Oa��
�@#�% ����Q��(�o�����:�[�O�>�j��IO��q��J��SD��$gG��g���X�)���u|DJ���%�_�O�����G�T�H}R,{���g]�����f��k����'{;w����{W�������}�)�@��J8%��n���(��]���`�3��=�����Ba����4�F6Ty�!	�<0�m8�zDqC�*�y�~�������j�Z�q����h��P]Z����'s);�wyzqR�&+b��j�P-8}���f����E����f����Eq[�6�.K����P���W���u���W��?A��+����m]D}V�����\���o~�nI�b5zR!mS��~�
-��)�1����?���������oQo�!3�/���$��4�I�������S��]�!���
�}�����z��i�n\���~������2�Q�d}+Z��a*
���u�-8a���F�B4�H�AJ��S����kMj�o���e�\d>f��Y�b��*un|�Ns!�C���-��4e]��FtS|1��<��N&�l�@�L����:2���Jn �7�#��0xy��
�Y����F�����>q�I���wF�L���h���%o���t��4g���	�T��l/���,�A��'��.��J.�|JK�h5{���F7T^A9Lz�q����p0$5u_����G��	a����P�q6U�����Cr��0�>����'F�V:�����U�Q�u���K'�_�b���@d7{��{�1H��u��$�vx2��k��#'��X�-��M�n�y3h��r��������1����8�u'��<r��Jh���n����Rq�2rr�x�FW���J-���A��m�|]�QXOes��\3�/(j����{�
U����1�M+=�N����|���<���������u���	��(�e������;����9�zl#����!S�m:J_�<��o#�,�5c����t�J�#�%Sst��(��R�
�7���hCs~.�(��>F�e�N���s
���AT\�w�e�K0�V�����zN�h4/;m%���]�����������m������?�[�%����0M�%��x<l�����7�������^��-�4��!��"������n�����Wa7�\��yfQ]�����!��7�n��������[{l=�~�����|[u�����s��9nN)���e���7�~�"^�nO�ES���)w�Ug�k�[$(����T�Ee(=6�	R�&�I�sNFl�`���JQr
�������"�O�Lop���_��������)��!$��[�Q��	�m7D����
K��g�N~�dt�o�I2s����R���l�H����Q�&�������(����wBW�i��e`����E��������-g��bonn�?��6(m��R�u��T��p����&�����_��F�0EJ�W��
�������e79�9���Y3n����������C?��B�OM�p�P�5g�$����R��9����m����i�+�,�e�"{�P��@MW_�^��<y����ASga���_���p
5�S��=Y��O*�A���F����~�����*��[-�Or�'�D=��Ru�*q����(-�,^?�q��0/��P���qC��`�q�Y���O`�d5����2�l��	x��8)B��W�,����ut?E(����3��M�h�`����g$��������T��n�90v=����Qmx���^����)���%���<�b$\(+x�W��<����pU�)�@�mb4�� �t��rUac���y�LFr�����q���'��C�TxV��t��b�A�6�YL<@���}�"����z����^Fh�vE~�E0��8�wMD���8��EbS����Y�,[���r��#m1�	&�4�#�����{�����
�]�S�>�1��D����H��PY1�T��1��_�K�
s�~�f������h����Fi��@����������!)���+n�&���z�J���O�	Z�4���ab~��DW������+L����j�I�N.<s-��f�b���xsWcG�i�Q���->���2���e�KW:���lS���Ay�J���#t������N�8�����8J3��4�G,.�l��ao�P?��\�0���������"���y6������n�o�
��3���u>cZ ++���m
���J?�nz��)���*	�q��ufA�_�r�Z��!Z(���������c87OQim���H�r.��y��G]4�|��Vve��z��m�j�����i��z04���2����R�f}R=u����0B�#�������H�Qv��H�M��~B^��6a��\��9�#U��p�����BP��-�VB�%X+�Z���/5w�78��S<��Ht��=.o�Q~`�`'�� �d�2N��4��)v]j�<r�>C	����F|��D�ig8'�� ��K�!��$���3i�0p��4���1`���O��(���8�K�*���JA�_(d���yc0&G�������$~+��L=��2�����kY���1���i�5�R����tBZE!��@�@BL/&�
����D����J��;��S��/hQ�5�rx��y��$+����Vd|��)���+1�L����p�����6�V����2B�e�o$�b2"�5a�$�.�d��q��,3��!��c�MU��V��Tt1'@��jk�R=�tO��B�`����P�_"=���wFx?�4�*�zIN��,@��������Lz�(��}���]
��*#��&�fdB�^�z�.�~�U&�ta�)���+]i����t+8�T��%[�	�$
v���G�z���bZ�����,�.�������9��@�����LV�D��?�Pq���-4��H	c�_�u����P��p�4[	��d������8�T_�}J����Oz��1�8�d~7G�����`��|o�� �d:�D
�x��@9�<����;�]�H�I�H� �LLG�1	-i/�2u�e(l�V�)#��y���.�C�����ZZ�{S�C����O���B:��_���6<����U���#�P�5���8���"#��<�&�����.����Z4��1���C�#�p�3�+D�gm
�U"�����������y:�s'��']�����!����)i���L�`�n4�T4�&
�@^%�7��!����+�Z#W�6�k�7�V!����w������f���=��:a�d����,��R����8��iR�K%EQi�e����"-��gP��I�y�T�j#A���rL\�z��)���j�	�F��[R�e�6��\4�VvZ��l4�C�`�&��ZO�Y��u�x���(���p{�K9�#��@�&c�W��~���IY�Xx���E�$G����m�������p�������l��\%A�07�,I����_��A��D�`�nE�-h�\F
��:�I5G���9��&�y�
Q��j�W�b+=a�A0l�%����
o�����.����R���'����qO<�]�=��������=r��������1��J����,�e���	�1�6��;F���m:��f�l`[r�w��2;���FB��I�����N	:�e���H��f��x��V�v�y��<s+K��������co���t�L\������)��� �����-�]��S�[q�8��Ns�����T��z^������0$�V���y%�^V��)9�
!�b	2�YM��G0��4�{F�y�-~qU�L�~>xs;���n�H����[�RRw���\��NE4J�2h8���/�����]�DC������s�!G�������Q*���d6�z)���k@r�pN���e����?k7yg���*�d����D�t
��'/b���iv2��F���R���1!����:G������1k�T��������2����>�>h 5����+��(���S��+?'?�V	U�0;��}
�-�hMSMQYW^p��k��*��IU>M����VL�y��_>��3"��6fO��=/��C�V_�_�8���\%K/s�����=:T���f��WQ��Q��h�L<��;�z`N^=�a5�h��`r\��E"D����k����5Y�Vo:����m��i+��������������B�jKI4X-��V�a���l����1�0,�g"���>*���]�l�c�|�sT���)���3��`2���n��(��U��Z65��X���P@�&E�/8����.E����[W���g��>��#�E�%^m9Z&z���b��<��&�Q��1���-,4�I�]��9��)w8_����&��A�����.Z�)����YZ���,�;�.�{�\�����{�3&h�|c�1�3��~(�%�w�e��D�a����Y,��3��l�
��*)e���R��y@h�&w5��7S�(S:��=�W�,���=�����w1���.�;H������,�MtI�$vZ��B�������6)}�t��N����N��U��}���F7��7:�`/z�9�K�v������%������z�JC������#�'hEc�M���22S�����q�k��E.���C�(s�;�A�������#_�3?�Dp���*����L�,3�XB������"�{��J@��dRLR$��CSJS&N�2>J(�\�i�<��pX��)!��	���Q._�e���U��q0�h�j��q��V=��T.�N9�+����9�,��!\E8l���_��fu4����S�SqY�
�[BbE	e���8�������0�s�.�S���8:��Y������0�5!���FB�23UI���������76\���?���!�����Z�vN�Aj��>]�`'�p�0���u6�c������G���A���C����s2'���D=�B���-
B/U%;�|��E�u�0I����
v�7/v�������r����G�=#n;	;\�"M�Q�kF��r.���&�)@h"����M�K����V��HS�������$���@'J�'{�~U�P'Qv�y�����|Ys��+�+�l��V�i�{'I�����N ���Z����_��I���V+�A!k�"0'�M8�\�A�����O�e(.`x~���������luO�(-�	��E$�*�"�l)���$��O���$���Wry]�����`�Y�h7���j3Q���Z	{)���o�l�5 _V�6g<X=�V��2W�W��W�1�7�I�\�$�C\#G�4��]S6R7��I;/���,���-������s�
=���w�v:�-�������F�t������K���)%k��sX�I3�����;���|�x��ct�X�v����5����O����������$��������4�fB3��b�M����b��.ZT������xsw��J{U����K�gl�6�����t$I�����XDB���'�'�r0S}.�%���,�LHGk������P�?
��'���I�c��4&NL���k,�h�<��6�g8|�1D�0�,�a�93���5�_	����I%=)~6�����H7�|G�BW��**����I�{��������5�G)d��
a�f	k���?����z:������e�B�a)��Q<v�
�s`6�@<������dC��UBo�Y}x��l�,������V`�B+$�o�,�C�<�o�R�:�@��@\iY��N��V5Dx#���q_���f�]vb�or��?�Hj�O��9=���������+�n�	��U��"+�$���Q:�RBX�zR�F���B����DY����IMAd���5�'���gj���l>�V�t�Ub'��3������G���Xlc�}����ggqOs���-���
����N3(#c�����s���	�����,Ts�m$��T�'
xc�`�1Xi����R��%h�2�K����g�
��W'��m<U���)���)����g�8�kK>)�QTjP����:��(+t(M�6Kg�:?�����AW��fg�IK��59<#�M`-�� �����C�xq�I�O9h��V�7<��V�w�B���e���4�6k�O��vj��V=�~`�"`����?�����8����7rzq���������������3�G#��7p3~�y��D��ct����MH�P�������Z+CZ��������X
�c-~��>\kN�\FC�����Hi7���`�e\A������� �B�q�h�R}	hfN�p@�uL
xW#\�9�� 0�u��!@w��`+�U+�nY�E�/���XI1��d��~�
�����8������D�R����]������g����;�����u���"V*�V�w��_o�32*�����b��B&hR���+��	N��j:B�X��_��p,��"
=(f��j14���s����f�Z��O�"���G���!��z��3��#�N�-�G9FW�5N�F���UD3�� 
�!u}B/����� =3�+X���$�'o��{`�S���fC<��c��3���7�n���a��84�	�Z�T�>�Mv����tr����
_��%anzLY6;���+aeH7�6��K��RI�|��.�Z���*}v�3S��\�. ��|A ������6h�"�
�/�5i���zs{b-.z������}X�Y��o4��OVI�G��9��9�2?���2<{c����D��2�{B��P��Z�|�����������*@T������d#�cM��<d�C/�����pR��c��f�^d1����������~�P�U9$���;�����:_H���`������3�t��=��}�}�����B� �q�uLG������5�>ZJZD�G����uZ�j#�@�;�?��I��������=3
��?��(����M�N��E�u�z�IHM�����:"���L�p��Ek��bM���#�������g�S6b$U��)�.�To>|Z9����02G�X,����7s<��
=0�����/=�2��/zW���c��|�d�[n���)\���MTx���9������n+�����K�
Zf��g�8���|hXLb������S�{�|OStO�f��v"O��h�v4��Y�������Rp�E\aA�E����}�<�pFYK�Y�+jeY���{��W����:F5�D:�	�n[�S��������Y�4(�h"��y��*��8n���c��N�m��NG�$��+�g�����N����^�����nd�N��6�oDFw73#� ny�u�f���\���8c�4D	��s�"qT^�Xl����pH���8%�:�CzS����kp��|}�le��+]E����>�d-}�}'�/C\)�%F�^�=����,��Z��U�fu.ioI�n��d���	�B��N��Olp�:#ht�d|��z�'������F����������y�y�\�Y�E?������`��5���9u�&q�@�DD�g�j���,
���Z����1���s�i)4i��\%����l���������}���j��4>Oj����.�vA�� ���q�����yJ�)��0�/�DKVU��m��EP2tT���ZE
+�T���!L�L�]���q1��d������p/w��4]��������j0���������]��9�Z�lvj:/5?U�q���8s�qg�l���HL��v�~N��m��Q�t4&f��������7����D��0���o����zi
�`��B����3��L����v'K���ZH{��m|�aHg����C
k|��:{r���n�=�oT���EKK�k6��.�-:�frH:k�J(�Qe �g(�1�$��*��^:�{���^�)o�w��pW����Sr)���d`������"��2�?��7��n�����@��*F
Z��������s-��R8��h���q#7gL=twf/:����I���`�6����p���eAL�0�V�3`�M8�`�h�F
�5ql�r^�<Z�U{D�����,+
�*g��!�����O1�#����K0�YIy��.z&�1Xz>Q��&YN'�����������j���z����F�`�o`�w�&w�'��Ms6��$�$q~��(n�u�\�q�����rC�bY��@���]���DVyt�gl>��|���=o)�R�B{�lL=����i�[W,�u�}N�'��������|����/�^?tfZ��TV��D����.	p�������q�G�L�����o[�}��x�+���_�x��@l(��f���sc��(N�Q�mz�������T2n�.�M�u���J�-�3DkwCeJV� ���t�W[��*����/�E�������Y4�*�w �{S��/4-x~��z7�{�e��:�JG��� �)nu,�/��f=�������rb�$(8!:q�19+,���*����Q��s�����1{%|[�2S8�Qc�I\Y!a��co�����FB��Xu�5��T?�{����L�U����v&��0��r!�t��Q������gv�dT��`�DClY ��H���$����,��F� s+����o���������N�������y���+ U;\\���$
1��J_��'�O;����&�"�L�h-����$=s�X��u�y�����d��at�d^H8�� ��W-�&�;,��4^��0Y�=�=�R��Gu�c���������5�`��
&/*��s��lt����u���b5l�ry�����e�5�x�����N��=�������b�T�mG#������������:��e~��De��4�O�i�fi|;�0y)\P����
�y������C�������G�������TU����c���o��������1�u�p~(�_t�r��;Qv��A�:��D��"p�~T����Iz�JQn�������5w8����������0VP�w���}��W,"�����x�����kq�g����P��<��-j^�A�����h��k��_��4���z+���-�����`\��|VEl�[]�&��j+6��C��������H����������U~����{�*�I�rqGM9�f{�����m�a8����+�F������3I�v�����3E�0*L-�_m��
vay�dY�OFh�aG�UC��xh���YW��~tc^�z!��}K�F*���8����
��MP���U�[4���:���~,��iV!w�WG�/U�����l<�B�I��|�����������2�����2Q���<��"Ft3��?��.�C����C^����q�id���'5|;-=@�FVZ�^������tj ���w��.���0�0G�t"����+w����(�������%t�`*/F-�����-4�����V7x��Q���9;��7Pd;��b��/t�8V��-oF ������.�Wx��j
�Os.@O:]X�?O�}�
h�
�h�{iM���]���^�
�j��9$�;�����Qs���-/�I,	����2�
�>� ���<lWd�Y~���w�P��f�~Q|B�{e,�4
��2>��B�}����Y�W�j{�c�N�����vO!_
��k�����V]7�
L8"�K�]R���oR�
 M�a��@�e�4�.��l:RSo���(5���`���B{A!p���)��q��m���vci��&i�`y5���l�����L�9�L�Hr��fg�[z���3�mKA��X
iD��.xQaz2�`��x�9����D,�Qk�do�,�~�trtx�W�q��x�p~<~�z��}�W�������SD���>�O�`q�4��U�����D���8gM��Oy����f��	��)���An�?rVL�W��`"gL��\�g��=�	�e�EAH��8�z�.TmR�@M;N�����d�����Q��G��������T�����XEf�j��U,jb^��,��9����6�tru���Z�A���^�-A����i~}��T�u�;%�nz�����/v;����I����)8E\j�c����M>�1Y���������~��V���I[�
%q|h��6������^��:��\�NN���)X,�nC�"���J���,>43D�f_S��5�b7�	��1�����#���J�(�`�G��xm������@�2���O���]�po���n�m������������(����k$��������,~��W����x���Z@��c��Y��3����>��-�=��{����j����U4�J�6�7��RI3�4��-�d}����F�?�C���S{*L�?(�N������*W���o����o�l ���&�"��:�lV����V�H�y�=�
)+���u�	�1�S����47���Ms<s{[�|)������,��{�fm���w���T������{fs�M�`7f���E6����P,��&���)ur�&8���8x8��:�U�����f�hky��
�\�+�����
�>����=��W�U���������R�������(�u�	L�a#(��l'K��0	����:7���L��\G�Z�����b�	�K�V^�*�)Rr!��:K����=�[Z��w&xm��������(K3�1��c)ya��G�x!�X��������G����!$KK�n��l�s�3M���z�y�tWw��P!f����e�ww[�2m���2��![�����
3�Ia�@��oL�������)`J�n�����`J+�##V�c�1H�����	f\$!@t����e+��U���l�8�Og���D����N��R���s]Ys)��R���!�l/EB�O�s��v�����:L�Q���v9�^jtF�4i�R-eu����(-�0�����T!��M�j���h�Q����"�>d��i!���dH�y���?�������V''�"�����g�Vx.����#y'�8
�6�O��|TS��!
Zk�$�v��S��Z����������2x��
�FV_wZ���j�+u�5���Z�J��=��
bl��?��H�j�
�Z0X��rD�+����g�W`��I��_��9#�u��W'��>[L	���Q/��*5�+��<�.�������{��7W���_��j5S�wiJQy�i�����P�������Z�Z1�F�
����&�n����i�#����������t��c^�PJ���O�����}�^���i��P�f�
��x+m� ���a������\P,61��dH_����Wp�$�8�y��I�y�1I�d9��_�O���Z{���y�w�:>�����9������������:����]��IQ�9om����_p�����F_o�g��H����u*�RfP�t�?��B�'�{���\|�~���?�7R�M_4�����6���?���{�zBaxp��������]�N�����ix4	�����P�q�����,0Dlxk�:���Yy��V��-�{���I<]s+�SGz����9���<x��3pbo�=��Rih����Xgh��HL"c���t��1nl`������O0��;��3�1"	r1��fZr>C�a�x�������C	1��'=�6�0�u0Oz�����V����`d��
�4HJ�@����R�`7�I����xH)|B��nV��h�uQ�|\h��K�5C�zO������x-��_����urT�!�U1����C�������N�>���HU+�/D��� �yB��	�������i
�4���?[�s���E�^��,�5�n��&������/]}r�M\��%���if�U�?�M_�'��H�F�������N����9�$(kj&�����mw�N�j��e�<#��,�����o��b���&���[7V�D��/�����@8�M�!���^���n�z��l�9V�&�1�6<�6g(�6�y�����.'��Ad�D���v������sw�����P��f($�L<y��5[����N��������]�����3!���,�\�k�}�P���Z(�HO����s�Ui{L��
j5PQr;�8���+����z$�U/���t�zx��WR9�0��qY����(��Q��=	�����x�Dav������!8�^z+�P��Vv���	�K�{rt��m�9�C(�_�w>~�P��C�h���F94�X�}��
�����w�4P%���-����&Jsr�����;1>W �T��#��A %������r��*�cFX��f����;z��%�X����Q��Gomt����
��E$�u�j�S��yZmK���L�>��l]L���@�wl���w�'��9�4��u.�e�)����F�����f�cM�6��e�O"�����_�����6��H�5�hg���E��1����-��,V��c�-�R�Nfi�����59e	�'��y� a��D?��6�������������	��'��������7��3��t�����������`���8�H����Z�{]]�L�����i���'����4�@�P{�af��`�\������?T�������#���D�?������nHz[d;� 
�oa��t����d@�:��W���C�((8��y�
�A�T����I�P�t�G�D������.x=�K�:��vN�b���47�DB�����H{����(:[��uP�� 6��7�/�!~�n�c�Q�I���-�e�zB�@��f�KC���i6�Y\l�9D�yd8G��:��9�w���f�p�<Q�O#u�����s��ud���B#�'{;fV[���"�
`
�|��"�VV��(�/*�_����{������%���c�=^�09���h�l,%�r%����b�!��H�c���nn�W�H�SE�1FQX�����`FE��a���,TU���
������-��
yV	;Q-���=�p�#K��������!��x�������f�(p;���z ��(+A���"�-9R�\�z>��@�TNt�z��H�!�a������S�xxe����]]�7��M����J�����fH�L��Rn��U�����K]g�H2��X�����r�����6�s����,�z�a��0%������U��O�Jk�����3Li���d<���awlZS��c��*�2;[�z�*���<k����F�w�^Y����f����f��V_�QcS����9��I}Ho��x��@�Mg��?@�8��'	��wO�]m��^�my��$ ����j�5__LFt{!���>'[i��V��2*�4x��v-������4��j�e��qHjx&k\�4���S�(�rz�9�zbX\t��}��Bc9y����=UV���88�w0bb����������
a���oAm��R������������O���mI��v�p��=<�O���=���pbj~����*E�q��#E�E��E�K�����Na	�h���*�43m��1����E3�A���m�Y���gIv!Q���5/�����
�f2�
�X8�$����y�������_���I*4�F��G)�b"���L���m!��]���H�n�6�$Q;r=H�6A�|P�l���m���V�zG���0�JL\�O]��>�N�?K��vIJE�ob*��;-����%M+��3����"�;�%���L����	�^��M�@��m�Y�;H�	�lr�� �������Fu�$�=>�&�rJY}�����z+}[��S�M��p���(��0���2�� !N�����Cr��E9�0u����D�N6�]
]����8����dHy����\x9>�(�:�/�-4u~�iSn�2���.��/U��U��]4H�`RP
^�5�'Fu�{�O�B	��m����$e�L$M��hc�
�-�����Oi��������9�#n�Y��xI��8oD��2�Y��6����+ab�z�D��i�w`�;0�]:��]{���D|��jBu�Q�5��2�Dn}Mv�4)yU���e�I�����P�)���^��`��p�;�F��}aP�7���r�Dz�|��H�Wo�v:��W��w�@�U%'�	p��B���hey9��96��������������a��C��"N�y�,kkcP�����=oB�|�����2
���%�V3)IF�6�=#������������~���/�qR�@����G���E��)����B��G66�\Q��� eW����:H\�e����6:�t1mc�=�H�J�f�$q���@%(k����9����
�[��p	#]+F�5g�~g
�y�.�5q�]C_�s�����|�������x{�e7���=y��!o�u��l���O���2��d��}]C	e���G��o����7��G��g��%��o���bR��)�0���v�m����2����-����F�iI*��}`��d}�1����|�����l��2a�2����>���xn��P�s���Fa		+��2:jC�`4b�D"I����)��m ��~Z>��<�;���x�(D��
�	������;;��kq-���f��
�4�B`�>E&t<f���&
�;�"^$!��(e��X����f����@�J�n��<l�����?}`xfa�.[������bB��ou��5^�?�7G��}��,�j�m�;���I�2%7��qx&����:���lR��rr�@��Qk?Xz�
�U��S������,3���"��*�T4/9�7��V��������QB�]�)Y�����:�li�YZCD��o���l+�-���5]�o�X����/&�El���_���O�5�}�uD�]/c��)ew6Qj�i���7NvI'"�T��%i>��a-z�g����]n2�����^�5�.���eCl�����~��Q9�%�
�x��vC��
��s���V�eL��X���A�L����R��$�;D7W39����{�b>����w���[=}!��$XOI.�8�NT'#%X����g�kD������������il��#v�+L1���P>��P����n���S�@J���)��Nn!� 4x��|+���&��F5����"�eV�QN��4J.������@�������?�Sg���A�	L4{RWrY/h=k^6�i����w�v?������jr{s%E�����:9����?�C��n�}�%�eiG�"�{�"�sG�����$�N
��Fv)���������w%�*u�H���j�..�vN�[���Sm�,��0x���_k7/�Vm��7�S���5��
%������yF�2������J9�7�z��v�
��%a�P|�|g���C�V��j�e�2+k�����Y�v ����No���i�i�f���_f�qi�N%���Rw�[��"���
v����]f2�C`L��5��Y2�{�IA��������r������h-^�P2whaB��(y�"�	�%��W�;H;�|��=��3(�C8�������
�!}�,A���.(20�W��95�b�v}.���Z�8��{��Cs�8����,5����6��A�3�!�9�s4~�s�����$���0�e[�]O6�76W���
LK8�{����������9C��`?M%��{�g�o�w�1HX�*�&"U|W����b?�,Y�8������������z>F��"x>��e���n���h�8|a�+���P���Z7��T�T�)m/��Hp���||����Hu9�.a{p������U�K����L����c��kai ��C�*��T��]���w�Ld"�wxc�FzxK���|ry���,�~/���N���������nkOy��F�m�{�x�k����t>��@�GDr���*I)%Cwd���TvJ���w�nk8��:.	���b���u0e���-����c6�4.�����H��2gk�
r�A"B�U����S)N��oY�hm�9r4���PR}���Y��jN�%�j����E6�*�iu�NDk<q����|M��`�!�{F��un�?r�K��*1HWw�-�������U#��p�=�r�m�<�B�S����v�Pd����b�
�RbB��v���p,a��O����pI[W�3�sx@��*�BL]�1�[��B�e��e��r��>�w�G��\|��1P&�#A� O	16��
x����`c��)��Z��w ��_����b��.�	"�&}��~B��u&��O�E�.G1��<��Yw���hHbk���4o�as��g�c��n�p0lh�����(�(����[lT_`d�v�j���P�
�[�stx�z��E�HL����8oP������������<��l�}�<Jo�"�(����?�����B�|�l������XIAC��KK�:�=�����H=B	6�:�;��H*�J���G�����^=����Hf�)%�)lR�$��C��XZZ�O����_�Tj$5�� �3���LpU��0k$+_{g�*�My����29;��`452��[�G����,�����z��Jjp�eM�M���o��S�W3Qu3��+���e��� 9j �����(Ez��Kk[7{Z���?�[
��N�S`6��@����K�d�4l��
X'�G���C�����0���*#��������tH3����4�.���I4��T�a��|*��� `H1��)��zBI.��hK�~������

�
Y�v�P������u�������c�B������lF��S�u'�9�l�����RL&t;'��d8]���:�vk�K��#��r&?�#L�b����\�&�D������f�-���Ta�������M��\a��wwpe��c�t �Y�2s ����k>��[&��Tr�G�4�w-L.����O�LN�D�{�*�=>o�U���a����B?F+���;g7���=yH{{����%�n���{�k�i)�mZ�/N��h����D��1����v���`M�f�������CQ5���5��|�I�mt4���U��~�����o�dp����nW��f�;L^����2fVQYb,�WYq�����iWdj�5�F��AoA~Y�'��"����]za��>:g}���D0���MC�,mJ�u�q�����G����.[r^�_�#S�%U��q�J"f	��*
���o��H�|0�`��i0��X*�a������E�_	'I{���-m�`�������g���S3�������B�?k�<��~�v��l����\}�7��m�|�?�Eos��0el'��7oe'P<�
����0#�^�t��)�����?�*�Nj\�=3��������i��,�	�L�kf�����Z��y7��V�b-b�+6�N��&����+:W�;����N��6��@�c���T�}��
��ey9n���qH�{%*E�\�S�t><���'����_����z�	U��
��������/�5xEb14�����2��I��
5��+)���td�tD6�h,�<L���{y�Y���Y������r_E�Y�%�X�^��^����v|���u�����#=��B�6���3��[T�+���d@���A�=y�q�AB�/Rj�]vZ��9 ]�	�������I&��h�-U���9B���XM�������9��f��|d^T�e���\KJh����u��T����xW+�n�}My�W �f��=�����IdHq~�am4=��*>��7��);���.�w����p<z@�f��h?�y���3|x�.w�_��,��y���c�8�/jz��c<
T?�$!)V���qq$!�4"��"�9O= M�=��� �!�ctD{mh��CZ;��L��x^�;3�d>u
l@�_�b��������=kV"��.!��f��yi��d��cz fTh��~���{�_���������K~���z����<���:��\����:������;<F����+�6�Oy��'�U��Yr��'i��������VLU|�#�^X�K�^���V�U�p����n�v���*<�9���CL�5"w<����2I��5��!A]>���s��]�G�7b���$�h7g 3�v���lV�*S2���x�:��I�Om���?�@��x���� �p-��vz�������}��u��8��O��Y���qDgT��.w���B9��H9	����	�6#�;�CB�p��H��������+����"�H[e;a��X�6Si��
�WB�pr�$��H
����f�FFT���/(��a2_d�NM�������t\o�~Vw����Q��IRH���%��&��x	u��q�A����gv�k��X�~>�e�����RA����MU�N3:4��}�hhga��Vk/�J��%^��=�"�us���$/o�D,��*����Tr�����(��:G�{�z��A�����>E\�@����m\����YL���O�����`�����I���*�g
w+.�����w��6�@K��t��Ej��\B����s+"��.����O�i�T���@����������h�B�=�_���%�x�<\�����z���p��W�VP�"��z,WD���.�a8�ek��:�����]*�i���S�I9U���Z��D��B�e�Z�io��):=�94Z2b7����?�?�F����#��W���^j������M�3�l��?��;�S��,l0
����'��QB�U������!R�Zd�����d�a�D�����d����t�D^��W�HDn�4I�b�D�	�������a%��)��Gty�����$�w��Q����r��{g��������:�'�DZ��-P&�J��g�7�K���U��/p�KO-������(�k�oE�0.l��M��h�����1��&������:�	��t�)���(jE����S��� B���|Xl����I�=�T� �I+�H��S���������Q^3x����aI�(?DlW���i���n<'��Q�����dr��L�kTT���)I�cX��x�P#n��1�����*]m�)�@F#�R���u�~A:r=�@���?e����+z��>�_��P���'��A�s�C��3�;}��������!�i�q������t��hk"�p{�f�yD���Y���9�.����H�����D���f����8��3,M�9����}����������bp@����3�������6������[RS��<���8h?�+#��7Co��B�O�@����K����y]��0k���2q�������^��y������-'�H��h�p
�����u}��8<0�6�+��n��^����?qJ&S6���0������t�r6�U��������U�+9�AYg]�����U��������NO���o���d����v������J��A��%�gs����20�(�Wg�����#g�S��=�:����0H/[�5�t����:�.Ga������ ���g5ARM��um��P�P|<����w��H�a��������n)Db=��&�&����o�������f���p�n83�O���s��(�!x��z��0=z������.
m^��7���#�te��^��g���	���N[i��I����6�~���h�������s"�#r�������Y�4v|��
�h��A��[��$e�8�l�L�p����1�D,>����OEB�i����9=��i>yB�E?=�P��)P����>lM#
[���d#����V��P�F�v!F952�����!�"�����3��t������n��fv�ImH��
4�L��S��[T�|�djv�l�v����{�FU�V�Si�M�03N�����d�ak���M�������-���H�m�t�0�q��}l���x���Z�Qc zi��z��@�F��2�0l���X���Up;�����?y[��h^��Nk|S����'���
��%������lL*��D�*�go��\�K���nXL{}�i����b�Ut��z�Q� ���B�4�l�18Q7�)��,���nV��*4]/J��������w@����[��b������{^5P���x�$d����Ci�E�de��`�H]y����Q���U)3S�00v4����Z:��1'&:���:��[��
};�-�.�fm��;��g/Q������p !L���2��-k���wl�V��4�h�(O��~�
G�4c\m�\=R|-:�����6�(&��zU\�B,1� ��I,-;q��F���jj�e��\��%�%#p*��Q���9%�659�D!g����L����X���N�G?w��o��b��8���N&T�!��� (���{�52��&�j���N��C��_��X!6��E~��
Rn$Mm^�$w2�9��Np��`,�*m��X)�IR# ��>�13y#���)�!��X
l7�T������^ �j�����!�� u> 4a:
��5��y%��B�?Lp_2J��>�(Q(��8.��m�f�� g����p�w�OtO���	�Nz�1�!�T�`A�BTC�Y
$jD��I�S>�i0���g�O���i$gN����w���?P��!��qtz 3�������`Z`�~���(w��8#�7v�!��9�_8?6�c���"�
���f$u���E��3�g����gPU�x!���3f��!�Iv�])��M1_���r?��]��5~�=�Qnf��H��K�>U���*��4#PD���
���)�{j�L��/��"�E����(�o���a
}V�M|���a��,��qpe�o��W����%
_3)�!���D<��B�Y�E���g�)B���Rjc�v`<�'jz
D�����<(l���F�lMph�2M�u3Cq�i�N���9&�#��BK(0c�Ow���S&NJ��M��o��\��c�����Hp����Z����?��o9���cZO��@����\a"��	��<������h�i��{t�����
p���lB
���w4	�w�Z����Z����'=�^%S��j��Md2�^R��?C]�~Y�$�@3JRExE�@�@�5"k�����b������O���������)?	1���$�����f�'���)C��+�b��^���t�g�L@���v���!}������Q]>N~����g�h���6�������+y�:����V4����@������S)k���_\�a���J�5�C�,��J��3�f�J�#������6��O
.5�cV����M������3��sQ��{�������"�#�%�NA��e�g�vv�F-�����dB��Nt_O���*<����;��oi��M��e4�����{������@��F[��b�G	��+���gP�����I�n�8�P^05DG'�c�� G��@��#b�[TP������U��k���O��R�m	���'�3��G���)�F&q�
`4N�G���5p�k��\iG����@.^ZtrR�_��s�Q��k�[����_x
��i�j
.."]���E����d�5�J���n|�$�5���k�6���;�ztS��C�I#��/#�8=�:oz��L9����[�l)��N-����3v��3���
0��]���{_y����*���H^�L���YSx#�2���f�����]b�Sf�C��^�Kd�5�MWPP[�zK�q�����Ru�.����!�AI?��� N]0�N�r:���W��[���_�!!.~u[Nt����v
��A^V>�~�L�=����M|��R� ���Jv;�mz���N��	�J(Y���j���Km�?���7G��k}^'������j����`��m��L�S]���(f�c�mh-p���qB����}�Q:��
�f6��!��=��?�����l�&��;�3-v;��,.0|�a�1�#*>�5����������tA�����A:��$��c��z)u�p�T�9�g��+mW	^I#
�*����wO����B��:��21�������H1
�.'����]�ix��h
�C��^\�F�9<�R:�TK�*Yb�3�p�Q
u7�a���a)������.������9Q\*j����B���R%>M"�nB�&�Z�h��	hT�AB�;�&}��U��{z#��t�r�yUm�$�_]Lz��9%�h���F��I�Hi��	�<�g�������:u����6�W_����h8�AkR���[���������S����#���dW��%���4"� =���������`�ET�.��B5�&�������rGRv�^�*�x����q�zN�\��Ms�Ip���3����'�[4v��E�g����n�E	��f���W-�7uo��?,���/���G����0��{�<��5��$KB��C���c'<�b�����yI|Y[���P?%
�-(�Ym�'�W�{��B�P�F���GT�3�v.�J)I�l\V�[���M~�{�f�&����jlVX�p0�8#J-��#fY`��;�M ����"�L^�-c�<��e�/�����p:<(
���cuo�,�t�u���9>A��i���m�[5���\���C+n�sL�C�(n������V��~�N�D����$���X�f��8kd������6vb�K=��i�!�.7#��[�v�NT�t����O�nL�x1p���������f�)jX2�K�\=������~C������������s���w�t�D:Lsp��D
p���F��G��+#
{\�~U��g����LLK�l��s�D�^�N�����;����lN��!*�2]6���}��6��j�/�0��|���w	�J�=I�U�)&6��e������=f�3����!�cY�=�;	?ZAl"z�]lc���5��fi2d_cE`�f����7��& �
f����mJ���;8��
�����`�����,��1���"��� ������8Y��n�7���&�1HJ��7p5�)���������W��F7����\}
����-�3�(%P11$g�(��1���!�Rp����<�0�r��'��0�t4Un���@��*�#\e�V�=y����8��D���@
��{�P6:���9*�)�6D�d��"����+���b��
�����29f�iv.Q��A���bNSG��������=��h���@�I	����/�A&���Z�y"p������6z������o��.t�|��5��N��n%E�U
#s���z���s���� H�5�4)�=�t�����e�$��,n�m���6�q�N=(��_��9�'�O�g��di�g5�p	�X�*��Q�FZ9�67��;j�N�	�)S�A�1�O�8
���R����tJ�Z]r���������k�Z��r�n��� 0c��L����=R�W��;�\�������n~7����v3W]H_��,�^�����a�����e�#Z0��K�V��ee�����B�QU�*��8��
kF�4S���J;�L��s�;*�������&�����,IK:#:��W��QRp�_)�G*�
&�p���zF �,�:K;3�x�������B#G�"�a/�^E�!SX���R��*��������]'�x�A>42���I�NG�����zO	b�B���9b\��.�������Y4ub6�n\9�9F�P���3��(��	k� M���Pb<%�;f�1��$*,�Q~�L����(��T���YK���}M��!G)��`���P�X��?�ID�R��!�$
�����bf�.�sM E�q���6h�	K�{���Q���&C�"9��I��i�LU����]��^=��F�v�}���T��J�q[m$����m��[�I�7y	=jm�P��"��s6��+�RM��QByx�i�l���!��P��z�^����2�[�?v�D.%����q
��x8�&�����a�6��M�V�+��Q'��{BO�~���$V�����}+�!�a�P?#�`�O�rG�28F�]�����9C�D=pu\����|z���|@���_��R�s9�v���5�%GT���>K�v�7��|WY��:�sSA�U{�����k�B�O|(9jOC�	+��(%c�V�h��,�/�%��'��R�p�����+�K�4��|8���8�He>!���&�����a{�d��g{�����^���J�X��������2hh��k��Hw�P��U�������<�����T�` ���t!/,T0�n������_�T�S��1N�����������7���!F��������/?������0�����n������'����<m�\^o�>����`�5 K5�����������g�jHEF����,�&).�+/���r���tJ~YR���(?4+�����sn2��/ G�=��A`��3�����@Y��r���fxjM��)w]5K����*}�mme4On�?����$Y��,���x���S%'��H�<�"����'9Gbs��,W�9`+s�'����Sp���id��2*x��Z��(���;����{/�9JI����d|5���<���Q~5�<i������)2�R��L��,z1�+� �5�<kn|����3�FJz]R��D�<28h��=����� �R3��V%j�)S&�o��5��+�|����
�����S^���������R\�co{�q^�����U�p�;�{��)���.^KF���(���l�����6�����>���~�3i�����#1��vq��0�	�E���d�Y1��@J��k������@���%`����wt#�����a$��v;i[�<�����������cLb������/��<�=��[qe�����F*�Y�A�����i*{�sC�vEw@��Z��N��%~4��<���!����������L�pG����E:������|f�wj/ld�U�-��|g�%�6.8��*�\�ru!�
G�3����Es�B#D������g/&C�?Y�w���?iz&��RUrZe�V�V8�r���a4���j;�����mqJF?�!)���raDb
��%�+����at��aLYEGX?�����2oo1#���|/!���\����a�%��5�Vu��~���4��d�d�����>�a ��c�f��AD&{����1���a���N�~�tc{co}}�������A�aM�RW�Tl(�A���F3am��7���+6G#�}��(|q=$(��A
�����6�(������Y���N���
/���,fX�.n-o�y<����Z�_��,c��T�%�|lR�0� ����O�V%m���YNM���L>9H��� 9��v�M	��Zt�9
��c
,�{C�������������������
�T�/B>p��W�|"R��M?����zB����Ia�K)��
�F�{k�^Vi���2t����y�������~D����Raa4n�5���I/c<Jj���=���<�����u��*^�Z?�	����E�# R>H&g�p6U�����_�p�p�6�e���0�0
�c��c3�������"�VT1�f��'�I�@O�����V6
FN�*`[���W�lp�f�A*X��s���`<0�x��_R�}=qNB}�o@�`+�o<DN�2��Rp������C����e�x:%?!�>6]��@��3W�n$\UJLe	<I�U{��%m�F�4Gj��4uG2t:�1"
���������Q��(6#KNJ,q��Sx�����u����bG��5q/' M������@���*�������.�9�E��O�bP,��$���B"A�5�l��J#B�a�GO�>g�N
���v0A�����l:�rN���4���PL`�,�(/\��:����� �����bO��\\YL�ai&(��;��SP�f�r]�zP��t_Wa��r����2�p������4���e��3�zCv#O�d���JJ�~�AM�?M�Prn����P����0?��|��
F����%^HO/��

n���������FD���
�N�D����5J�^�(Y��Rx�5sQ���)��l+	I�;F@u�
Xf��'4:��o��\�	]�EW�Rf�I���[D�����F&_&?���2]~�~����>O�E��[D�;�#]��1<FT�	����.��6=�����6y��Z
�yebS-���bzI�h^e�[������d�(�pHlUq��J�}���P�*�X_y��K� ��\��f@]�_|1^~����2�F[��8��'R�+Q&�F��S�tAQjC�BqCr���������Y�;���y��~�trtx�W���]������_�ON[�'OO�5��	~�AS�`���9����p�+���O���`c58���S�n�e�v��"���B|I�N������@h�v��vQ��v\��8���T��
K�UIr[���
;b�G� b�9cp��Rx;������nS�����FU������8�=����L�N�r$��Q���e�`4�DQ����!�m�.p�W������C
���
��V�Cyz��/-v:��#�{��l]��9��7-xe/�=�x/�
ro��L�EY�t�~�Bt��0N��7S��#b�-�fY�P&sx�}���
s(�wtix<�x����'��7��^gFzab�+���Q��d����we>�*�Kz�k7����Q7�6"�d:6��B������NU$0n�`��d��������=�=���(�L'|�aMT���7,�UJ��r��
�,@��>H�D�*�����Tr���Y��/&�����"�Y���l��jf�WZ�I��,k"#�F������r�;�\8~����5�9�����\�����`D�����n��0pu���"�+OD	m�FqS����4M�VB����[5e�[�t,K����MJ*��tI�[��n��z��i�����	�)�Q�����f���W^�%��c��6Yj�W������b�����X�O���/��6�(5^w����+9y�,���gR-H[F>��7KU��y������������������3�A�����`�b7��_���.K����&@���KS}�C�z�����yx^�Z�i�*I���������a���e��@��GA ,e��9���S8�����xS�"I`%]<�TRQ;�9y�-.��M��+);ouVD�K�s���oPJ��}P�����d�������,k����g������R������+�G1.�N�_��������+���rYdx��	kp�@n��trd�������.�u6����[e�/��^wq��U(����y�"��8Fdn��b��X*��H%7�����PU��)��5�\�)�;k��3 �������x�����Pn0�$D��B���A��c�:�����i&fNHu��i�^#f�2V�[��<���a0��������oj,w����1����g����I��(��2�d��d��`�C��5+W:y+�S��b��ZL�w�x��*�r+�z�xf��.���l����&Ar���?��@w���xW6L���6��2���NP|������nW�8+���c����Q����vh���cf��]��H1�a��^M\�n�����b�;�|�f��B��-�(�.'���4�5�Z�wT�Fa�4���s�@�P*��R?[�7vqV���������������%���oR
��N�*z����$!q������		q�Z�|����o8=�*����MXA�ki� �Mz
�GP �-l0�lE��b��!��!?����������\D&C4d��C?2��}����F_%��xt<5+�"W�.O��`7��H2��y[�p<�P1�iL�
�$���������$�
����eQpd5���0	��(�����p�������B+���VX���
�P|Ei���]�,�Ro��p	�2����r<j����������3��:�#i�nuA-M�6[��Z��V7�D7t�B_-���������������V��S`�������I7��z��\R�Y�+�!�'-��Z7(�75��*���F*�v8(��
�����a�A���Hq:����U;�> n���csf"3*��g)��������J1���~�_]P�Ed3�����fe�hoFV��� ���`��
�rds,��&������������K�x3�=�����E�kx[�.�"V���l��-F�`Uf��A��,>0l�R-q���U��Bb��bc���">/�V��!lA�1Y]�DW��b�V�5�x�]�n���_�{H��+���;�M�d������f�������g�����'O�WWW�mq���Gs��o���nl<~�x�<����O@��_O����oaI��Z[�����'��P�H��&[��q���j�����.����*"o�(l������k��Y�N�?1�����E�6����a���V�\@�p��z�y�6�/��ezq^���*�k)������,y�����I��
�����_�A9�z�h^�T�E�|T]���e�2��0��j�M-U��Ket6�>�]�K���Oq666����!�S�d��v��^:���m����Q��0'sb�Gc��K�7�v����5�F<�/aO�zY��.%�k�D��>��+a_�<��@�-�]��8��0!sFB0_�7g@7���[o�v}��{,P�� ���S��������b3h?sf�{�3b�GI���26N%'N)�)�����Wh>�x�b2Mq��NX���^Oi������Ya
����7���.�,x�����`��Ei�v�8��>��r�|�=�hl ��x���x��1�ncn _��	�@Tn�zB,$�)�
��}� ?��%1����K ��M��E���U�v: ���c�{/�p@x#��Hqy$��qN��or��qOY�yH�y�{�op��3�t��sI"�;O�W��6�//���q#'�Z����fi��i�k�����)&�*B�T��f���g��;����c	!f|��'���6�ajf�!��O����'{�F�t/�;��s��c��S��4
���b2��'���S��80�1k�d7�|�7��zcc7�b;(A�7����"S��� 1��d:�����z�*�v������t�YC Z��`���3���e����p��<������������;���L����������m�	��/����H��I�E T��]�wf|�;f
�� ��03���q���U�A�����Y`�67�6qX�7�X�U�4)r�k�e�]�Y�x����6���=�t�����x�C}��E�C��I��X�-L>j����JC&����'0�O���xy`�����|��sFY9�ZR�BN�&��&��Z�,"�8,	h�m��C���0���J��J:^�
��9<���D`�D����9��a�!�ys9���:�\'b�x�	���L�5��R��2��*�gXG_c��!\S�U�6Uj���P��?~��3�����.�w���Y�9L��&��/��(�[��Pl(�Yi�����k��������D�M�W���j�����!�1��bI�~��I������t8g?U(�q��cn�������!:���r���
����4T#��9��a�h��G9�����+��?��xs�����]�,N�����s�/1�����&'���$��V�H��isK7�����?�x���%H��W�2�����b���>���IG���A���{���g_�z<y����S�C�����0@�L�s��w�V������G��_��2�.K�J0u��{PIe���)0�B�~����������WW��l~E+�V�%�������
\q�?�������p�H�\�y)S0G��4��h=��������<�����I�T����}��9+,���A�-�|ch�)bU#�^�9�l�~�e}k8E���2�g�p �|l{���CM��.#NZ+V�>[=��*L�mF�HH�L���]�!�>�nm���^�Q</�����#T��(I�����6st�c�#d�<K���S����
����x5����!�*�}��)%��������9L{�����e&��l�p��v������YO�����[�.
IHU0~���l����N	�nDT�����^�����}�l|������/�?��p|��-j��4�_
/����$t�,�2's�}��J�A�j.k��f�D9�nG��80��L�����	9�+l����I7���O'���&��lU������\E�NK�	���BP�����������W�����J����FT,�"z��
��U�u�;�.O'�t�H���l�ZV/��D�'�Y����%\�)��>�����Qt��LM�#�E� r�\*y�GNU��2�����n�������O����������x[����/���6��w|�%������(h�k����(
j"�1d&�+��^�R��)�2���RP/��u��������q��4i���r�(���:`��6������%^$1�.0�']y�K����z��F��+�`h�y����c�n�.C�4���<����nS���
H��������\������ [{����G��w��s����G{8�89#���M�L.|����Z�\���|���_���ks�����������b�yn�N����4k9J�%J)!t��"{�y���\���zZMbu��@���6Qw������WFJD[@{�v�8����O0I�"a����v�6'�A��EwR\!s6V�RMk�/z�H��0p��e����M/����a	��m�v�n��d��������y�<��b�"�Aq��F����hZ^>�iy)f�on
u]�`��8�v)��8m�8�r��UZ"����B{W��A�S�7%�>;�:�������C+���7ky��o{�^���p[H6yz�d��&l�������/���N5���S���g�d{���':e��|W�v�����U��������$������W��N*tC���w����"C�N
�T5��{Y3����>�>��j+�.
�l"���m���.��	
6)]�����P���z�Sta��u�M6l�2�j���?�:�b����|�Q`U;cM�HC�����Y�����?�dm�������)y��x�����p6��5f���tbf�e��9sU�+�K�v_�&x�����=���6����;���V_�:�����l �<�������*U_>gS����~��;}dZ(�Hk��N-)k�1ark<���
�&��`���>��\rl��*�C4���-k��"!��\@8���jL�;l��g��f�L����;�{$�[�R7x��ZB�js��S_-�2�mv�;��yDi�NRS�!�h�2�|�PnM��a��}����~x��J�|S������]qq��rev.����?�+���������L�����)B����S�?}��x���Z�R�|�zg�O������c(o���y�>�� >PX)��h��%��&;��d�b��~���'�p4h��&�
�h��/���F����R2b�4��w;�������g{'��d��d������������;v���������i6f-5�I!B6Sg�wA`�2�P�;$�z�EsE"����:�z��Mz��H��?��l��zv�0����Q�������A��o~[A������p0��O����x\1�d��	��������<��&2��\�0����@��"E�T�����#}��"{�9o6������X��HW�8��W|C[}�)����/���1IN	8C����)�A��2����SHYF���@�}����c��@�����p��j��5q���
/�pK��9H����� e�%�x����5�+��_+��J���O�1����yU�=�j�$�}�H6�}x�`���d]���?P���������E��������o��O�8un�����1���\gE������S���'������TK����l�j*��n������l��Z��yF;������v���i�fA_5g�l�s
qC���u����������v��������3�����#�S��%*%�o<������YuE'�={���>K�g8���Iq�g��_wv���WrJ[
����Y������]��o��X�����I%"��&N:2g�|�����	z������j9[6&�<��R���MK`����b�B���p8���7�4(l$U�h�������0���#�7����L�b�!������!Y��9�L*��n9Y��������f��O�=��DhS��������3v��/���3����J���*����A�|��;A�2�;����ut�x�)����y0�^T�z|��>��v���KxY����m�������� �VmQ�I���^���z
��V��u��*����g�o��c�	Lp���<)Cx��V��y���dG��u��'�Q:�p*��HO��*�k4��C����Ma�����D&W0�H
��18@2��[~u'������e����
r���a���(���zm�O����h�����������t-x��J����E�H\`?��&O7��8��&�r@����������
D�;}�r�LD���lF�!�$�2�s�IY�O�QA(C��?se�T��0iSf,��M�����B���>����`���yv�o��fR0�r�WJ��5y=������S�iD2�%����[�~��m��?z���@g�>|-�Nn6:��'�ipHv����}��'��c~%o�1�%����RV����&(�\Qt��%HZ�M�WP�����,�c;}�����X���>��N�����q�s�����gS5�802Iy�f%��T��E�h�	r X�e:��-c�q�;���k!�������+LX��tZ
�VO���E|/�R������$�����(�:�M��}G����#
>~��8���WsV��$�������h����T��}�[�n����/���I���:r���.���n=s���;X:c���%���������������ZD������~��$7�$���1x��HY0q;"X��s��T������.�����[vP�����5�`bQ	]��K��ZpX:S�Yx�S7��y��_�4v��t2�0�T5�91X��z�Fu������?����t#^�n8��dh%v�+L���-��HM�!�
���GmtLpPC��u�D3����a4���G$C$g��Z�y�4�o��!zH�������lL��^J�U���f���(6���}xs���K������LW|I'[����	��d6��Y.JA���5�����o;����^�S��8���r�=scGV��r�i)��w�k��2�_9�����t�#�8!SE�_�Y�����X��)��*�SH(�0�fA�z=~���<���[�2�i4����D�F��=�7�R����v���;2��!��(B^�2��������<�x�(<�9)s�����*��]
�����5��#k�H({j����N%�o)Woq4�C56���^����
"8p�K\�
��o\=U��j��# ������a�o1���!,3�9��n��s��s��K��it���G`�����c'�9�![Lg}�s�v�>�I�u���p�S^A�pQv�)F�%�����R������-�6N������������>��
.+����I����$0���B	Sx$����&I%W<�;��9#���G'	���}<��>�C0��ii��9f����������!	\�GB����,��w����Ts_�q�*�*��e	�<`�F�{�	gc��������`�������y_C�Y�5�%�=v���6	��7�~*�w�MO������[�%���j�h����
���t�������_���X������@L�+��v��P��
�t��.���&+�d�����\��[�F���]*���n����%.���K�����-o��5s���m6���`���n���vO)`=$��� �4���C�X�Dj#��HR��c)�0Up,����"OZ�P]��0��Pd��Y�A�{M����{\���U���B�:���	���OFE�5�]��&L�u2qMrF�1�5UR!����dO���[�;6������E���C]
m3^���).��9K#����RD9�SA9p��{���mmM�,�G��i�e+�n�7��_�g��zr��e��,�e�Y��9�p�'/���v��	�:�����Yp �e��;�����N�&Z�0c�l��������i�g`����x�=��Y;>���������Y�9je���+,�[s����.�����S���,y#�~4�����&&�p=PeDF�j��:�<6��z�t�b�eK��v�5�����!)���]����+����-i�ag0)p�"'�#�1���FF&	3���Es� Pd���z���A~A�!&�IHL)�k����9,�|�(X��>���<��d�x��v(Z�����9��k>��n��������H(
�
�)�7)g�������Aqd�V����pS�C���
\a"����[��<I��s^�����J�^E���R���L��J0$<1O�H�������@��F������lh�r��[�Q�T�*�������v�zL�0���rW#&��l�1lj��-�s.�X���us�L_�L;�����t�E,�1���
y����A *���cq�*&��
��&��w\�XA_D��w�a���;~Y5�����
L��{C $����`9ag����S������k^�(�^�$n>N�l\�$�x�t3k?-Obev&+��W�����DS�����*b�����C�S�v���b]��?YM�%��'�*����6�0���Ytc�:��-CS���{t'��=����=�������h$�����8�G�Ceb@�H��{���P�d7����	FgM��2kO�t�2�H����%dg=~����m6����v{s������\^)>��e�K.���������������{x*+�?u�����h���������v�ac��i7�o���
~0���h������1 ��awSL��!g�+.[���I�*��KR)��@N�������&,�����2��;o�sg=~8���O�p��$fp4����$ ��|a��d0�/�cJ`�<����H	���������G�-���9������}��k��"5d< _��0�k@��9����������gi�l�w�>��\]�5�OF�E`<�d �g����}�]c�l���������$z]f���� ���;<=N��}��p���	�w��-'�{�;'���
�:���RM���5�F�93����E��x����O���S�O�p_�ds���de%Y�m�qc���?|��4����0�4h����Rl��C���E`%����Ny���D��J��V����������nS��g��'O������a�@��-O������������X?��v'� �)�v��� ���r�nu�~|J����Gs�n4��*���3H2;N�3�k9-�pK�����/3��5:;N���(����x�b(/���7��F�!#�|<sL�V��t��sF=�_J����x�]~zf\O����C������#���"TP#���nQ�~��f�E����g�d�l<��|���_�'bF��W{��G����D�Qr2��=3���}�a3��x	{	i0r�?�D�F���<���7������s�)0Vd��:�Ud���u��������gd�6�le
7����N�	�\���]����Sd�O�)%��-)���C�
[�@�2X�y	:�k����]���	�=����@X���-dS�:��J�2y���w`p_{7���U�v%��F�����X�X�e��)���)�(,%�i6;�����xI����'�5F@���@��e��W���JjS��
r�1��������%���k���@�(�fg0���'�v���g����n�������y0$]���`2�Q�D���H	q����`T4�����g�<�n}�IBU����{;G�{ ���}R!��t\�� �wY!7R�#V�'��X����^��Z�=�c}��=��^3IIjKb2G���Wz~1��1����M�������������m�;;?�����zz���es�C�#�������y4EYs�s��p2n13�3��0sV�;����q�Z��4a��4@[������J�,j�0���zY6��Fy�����I�d%�'�n��[����dLi���A�G���<;��R9T�	<Y�A�_����d�P��Xm~?m�mF��J�>g4�����S7��0oY��fI���B���F�&�(MMd����0u��T��E�v���s�����E�&����Fiq� X=�b��sb)�[�����(~n,��iV���)�~�Jl�p������-�.H �r���6�E	F3�����r�|���}��6i+����R�n'D�Q��Oh���2��C�3"�x@	��t�K2	hg�A�W�3�F�����4�"-Md�>]iK���3Y��$��C���W[�v�3��v����aV@��fY�}IyA�\�!��!�G����Y&�8�]�
����d�����&�Y*�&��"v�h-�G��H��%�K�&��"-�b�{����(l�=��R
G��V%��s2PXdj���&~.R�3�bg�Z��sL���De�xL6|��`h�ko	hd�&�5���{������ON�����������c&�p��E�(���i�z���a��V�7���n��b����r������#���i�|"�0����\�n���@��;
�����	����%����uuz�_O�A����%��������v���EvU�IDw��f�}��~���o�d���;t/C	���E�����"�n�H�*�G|[+U|ey�����Pn��][�&�2���u��N�?>���E��b�Z��BvY��$tB�]S�R���?bL1��Hm;�`H���0�
Y=�%��U����w(��;Dd�%01k>o&>6��<��������������9
��rD�5������G��s�'���YI�kB`0R
A��o���-!-�I�����-u����{�c�zk5�: z(�����{������a�Q�)�������������3�W)!���R�03�cfR6���D���
S�c-<��dGE�0������jB�	����;U7G�O2�C��W��<pI()�A�;�B��0�wpz�L*E���#����K�������iX�C�"z��L:]��4R��t��W�i�}���s�y�y�.�a��DD��M0��Z`���\��8a�DBl����������7$������3�*�bp8�y�����/@G�'hf-d���`a&�y�P��T�x���R��G~�]��U]2��m|�����t�����\�3j��"�}�y��Rp>k�������]T����W���jUmpA��5�*��&m����;���y>�u@�m��'�F�Fk�}�Z����t!�p���|{52��q�����/Ni���3e O���.\����`�#� �Nzs������p�>x�����/P@O9A���^������,t��v������4���Oh���&�p��j]����a;�xv���u|�����{�J/�7���q�"H(�p��l\h��� (��j�e%�JV_�����Du&#��3��������(B��]�3v,!�riU�MR�
v��uV�"E����T��>Br�Ms+�b{���a���d�|�S���s�/n{�pw��ge3��hK|u��)��f��<�h�SR�P���l(�e������#F�	�0��	����g|��gD�L���i�����}>LB�sa'(_�5�l������p�������y:��+���^w�!��m�&c����D�7�6S���lb]9�{jD8�����L����_R��u�����I��y�5Q���a��������o�;k
�����y������|!�&l��o?y)����%���a&\���V��-Y����$��g<N��Nv�N�||��b^�Qz����?�|c�����m��wAL/�1��0|���W�{E�}n��c/)ny����%��s3P�|C ��@��������X#��P`%��E��S�p"�f�N���Q�c�;y	�2vL�CE��	Z?
|��4%���Z.!���+c���!2���Czc#<�fl��Gk8t��&W�0-N>M��V�O��>HL���j���������~���^k��t���Q��q����������\rl�m��
u"��@��5���g*��->,Im��40�
�������pD�������w��nk��b��x�2"�p2�
����qF��(56vT����(�N��vx������~����e���)�n�I���6i��&|���c����[��xB�x��^�	2����LhV��o�����c~��4�y��_��4���0lZ\]0���[��o�OJ������"�z�<�m�;���yS�yi�
�q(�F��N'HM�Q�-L+����-W�����y��~�=�:�u��'a$��5���	����G2n�*�)���u,�@.Z���e�t%��8�G�p�&���n�z~�$5��N�T�I��r]!�Ui������:3�f��O
��=KoJ����ti�`�D�'rL� #�|�����SP>�CH���������?(��������n�?b�!flYm�5��"��;�������bT���(*�br���pW8�����������cK^���w�����#8"��g�qV,Zc��J�H�Awz�4��.E�2���l�9�����@���$���PM8O�����~T�6\�T�v#T�|B�S6�����Y������R����x�r��xX?L�t��b���
V	N�D�[h���+?L�n~��bn&i���)�,��!�?v7����D>-W�WK�
�}L�y+���
jf�4�}�ln��N+��	�����sG�<J6����u������?�>"����kkex�7����L�.��&���I��D)�A�����B
����E��\M�

�'��t�_gdMt��is���g�\R]F��.��S��Xfyq|P��D���W�HN�PD�����(&?�����Y�#�.G���Gq�I�UfPG)����������L��PS�u�
*6g�0���8B�Vt���/��jX����-�*��(�Ab�/���|~�+X8(�����������(z-E�
m�f��N�WTx�	s�M�#1s�����n�}I@9��jturI�/�EL�������
+2��#�<�����t3��P]=�������v2b�(���WC2k�,+��EHH�=2�v�}���F�8@x���p06b4J�S���JF�������������Ms1��;V���h�.���t�?��^���t_vec����<�Y4�����D��.w�j�\��M�,��MX,����)H�&Q|pA�3��x�2�4
'%�e&��\^%xMc�9���v��������HQH
����>v�1�C�����z�V-!��
�]�P�����*P,�ieT�b�_'}�Q��z���m�L�������:�U�cn\���&6����H����c�K3S���xh5R�t<$�@u���=vo��'�D�<��\�������U�)���F����Z��"��a!S.�}�2���%�^}{Jlw��"�.�����f�A��2]>�E��O��Jt���}9O-���d��2��I�g)�bu(�E	�V��n�xR�Y��]�_��x� ���h�����{k����,�\�5+M������
��F9�����*M+��������1���B>/�sg���R?<����H���A0A�
-�6���QwO���&�pX�rF|�]7����h�������l;�V��J>x��nz���e�?e�^%ks������H����AZ}"��m ���^��w �3���{�l���Es�������FZu�8���W86�yZ��S����[���|��tk�Ys*�r7�p��������q�F�v�$Pj"�Ck����q`��� ���8�n���b�`"�AA����v�JrD��A�L��I�}���xm�]�S���?��B��t��CL�lA%|eT���������Ui��-W}����H)Xe�u����Q��FT��	�����l�UU�]�{%9�W�iG�P�h�\l�O�:$
���L�(f*z�M��U������I�Y��I�K3'5��T
%��r?�fcTo�����)'�d��P���w:?��$l�0s2-��Y2�=BS����������m���FZ��6��i��$N��b����+�C�%Qr��,�U�����kn���w�����n �^������A.�9j��H���i�p�Sk[�j]�
�~xQu;@G�U�
�S	��]W��3E�����nrb����cM��S=��Rsy��w�o�|�x!�3�
������Q��bN��Q�w�1*��;��t����x����������o>���.p��
!(ZT���.�_�*����,�;S�s��h���^���D9v ����m��rj��i�)�!"�&�W��
m$'!�lT;��h���H2�1�	Z�\���J�
�R0���3��l�
��7%#��Q"G�Ylt�u�hGa���������H�B�h��c�c��~e��I|��������?
&�-��%Z��*��Rd�h������4�����JO9Og:��a���Y�q8�y�.�t�L<�� =���
�yEl�;G����g{�g�6h�8�JA��S�F��o��4O�:���g��.�$������d��!�J�a�f���z�<�y���wLk�����t�Q� 1�T���d��AH��,U$r�eR�9W�P~�-;���"��f�����}�]�a��m�5et�wn����o
�H����o��G\Af����� @�9k)�ie�~��)�&���_�a��8�a��q>3�1KS�k1���	o�����]h��}!�E��%b1����vTP?���1~IL_LL����L���X�x����.���Hp��"��r�VR�������^���v���8�e��'��pC-����	�b�P~�l�4�������g�]�q���q"$��l
g�����#��g�/�4�����>K_V�N(�P���e(E��3�)�P ��B9���`Q�1��[
�gN�?�����#7{�QD"v���R�O���?%?q��RG�a�!��9��
����36b�#���t5=X����	(����i�n_�[J�f��C��0!��)A~��7I
�����k���\{}-K���z����f���"k_<�������7Ep�_P��������~�	1��;����m.�!���Q�gv9B~��`�%����~������v����%����3���k(;���d�����O3z'Ko�	l�� �o�1�������>�%�G���Sq�>��6f/����k����k?�x��N����������s��^A�.��uL�E��B���zf_�G^z�[��$��K)H���?,�ZL�2��m�D�~�*������z�J�	v���D
_�����=��
�J���������������,�����)N����k7����s�e8��W���W�q?
U=�;��/�Z�J�|={���|����/foKSA��4E�:y�����'���nI�~��Q����x��JP����?1^�:�<��$.���TI���Cd(������������q�����xL������<�C`=K|-�w���.:���r����)	��������$vOp��������d��7����Z���@�$�\k9���=�-�/
���������&o��Z�U�N)�+u��Gy�0�*mWt��[Y����g�f�����oLI*~_��Q���O{���;����"�@�������>���xPJ�0��w{��1���{�%+d�x���D��C���w's�����S"�dm6^^<}��-��<�x�b}����U+��)��&%��T|�?��f�������&U��+��$���(���/-������B����2�:������<~�i��0"N�B^���2����
5�������ua�}^����
k�i���R�>X}�$�������~�����2Y�;?�m�g�D�r�_1�6���[�!ey����<ja�����
���N6d�����7n�_�m�vo<#u}/-��Mr�	��/����x�Lx�x8��LD����h��M2����8H}2��f&KU4TN�]���(`px6����:AQz�����a�&
�������#�PK����?�+.��mn&�G"�
&>�������|�����rw/�d���5��a���� :���HJ��N����<}R�L��<���������QF���&H_M0���|�R��\l>i?�h6;O^fO^F�O�������$9<{��m����H�N��v���yv���D�9���������E������$�"��_^'[a�^-�>����."^���/�y��q	� Qv�����SJ���o�����7�`��Vh/�fJc�?x$��{���L2�p����Oekv�6������h��7I�)�t�~��Em����N��?�Z���.��?��s<�����c��?�?����V�?���tk�W�g���7��D9�4�f.
:%WJ����?fz����;�������������9t�S6
�����x����'�{��dk��Nu�������:���K6/G y�AF�_�g1����Z{�$
���������c�l���xQb�\K���%B�K��5�a��5��X^�����!�hr���"�������y���i�9oG"S+qD�i�p��|��T�g��9��H��0�d��+Z�����^
��p:���ux�QO8x�A��c�;
�K�g�����dl��_����U��
�k��H6�pz�)�������C���l��|�~���
�������������p��������`pA��=�1��%�'�3�xZ���k6�h1-�����le���������m��b	}������sQ��d��U���.EB8��'tBp����a�?�����W/88xg6U+��p_�foy�w��K���1�W��D_��c��J�=����!-?��;�\�R�G����}�$A�������
T��Q��iI��W�p�!���(Q�)�^����z��[:N�w[���ZO�����f������b3���)�����;��'G;���=���������Q�m��B��K�Cw4a�5Uo�������fT�h���^7��Q7|To���O�1����/7������|N����h��[��e�z�Q��oa��`2jKm�#��t�p$���'����������/�5;�h|����XOR��
0006-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0007-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0011-Fix-pg_isolation_regress-to-work-outside-its-build-d.patch.gzapplication/x-patch-gzipDownload
0012-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
#67Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#65)
Re: logical changeset generation v6.2

On Fri, Oct 11, 2013 at 12:57 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't see any need for SQL syntax. I was just thinking that the
_PG_init function could fill in a structure and then call
RegisterLogicalReplicationOutputPlugin(&mystruct).

Hm. We can do that, but what'd be the advantage of that? The current
model will correctly handle things like a'shared_preload_libraries'ed
output plugin, because its _PG_init() will not register it. With the
handling done in _PG_init() there would be two.
Being able to use the same .so for output plugin handling and some other
replication solution specific stuff is imo useful.

Well, I just think relying on specific symbol names in the .so file is
kind of unfortunate. It means that, for example, you can't have
multiple output plugins provided by a single .so. And in general I
think it's something that we've tried to minimize.

I don't see why you're so pessimistic about that. I know you haven't
worked it out yet, but what makes this harder than sitting down and
designing something?

Because every replication solution has different requirements for the
format and they will want filter the output stream with regard to their
own configuration.
E.g. bucardo will want to include the transaction timestamp for conflict
resolution and such.

But there's only so much information available here. Why not just
have a format that logs it all?

Sure, that's no problem. Do I understand correctly that you'd like
wal_decoding: Add information about a tables primary key to struct RelationData
wal_decoding: Add wal_level = logical and log data required for logical decoding

earlier?

Yes.

I'd really like to do so. I am travelling atm, but I will be back
tomorrow evening and will push an updated patch this weekend. The issue
I know of in the latest patches at
/messages/by-id/20131007133232.GA15202@awork2.anarazel.de
is renaming from /messages/by-id/20131008194758.GB3718183@alap2.anarazel.de

I'm a bit nervous about the way the combo CID logging. I would have
thought that you would emit one record per combo CID, but what you're
apparently doing is emitting one record per heap tuple that uses a
combo CID.

I thought and implemented that in the beginning. Unfortunately it's not
enough :(. That's probably the issue that took me longest to understand
in this patchseries...

Combocids can only fix the case where a transaction actually has create
a combocid:

1) TX1: INSERT id = 1 at 0/1: (xmin = 1, xmax=Invalid, cmin = 55, cmax = Invalid)
2) TX2: DELETE id = 1 at 0/1: (xmin = 1, xmax=2, cmin = Invalid, cmax = 1)

So, if we're decoding data that needs to lookup those rows in TX1 or TX2
we both times need access to cmin and cmax, but neither transaction will
have created a multixact. That can only be an issue in transaction with
catalog modifications.

Oh, yuck. So that means you have to write an extra WAL record for
EVERY heap insert, update, or delete to a catalog table? OUCH.

Couldn't measure anything either, which is not surprising that I
couldn't measure the overhead in the first place.

I've done some parallel INSERT/DELETE pgbenching around the
wal_level=logical and I couldn't measure any overhead with it
disabled. With wal_level = logical, UPDATEs and DELETEs do get a bit
slower, but that's to be expected.

It'd probably not hurt to redo those benchmarks to make sure...

Yes, I think it would be good to characterize it more precisely than
"a bit", so people know what to expect.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#67)
Re: logical changeset generation v6.2

On 2013-10-14 09:36:03 -0400, Robert Haas wrote:

On Fri, Oct 11, 2013 at 12:57 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't see any need for SQL syntax. I was just thinking that the
_PG_init function could fill in a structure and then call
RegisterLogicalReplicationOutputPlugin(&mystruct).

Hm. We can do that, but what'd be the advantage of that? The current
model will correctly handle things like a'shared_preload_libraries'ed
output plugin, because its _PG_init() will not register it. With the
handling done in _PG_init() there would be two.
Being able to use the same .so for output plugin handling and some other
replication solution specific stuff is imo useful.

Well, I just think relying on specific symbol names in the .so file is
kind of unfortunate. It means that, for example, you can't have
multiple output plugins provided by a single .so. And in general I
think it's something that we've tried to minimize.

But that's not really different when you rely on _PG_init doing it's
thing, right?

I don't see why you're so pessimistic about that. I know you haven't
worked it out yet, but what makes this harder than sitting down and
designing something?

Because every replication solution has different requirements for the
format and they will want filter the output stream with regard to their
own configuration.
E.g. bucardo will want to include the transaction timestamp for conflict
resolution and such.

But there's only so much information available here. Why not just
have a format that logs it all?

Because we do not know what "all" is? Also, how would we handle
replication sets and such that all of the existing replication solutions
have generically?

Sure, that's no problem. Do I understand correctly that you'd like
wal_decoding: Add information about a tables primary key to struct RelationData
wal_decoding: Add wal_level = logical and log data required for logical decoding

earlier?

Yes.

That's done. Hope the new order makes sense.

So, if we're decoding data that needs to lookup those rows in TX1 or TX2
we both times need access to cmin and cmax, but neither transaction will
have created a multixact. That can only be an issue in transaction with
catalog modifications.

Oh, yuck. So that means you have to write an extra WAL record for
EVERY heap insert, update, or delete to a catalog table? OUCH.

Yes. We could integrate it into the main record without too many
problems, but it didn't seem like an important optimization and it would
have higher chances of slowing down wal_level < logical.

It'd probably not hurt to redo those benchmarks to make sure...

Yes, I think it would be good to characterize it more precisely than
"a bit", so people know what to expect.

A "bit" was below the 3% range for loops of adding columns.

So, any tests you'd like to see?
* loop around CREATE TABLE/DROP TABLE
* loop around ALTER TABLE ... ADD COLUMN
* loop around CREATE FUNCTION/DROP FUNCTION

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#68)
5 attachment(s)
Re: logical changeset generation v6.2

On 2013-10-14 15:51:14 +0200, Andres Freund wrote:

It'd probably not hurt to redo those benchmarks to make sure...

Yes, I think it would be good to characterize it more precisely than
"a bit", so people know what to expect.

A "bit" was below the 3% range for loops of adding columns.

So, any tests you'd like to see?
* loop around CREATE TABLE/DROP TABLE
* loop around ALTER TABLE ... ADD COLUMN
* loop around CREATE FUNCTION/DROP FUNCTION

So, see the attatched benchmark skript. I've always done using a disk
bound and a memory bound (using eatmydata, preventing fsyncs) run.

* unpatched run, wal_level = hot_standby, eatmydata
* unpatched run, wal_level = hot_standby

* patched run, wal_level = hot_standby, eatmydata
* patched run, wal_level = hot_standby

* patched run, wal_level = logical, eatmydata
* patched run, wal_level = logical

Based on those results, there's no difference above noise for
wal_level=hot_standby, with or without the patch. With wal_level=logical
there's a measurable increase in wal traffic (~12-17%), but no
performance decrease above noise.

From my POV that's ok, those are really crazy catalog workloads.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

results.txttext/plain; charset=us-asciiDownload
bench.shapplication/x-shDownload
bench-altertable.sqltext/plain; charset=us-asciiDownload
bench-createfunction.sqltext/plain; charset=us-asciiDownload
bench-createtable.sqltext/plain; charset=us-asciiDownload
#70Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#68)
Re: logical changeset generation v6.2

On Mon, Oct 14, 2013 at 9:51 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Well, I just think relying on specific symbol names in the .so file is
kind of unfortunate. It means that, for example, you can't have
multiple output plugins provided by a single .so. And in general I
think it's something that we've tried to minimize.

But that's not really different when you rely on _PG_init doing it's
thing, right?

Sure, that's true. But in general I think magic symbol names aren't a
particularly good design.

But there's only so much information available here. Why not just
have a format that logs it all?

Because we do not know what "all" is? Also, how would we handle
replication sets and such that all of the existing replication solutions
have generically?

I don't see how you can fail to know what "all" is. There's only a
certain set of facts available. I mean you could log irrelevant crap
like a random number that you just picked or the sum of all numeric
values in the column, but nobody's likely to want that. What people
are going to want is the operation performed (insert, update, or
delete), all the values in the new tuple, the key values from the old
tuple, the transaction ID, and maybe some meta-information about the
transaction (such as the commit timestamp). What I'd probably do is
emit the data in CSV format, with the first column of each line being
a single character indicating what sort of row this is: H means a
header row, defining the format of subsequent rows
(H,table_name,new_column1,...,new_columnj,old_key_column1,...,old_key_columnk;
a new header row is emitted only when the column list changes); I, U,
or D means an insert, update, or delete, with column 2 being the
transaction ID, column 3 being the table name, and the remaining
columns matching the last header row for emitted for that table, T
means meta-information about a transaction, whatever we have (e.g.
T,txn_id,commit_time). There's probably some further tweaking of that
that could be done, and I might be overlooking some salient details,
like maybe we want to indicate the column types as well as their
names, but the range of things that someone can want to do here is not
unlimited. The point, for me anyway, is that someone can write a
crappy Perl script to apply changes from a file like this in a day.
My contention is that there are a lot of people who will want to do
just that, for one reason or another. The plugin interface has
awesome power and flexibility, and really high-performance replication
solutions will really benefit from that. But regular people don't
want to write C code; they just want to write a crappy Perl script.
And I think we can facilitate that without too much work.

Oh, yuck. So that means you have to write an extra WAL record for
EVERY heap insert, update, or delete to a catalog table? OUCH.

Yes. We could integrate it into the main record without too many
problems, but it didn't seem like an important optimization and it would
have higher chances of slowing down wal_level < logical.

Hmm. I don't know whether that's an important optimization or not.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#69)
Re: logical changeset generation v6.2

On Mon, Oct 14, 2013 at 5:07 PM, Andres Freund <andres@2ndquadrant.com> wrote:

So, see the attatched benchmark skript. I've always done using a disk
bound and a memory bound (using eatmydata, preventing fsyncs) run.

* unpatched run, wal_level = hot_standby, eatmydata
* unpatched run, wal_level = hot_standby

* patched run, wal_level = hot_standby, eatmydata
* patched run, wal_level = hot_standby

* patched run, wal_level = logical, eatmydata
* patched run, wal_level = logical

Based on those results, there's no difference above noise for
wal_level=hot_standby, with or without the patch. With wal_level=logical
there's a measurable increase in wal traffic (~12-17%), but no
performance decrease above noise.

From my POV that's ok, those are really crazy catalog workloads.

Any increase in WAL traffic will translate into a performance hit once
the I/O channel becomes saturated, but I agree those numbers don't
sound terrible for that faily-brutal test case. Actually, I was more
concerned about the hit on non-catalog workloads. pgbench isn't a
good test because the key column is so narrow; but suppose we have a
table like (a text, b integer, c text) where (a, c) is the primary key
and those strings are typically pretty long - say just short enough
that we can still index the column. It'd be worth testing both
workloads where the primary key doesn't change (so the only overhead
is figuring out that we need not log it) and those where it does
(where we're double-logging most of the tuple). I assume the latter
has to produce a significant hit to WAL volume, and I don't think
there's much we can do about that; but the former had better be nearly
free.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#70)
Re: logical changeset generation v6.2

On 2013-10-15 08:42:20 -0400, Robert Haas wrote:

On Mon, Oct 14, 2013 at 9:51 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Well, I just think relying on specific symbol names in the .so file is
kind of unfortunate. It means that, for example, you can't have
multiple output plugins provided by a single .so. And in general I
think it's something that we've tried to minimize.

But that's not really different when you rely on _PG_init doing it's
thing, right?

Sure, that's true. But in general I think magic symbol names aren't a
particularly good design.

It allows you to use the shared libary both as a normal extension loaded
via shared_preload_library or adhoc and as an output plugin which seems
like a sensible goal.
We could have a single _PG_init_output_plugin() symbol that fills in
such a struct which would then not conflict with using the .so
independently. If you prefer that I'll change things around.

We can't do something like 'output_plugin_in_progress' before calling
_PG_init() because _PG_init() won't be called again if the shared object
is already loaded...

But there's only so much information available here. Why not just
have a format that logs it all?

Because we do not know what "all" is? Also, how would we handle
replication sets and such that all of the existing replication solutions
have generically?

I don't see how you can fail to know what "all" is. There's only a
certain set of facts available. I mean you could log irrelevant crap
like a random number that you just picked or the sum of all numeric
values in the column, but nobody's likely to want that. What people
are going to want is the operation performed (insert, update, or
delete), all the values in the new tuple, the key values from the old
tuple, the transaction ID, and maybe some meta-information about the
transaction (such as the commit timestamp).

Some will want all column names included because that makes replication
into different schemas/databases easier, others won't because it makes
replicating the data more complicated and expensive.
Lots will want the primary key as a separate set of columns even for
inserts, others not.
There's also datatypes of values and null representation.

What I'd probably do is
emit the data in CSV format, with the first column of each line being
a single character indicating what sort of row this is: H means a
header row, defining the format of subsequent rows
(H,table_name,new_column1,...,new_columnj,old_key_column1,...,old_key_columnk;
a new header row is emitted only when the column list changes); I, U,
or D means an insert, update, or delete, with column 2 being the
transaction ID, column 3 being the table name, and the remaining
columns matching the last header row for emitted for that table, T
means meta-information about a transaction, whatever we have (e.g.
T,txn_id,commit_time).

There's two issues I have with this:
a) CSV seems like a bad format for this. If a transaction inserts into
multiple tables the number of columns will constantly change. Many CSV
parsers don't deal with that all too gracefully. E.g. you can't even
load the data into another postgres database as an audit log.

If we go for CSV I think we should put the entire primary key as one
column (containing all the columns) and the entire row another.

We also don't have any nice facilities for actually writing CSV - so
we'll need to start extracting escaping code from COPY. In the end all
that will make the output plugin very hard to use as an example because
the code will get more complicated.

b) Emitting new row descriptors everytime the schema changes will
require keeping track of the schema. I think that won't be trivial. It
also makes consumption of the data more complicated in comparison to
including the description with every row.

Both are even more true once we extend the format to support streaming
of transactions while they are performed.

But regular people don't want to write C code; they just want to write
a crappy Perl script. And I think we can facilitate that without too
much work.

I think the generic output plugin should be a separate one from the
example one (which is the one included in the patchset).

Oh, yuck. So that means you have to write an extra WAL record for
EVERY heap insert, update, or delete to a catalog table? OUCH.

Yes. We could integrate it into the main record without too many
problems, but it didn't seem like an important optimization and it would
have higher chances of slowing down wal_level < logical.

Hmm. I don't know whether that's an important optimization or not.

Based on the benchmark I'd say no. If we discover we need to go there we
can do so later. I don't forsee this to be really problematic.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#71)
Re: logical changeset generation v6.2

On 2013-10-15 08:49:26 -0400, Robert Haas wrote:

On Mon, Oct 14, 2013 at 5:07 PM, Andres Freund <andres@2ndquadrant.com> wrote:

So, see the attatched benchmark skript. I've always done using a disk
bound and a memory bound (using eatmydata, preventing fsyncs) run.

* unpatched run, wal_level = hot_standby, eatmydata
* unpatched run, wal_level = hot_standby

* patched run, wal_level = hot_standby, eatmydata
* patched run, wal_level = hot_standby

* patched run, wal_level = logical, eatmydata
* patched run, wal_level = logical

Based on those results, there's no difference above noise for
wal_level=hot_standby, with or without the patch. With wal_level=logical
there's a measurable increase in wal traffic (~12-17%), but no
performance decrease above noise.

From my POV that's ok, those are really crazy catalog workloads.

Any increase in WAL traffic will translate into a performance hit once
the I/O channel becomes saturated, but I agree those numbers don't
sound terrible for that faily-brutal test case.

Well, the parallel workloads were fsync saturated although probably not
throughput, that's why I added them. But yes, it's not the same as a
throughput saturated IO channel.
Probably the worst case real-world workload is one that uses lots and
lots of ON COMMIT DROP temporary tables.

Actually, I was more concerned about the hit on non-catalog workloads. pgbench isn't a
good test because the key column is so narrow; but suppose we have a
table like (a text, b integer, c text) where (a, c) is the primary key
and those strings are typically pretty long - say just short enough
that we can still index the column. It'd be worth testing both
workloads where the primary key doesn't change (so the only overhead
is figuring out that we need not log it) and those where it does
(where we're double-logging most of the tuple). I assume the latter
has to produce a significant hit to WAL volume, and I don't think
there's much we can do about that; but the former had better be nearly
free.

Ah, ok. Then I misunderstood you.

Is there a specific overhead you are "afraid" of in the
pkey-doesn't-change scenario? The changed wal logging (buffer in a
separate rdata entry) or the check whether the primary key has changed?

The only way I have been able to measure differences in that scenario
was to load a table with a low fillfactor and wide tuples, checkpoint,
and then update lots of rows. On wal_level=logical that will result in
full-page-images and tuple data being logged which can be noticeable if
you have really large tuples, even if the pkey doesn't change.

We could optimize that by not actually logging the tuple data in that
case but just include the tid so we could extract things from the Bkp
block ourselves. But that will complicate the code and doesn't yet seem
warranted.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#72)
Re: logical changeset generation v6.2

On 2013-10-15 15:17:58 +0200, Andres Freund wrote:

If we go for CSV I think we should put the entire primary key as one
column (containing all the columns) and the entire row another.

What about columns like:
* action B|I|U|D|C

* xid
* timestamp

* tablename

* key name
* key column names
* key column types

* new key column values

* column names
* column types
* column values

* candidate_key_changed?
* old key column values

And have output plugin options
* include-column-types
* include-column-names
* include-primary-key

If something isn't included it's simply left out.

What still need to be determined is:
* how do we separate and escape multiple values in one CSV column
* how do we represent NULLs

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#72)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 9:17 AM, Andres Freund <andres@2ndquadrant.com> wrote:

It allows you to use the shared libary both as a normal extension loaded
via shared_preload_library or adhoc and as an output plugin which seems
like a sensible goal.
We could have a single _PG_init_output_plugin() symbol that fills in
such a struct which would then not conflict with using the .so
independently. If you prefer that I'll change things around.

I think part of the problem may be that you're using the library name
to identify the output plugin. I'm not excited about that design.
For functions, you give the function a name and that is a pointer to
where to actually find the function, which may be a 2-tuple
<library-name, function-name>, or perhaps just a 1-tuple
<builtin-function-name>, or maybe the whole text of a PL/pgsql
procedure that should be compiled.

Perhaps this ought to work similarly. Create a function in pg_proc
which returns the structure containing the function pointers. Then,
when that output plugin is selected, it'll automatically trigger
loading the correct shared library if that's needed; and the shared
library name may (but need not) match the output plugin name.

What I'd probably do is
emit the data in CSV format, with the first column of each line being
a single character indicating what sort of row this is: H means a
header row, defining the format of subsequent rows
(H,table_name,new_column1,...,new_columnj,old_key_column1,...,old_key_columnk;
a new header row is emitted only when the column list changes); I, U,
or D means an insert, update, or delete, with column 2 being the
transaction ID, column 3 being the table name, and the remaining
columns matching the last header row for emitted for that table, T
means meta-information about a transaction, whatever we have (e.g.
T,txn_id,commit_time).

There's two issues I have with this:
a) CSV seems like a bad format for this. If a transaction inserts into
multiple tables the number of columns will constantly change. Many CSV
parsers don't deal with that all too gracefully. E.g. you can't even
load the data into another postgres database as an audit log.

We can pick some other separator. I don't think ragged CSV is a big
problem; I'm actually more worried about having an easy way to handle
embedded commas and newlines and so on. But I'd be fine with
tab-separated data or something too, if you think that's better. What
I want is something that someone can parse with a script that can be
written in a reasonable amount of time in their favorite scripting
language. I predict that if we provide something like this we'll
vastly expand the number of users who can make use of this new
functionality.

User: So, what's new in PostgreSQL 9.4?
Hacker: Well, now we have logical replication!
User: Why is that cool?
Hacker: Well, streaming replication is awesome for HA, but it has
significant limitations. And trigger-based systems are very mature,
but the overhead is high and their lack of core integration makes them
hard to use. With this technology, you can build systems that will
replicate individual tables or even parts of tables, multi-master
systems, and lots of other cool stuff.
User: Wow, that sounds great. How do I use it?
Hacker: Well, first you write an output plugin in C using a special API.
User: Hey, do you know whether the MongoDB guys came to this conference?

Let's try that again.

User: Wow, that sounds great. How do I use it?
Hacker: Well, currently, the output gets dumped as a series of text
files that are designed to be parsed using a scripting language. We
have sample parsers written in Perl and Python that you can use as-is
or hack up to meet your needs.

Now, some users are still going to head for the hills. But at least
from where I sit it sounds a hell of a lot better than the first
answer. We're not going to solve all of the tooling problems around
this technology in one release, for sure. But as far as 95% of our
users are concerned, a C API might as well not exist at all. People
WILL try to machine parse the output of whatever demo plugins we
provide; so I think we should try hard to provide at least one such
plugin that is designed to make that as easy as possible.

If we go for CSV I think we should put the entire primary key as one
column (containing all the columns) and the entire row another.

We also don't have any nice facilities for actually writing CSV - so
we'll need to start extracting escaping code from COPY. In the end all
that will make the output plugin very hard to use as an example because
the code will get more complicated.

b) Emitting new row descriptors everytime the schema changes will
require keeping track of the schema. I think that won't be trivial. It
also makes consumption of the data more complicated in comparison to
including the description with every row.

Both are even more true once we extend the format to support streaming
of transactions while they are performed.

All fair points, but IMHO this is exactly why we need to provide a
well-written output plugin, not leave it to users to solve these
problems.

But regular people don't want to write C code; they just want to write
a crappy Perl script. And I think we can facilitate that without too
much work.

I think the generic output plugin should be a separate one from the
example one (which is the one included in the patchset).

That's OK with me.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Hannu Krosing
hannu@krosing.net
In reply to: Robert Haas (#70)
Re: logical changeset generation v6.2

On 10/15/2013 01:42 PM, Robert Haas wrote:

On Mon, Oct 14, 2013 at 9:51 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Well, I just think relying on specific symbol names in the .so file is
kind of unfortunate. It means that, for example, you can't have
multiple output plugins provided by a single .so. And in general I
think it's something that we've tried to minimize.

But that's not really different when you rely on _PG_init doing it's
thing, right?

Sure, that's true. But in general I think magic symbol names aren't a
particularly good design.

But there's only so much information available here. Why not just
have a format that logs it all?

Because we do not know what "all" is? Also, how would we handle
replication sets and such that all of the existing replication solutions
have generically?

I don't see how you can fail to know what "all" is.

We instinctively know what "all" is - as in the famous case of buddhist
ordering a
hamburger - "Make me All wit Everything" :) - but the requirements of
different replications systems vary wildly.

...
What people
are going to want is the operation performed (insert, update, or
delete), all the values in the new tuple, the key values from the old
tuple,

For multi-master / conflict resolution you may also want all old
values to make sure that they have not changed on target.

the difference in WAL volume can be really significant, especially
in the case of DELETE, where there are no new columns.

for some forms of conflict resolution we may even want to know
the database user who initiated the operation. and possibly even
some session variables like "very_important=yes".

... The point, for me anyway, is that someone can write a
crappy Perl script to apply changes from a file like this in a day.
My contention is that there are a lot of people who will want to do
just that, for one reason or another. The plugin interface has
awesome power and flexibility, and really high-performance replication
solutions will really benefit from that. But regular people don't
want to write C code; they just want to write a crappy Perl script.
And I think we can facilitate that without too much work.

just provide a to-csv or to-json plugin and the crappy perl guys
are happy.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Hannu Krosing
hannu@krosing.net
In reply to: Andres Freund (#74)
Re: logical changeset generation v6.2

On 10/15/2013 02:47 PM, Andres Freund wrote:

On 2013-10-15 15:17:58 +0200, Andres Freund wrote:

If we go for CSV I think we should put the entire primary key as one
column (containing all the columns) and the entire row another.

just use JSON :)

What about columns like:
* action B|I|U|D|C

* xid
* timestamp

* tablename

* key name
* key column names
* key column types

* new key column values

* column names
* column types
* column values

* candidate_key_changed?
* old key column values

And have output plugin options
* include-column-types
* include-column-names
* include-primary-key

If something isn't included it's simply left out.

What still need to be determined is:
* how do we separate and escape multiple values in one CSV column
* how do we represent NULLs

or borrow whatever possible from pg_dump as they have
needed to solve most of the same problems already and
consistency is good in general

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#74)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 9:47 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-15 15:17:58 +0200, Andres Freund wrote:

If we go for CSV I think we should put the entire primary key as one
column (containing all the columns) and the entire row another.

What about columns like:
* action B|I|U|D|C

BEGIN and COMMIT?

* xid
* timestamp

* tablename

* key name
* key column names
* key column types

* new key column values

* column names
* column types
* column values

* candidate_key_changed?
* old key column values

Repeating the column names for every row strikes me as a nonstarter.
If the plugin interface isn't rich enough to provide a convenient way
to avoid that, then it needs to be fixed so that it is, because it
will be a common requirement. Sure, some people may want JSON or XML
output that reiterates the labels every time, but for a lot of people
that's going to greatly increase the size of the output and be
undesirable for that reason.

What still need to be determined is:
* how do we separate and escape multiple values in one CSV column
* how do we represent NULLs

I consider the escaping a key design decision. Ideally, it should be
something that's easy to reverse from a scripting language; ideally
also, it should be something similar to how we handle COPY. These
goals may be in conflict; we'll have to pick something.

I'm not sure that having multiple values in one column is a good plan,
because now you need multiple levels of parsing to unpack the row.
I'd rather just have a flat column list with a key somewhere
explaining how to interpret the data. But I'm prepared to give in on
that point so long as we can demonstrate that the format can be easily
parsed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#79Robert Haas
robertmhaas@gmail.com
In reply to: Hannu Krosing (#76)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 10:09 AM, Hannu Krosing <hannu@krosing.net> wrote:

I don't see how you can fail to know what "all" is.

We instinctively know what "all" is - as in the famous case of buddhist
ordering a
hamburger - "Make me All wit Everything" :) - but the requirements of
different replications systems vary wildly.

That's true to some degree, but let's not exaggerate the degree to
which it is true.

For multi-master / conflict resolution you may also want all old
values to make sure that they have not changed on target.

The patch as proposed doesn't make that information available. If you
want that to be an option, now would be the right time to argue for
it.

for some forms of conflict resolution we may even want to know
the database user who initiated the operation. and possibly even
some session variables like "very_important=yes".

Well, if you have requirements like logging very_important=yes, then
you're definitely into the territory where you need your own output
plugin. I have no problem telling people who want that sort of thing
that they've got to go write C code. What I'm trying to do, as Larry
Wall once said, is to make simple things simple and hard things
possible. The output plugin interface accomplishes the latter, but,
by itself, not the former.

And I think we can facilitate that without too much work.

just provide a to-csv or to-json plugin and the crappy perl guys
are happy.

Yep, that's exactly what I'm advocating for.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#79)
Re: logical changeset generation v6.2

On 2013-10-15 10:20:55 -0400, Robert Haas wrote:

For multi-master / conflict resolution you may also want all old
values to make sure that they have not changed on target.

The patch as proposed doesn't make that information available. If you
want that to be an option, now would be the right time to argue for
it.

I don't think you necessarily want it for most MM solutions, but I agree
it will be useful for some scenarios.

I think the ReorderBufferChange struct needs a better way to distinguish
between old-key and old-tuple now, but I'd rather implement the
facililty for logging the full old tuple in a separate patch. The
patchset is big enough as is, lets not tack on more features.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#81Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#75)
Re: logical changeset generation v6.2

On 2013-10-15 10:09:05 -0400, Robert Haas wrote:

On Tue, Oct 15, 2013 at 9:17 AM, Andres Freund <andres@2ndquadrant.com> wrote:

It allows you to use the shared libary both as a normal extension loaded
via shared_preload_library or adhoc and as an output plugin which seems
like a sensible goal.
We could have a single _PG_init_output_plugin() symbol that fills in
such a struct which would then not conflict with using the .so
independently. If you prefer that I'll change things around.

I think part of the problem may be that you're using the library name
to identify the output plugin. I'm not excited about that design.
For functions, you give the function a name and that is a pointer to
where to actually find the function, which may be a 2-tuple
<library-name, function-name>, or perhaps just a 1-tuple
<builtin-function-name>, or maybe the whole text of a PL/pgsql
procedure that should be compiled.

That means you allow trivial remote code execution since you could try
to load system() or something else that's available in every shared
object. Now you can argue that that's OK since we have special checks
for replication connections, but I'd rather not go there.

Perhaps this ought to work similarly. Create a function in pg_proc
which returns the structure containing the function pointers. Then,
when that output plugin is selected, it'll automatically trigger
loading the correct shared library if that's needed; and the shared
library name may (but need not) match the output plugin name.

I'd like to avoid relying on inserting stuff into pg_proc because that
makes it harder to extract WAL from a HS standby. Requiring to configure
that on the primary to extract data on the standby seems confusing to
me.

But perhaps that's the correct solution :/

Now, some users are still going to head for the hills. But at least
from where I sit it sounds a hell of a lot better than the first
answer. We're not going to solve all of the tooling problems around
this technology in one release, for sure. But as far as 95% of our
users are concerned, a C API might as well not exist at all. People
WILL try to machine parse the output of whatever demo plugins we
provide; so I think we should try hard to provide at least one such
plugin that is designed to make that as easy as possible.

Well, just providing the C API + an example in a first step didn't work
out too badly for FDWs. I am pretty sure that once released there will
soon be extensions for it on PGXN or whatever for special usecases.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#81)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 10:27 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I think part of the problem may be that you're using the library name
to identify the output plugin. I'm not excited about that design.
For functions, you give the function a name and that is a pointer to
where to actually find the function, which may be a 2-tuple
<library-name, function-name>, or perhaps just a 1-tuple
<builtin-function-name>, or maybe the whole text of a PL/pgsql
procedure that should be compiled.

That means you allow trivial remote code execution since you could try
to load system() or something else that's available in every shared
object. Now you can argue that that's OK since we have special checks
for replication connections, but I'd rather not go there.

Well, obviously you can't let somebody load any library they want.
But that's pretty much true anyway; LOAD had better be confined to
superusers unless there is something (like a pg_proc entry) that
provides "prior authorization" for that specific load.

Perhaps this ought to work similarly. Create a function in pg_proc
which returns the structure containing the function pointers. Then,
when that output plugin is selected, it'll automatically trigger
loading the correct shared library if that's needed; and the shared
library name may (but need not) match the output plugin name.

I'd like to avoid relying on inserting stuff into pg_proc because that
makes it harder to extract WAL from a HS standby. Requiring to configure
that on the primary to extract data on the standby seems confusing to
me.

But perhaps that's the correct solution :/

That's a reasonable concern. I don't have another idea at the moment,
unless we want to allow replication connections to issue LOAD
commands. Then you can LOAD the library, so that the plug-in is
registered under the well-known name you expect it to have, and then
use that name to start replication.

Now, some users are still going to head for the hills. But at least
from where I sit it sounds a hell of a lot better than the first
answer. We're not going to solve all of the tooling problems around
this technology in one release, for sure. But as far as 95% of our
users are concerned, a C API might as well not exist at all. People
WILL try to machine parse the output of whatever demo plugins we
provide; so I think we should try hard to provide at least one such
plugin that is designed to make that as easy as possible.

Well, just providing the C API + an example in a first step didn't work
out too badly for FDWs. I am pretty sure that once released there will
soon be extensions for it on PGXN or whatever for special usecases.

I suspect so, too. But I also think that if that's the only thing
available in the first release, a lot of users will get a poor initial
impression.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#78)
Re: logical changeset generation v6.2

On 2013-10-15 10:15:14 -0400, Robert Haas wrote:

On Tue, Oct 15, 2013 at 9:47 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-15 15:17:58 +0200, Andres Freund wrote:

If we go for CSV I think we should put the entire primary key as one
column (containing all the columns) and the entire row another.

What about columns like:
* action B|I|U|D|C

BEGIN and COMMIT?

That's B and C, yes. You'd rather not have them? When would you replay
the commit without an explicit message telling you to?

Repeating the column names for every row strikes me as a nonstarter.
[...]
Sure, some people may want JSON or XML
output that reiterates the labels every time, but for a lot of people
that's going to greatly increase the size of the output and be
undesirable for that reason.

But I argue that most simpler users - which are exactly the ones a
generic output plugin is aimed at - will want all column names since it
makes replay far easier.

If the plugin interface isn't rich enough to provide a convenient way
to avoid that, then it needs to be fixed so that it is, because it
will be a common requirement.

Oh, it surely is possibly to avoid repeating it. The output plugin
interface simply gives you a relcache entry, that contains everything
necessary.
The output plugin would need to keep track of whether it has output data
for a specific relation and it would need to check whether the table
definition has changed, but I don't see how we could avoid that?

What still need to be determined is:
* how do we separate and escape multiple values in one CSV column
* how do we represent NULLs

I consider the escaping a key design decision. Ideally, it should be
something that's easy to reverse from a scripting language; ideally
also, it should be something similar to how we handle COPY. These
goals may be in conflict; we'll have to pick something.

Note that parsing COPYs is a major PITA from most languages...

Perhaps we should make the default output json instead? With every
action terminated by a nullbyte?
That's probably easier to parse from various scripting languages than
anything else.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#82)
Re: logical changeset generation v6.2

On 2013-10-15 10:34:53 -0400, Robert Haas wrote:

On Tue, Oct 15, 2013 at 10:27 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I think part of the problem may be that you're using the library name
to identify the output plugin. I'm not excited about that design.
For functions, you give the function a name and that is a pointer to
where to actually find the function, which may be a 2-tuple
<library-name, function-name>, or perhaps just a 1-tuple
<builtin-function-name>, or maybe the whole text of a PL/pgsql
procedure that should be compiled.

That means you allow trivial remote code execution since you could try
to load system() or something else that's available in every shared
object. Now you can argue that that's OK since we have special checks
for replication connections, but I'd rather not go there.

Well, obviously you can't let somebody load any library they want.
But that's pretty much true anyway; LOAD had better be confined to
superusers unless there is something (like a pg_proc entry) that
provides "prior authorization" for that specific load.

Currently you can create users that have permissions for replication but
which are not superusers. I think we should strive to providing that
capability for changeset extraction as well.

Perhaps this ought to work similarly. Create a function in pg_proc
which returns the structure containing the function pointers. Then,
when that output plugin is selected, it'll automatically trigger
loading the correct shared library if that's needed; and the shared
library name may (but need not) match the output plugin name.

I'd like to avoid relying on inserting stuff into pg_proc because that
makes it harder to extract WAL from a HS standby. Requiring to configure
that on the primary to extract data on the standby seems confusing to
me.

But perhaps that's the correct solution :/

That's a reasonable concern. I don't have another idea at the moment,
unless we want to allow replication connections to issue LOAD
commands. Then you can LOAD the library, so that the plug-in is
registered under the well-known name you expect it to have, and then
use that name to start replication.

But what's the advantage of that over the current situation or one where
PG_load_output_plugin() is called? The current and related
implementations allow you to only load libraries in some designated
postgres directories and it doesn't allow you to call any arbitrary
functions in there.

Would you be content with a symbol "PG_load_output_plugin" being called
that fills out the actual callbacks?

Now, some users are still going to head for the hills. But at least
from where I sit it sounds a hell of a lot better than the first
answer. We're not going to solve all of the tooling problems around
this technology in one release, for sure. But as far as 95% of our
users are concerned, a C API might as well not exist at all. People
WILL try to machine parse the output of whatever demo plugins we
provide; so I think we should try hard to provide at least one such
plugin that is designed to make that as easy as possible.

Well, just providing the C API + an example in a first step didn't work
out too badly for FDWs. I am pretty sure that once released there will
soon be extensions for it on PGXN or whatever for special usecases.

I suspect so, too. But I also think that if that's the only thing
available in the first release, a lot of users will get a poor initial
impression.

I think lots of people will expect a builtin logical replication
solution :/. Which seems a tad unlikely to arrive in 9.4.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#83)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 10:48 AM, Andres Freund <andres@2ndquadrant.com> wrote:

What about columns like:
* action B|I|U|D|C

BEGIN and COMMIT?

That's B and C, yes. You'd rather not have them? When would you replay
the commit without an explicit message telling you to?

No, BEGIN and COMMIT sounds good, actually. Just wanted to make sure
I understood.

Repeating the column names for every row strikes me as a nonstarter.
[...]
Sure, some people may want JSON or XML
output that reiterates the labels every time, but for a lot of people
that's going to greatly increase the size of the output and be
undesirable for that reason.

But I argue that most simpler users - which are exactly the ones a
generic output plugin is aimed at - will want all column names since it
makes replay far easier.

Meh, maybe.

If the plugin interface isn't rich enough to provide a convenient way
to avoid that, then it needs to be fixed so that it is, because it
will be a common requirement.

Oh, it surely is possibly to avoid repeating it. The output plugin
interface simply gives you a relcache entry, that contains everything
necessary.
The output plugin would need to keep track of whether it has output data
for a specific relation and it would need to check whether the table
definition has changed, but I don't see how we could avoid that?

Well, it might be nice if there were a callback for, hey, schema has
changed! Seems like a lot of plugins will want to know that for one
reason or another, and rechecking for every tuple sounds expensive.

What still need to be determined is:
* how do we separate and escape multiple values in one CSV column
* how do we represent NULLs

I consider the escaping a key design decision. Ideally, it should be
something that's easy to reverse from a scripting language; ideally
also, it should be something similar to how we handle COPY. These
goals may be in conflict; we'll have to pick something.

Note that parsing COPYs is a major PITA from most languages...

Perhaps we should make the default output json instead? With every
action terminated by a nullbyte?
That's probably easier to parse from various scripting languages than
anything else.

I could go for that. It's not quite as compact as I might hope, but
JSON does seem to make people awfully happy.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#84)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 10:56 AM, Andres Freund <andres@2ndquadrant.com> wrote:

That means you allow trivial remote code execution since you could try
to load system() or something else that's available in every shared
object. Now you can argue that that's OK since we have special checks
for replication connections, but I'd rather not go there.

Well, obviously you can't let somebody load any library they want.
But that's pretty much true anyway; LOAD had better be confined to
superusers unless there is something (like a pg_proc entry) that
provides "prior authorization" for that specific load.

Currently you can create users that have permissions for replication but
which are not superusers. I think we should strive to providing that
capability for changeset extraction as well.

I agree.

Perhaps this ought to work similarly. Create a function in pg_proc
which returns the structure containing the function pointers. Then,
when that output plugin is selected, it'll automatically trigger
loading the correct shared library if that's needed; and the shared
library name may (but need not) match the output plugin name.

I'd like to avoid relying on inserting stuff into pg_proc because that
makes it harder to extract WAL from a HS standby. Requiring to configure
that on the primary to extract data on the standby seems confusing to
me.

But perhaps that's the correct solution :/

That's a reasonable concern. I don't have another idea at the moment,
unless we want to allow replication connections to issue LOAD
commands. Then you can LOAD the library, so that the plug-in is
registered under the well-known name you expect it to have, and then
use that name to start replication.

But what's the advantage of that over the current situation or one where
PG_load_output_plugin() is called? The current and related
implementations allow you to only load libraries in some designated
postgres directories and it doesn't allow you to call any arbitrary
functions in there.

Well, I've already said why I don't like conflating the library name
and the plugin name. It rules out core plugins and libraries that
provide multiple plugins. I don't have anything to add to that.

Would you be content with a symbol "PG_load_output_plugin" being called
that fills out the actual callbacks?

Well, it doesn't fix the muddling of library names with output plugin
names, but I suppose I'd find it a modest improvement.

Well, just providing the C API + an example in a first step didn't work
out too badly for FDWs. I am pretty sure that once released there will
soon be extensions for it on PGXN or whatever for special usecases.

I suspect so, too. But I also think that if that's the only thing
available in the first release, a lot of users will get a poor initial
impression.

I think lots of people will expect a builtin logical replication
solution :/. Which seems a tad unlikely to arrive in 9.4.

Yep.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#87ktm@rice.edu
ktm@rice.edu
In reply to: Robert Haas (#85)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 11:02:39AM -0400, Robert Haas wrote:

goals may be in conflict; we'll have to pick something.

Note that parsing COPYs is a major PITA from most languages...

Perhaps we should make the default output json instead? With every
action terminated by a nullbyte?
That's probably easier to parse from various scripting languages than
anything else.

I could go for that. It's not quite as compact as I might hope, but
JSON does seem to make people awfully happy.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Feeding such a JSON stream into a compression algorithm like lz4 or
snappy should result in a pretty compact stream. The latest lz4 updates
also have ability to use a pre-existing dictionary which would really
help remove the redundant pieces.

Regards,
Ken

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#88Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#85)
Re: logical changeset generation v6.2

On 2013-10-15 11:02:39 -0400, Robert Haas wrote:

If the plugin interface isn't rich enough to provide a convenient way
to avoid that, then it needs to be fixed so that it is, because it
will be a common requirement.

Oh, it surely is possibly to avoid repeating it. The output plugin
interface simply gives you a relcache entry, that contains everything
necessary.
The output plugin would need to keep track of whether it has output data
for a specific relation and it would need to check whether the table
definition has changed, but I don't see how we could avoid that?

Well, it might be nice if there were a callback for, hey, schema has
changed! Seems like a lot of plugins will want to know that for one
reason or another, and rechecking for every tuple sounds expensive.

I don't really see how we could provide that in any useful manner. We
could provide a callback that is called whenever another transaction has
changed the schema, but there's nothing easily to be done about schema
changes by the replayed transaction itself. And those are the only ones
where meaningful schema changes can happen since the locks the source
transaction has held will prevent most other schema changes.

As much as I hate such code, I guess checking (and possibly storing) the
ctid||xmin of the pg_class row is the easiest thing we could do :(.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#89Josh Berkus
josh@agliodbs.com
In reply to: Andres Freund (#63)
Re: logical changeset generation v6.2

On 10/15/2013 07:56 AM, Andres Freund wrote:

Well, just providing the C API + an example in a first step didn't work

out too badly for FDWs. I am pretty sure that once released there will
soon be extensions for it on PGXN or whatever for special usecases.

I suspect so, too. But I also think that if that's the only thing
available in the first release, a lot of users will get a poor initial
impression.

I think lots of people will expect a builtin logical replication
solution :/. Which seems a tad unlikely to arrive in 9.4.

Well, last I checked the Slony team is hard at work on building
something which will be based on logical changesets. So there will
likely be at least one tool available shortly after 9.4 is released.

A good and flexible API is, IMHO, more important than having any
finished solution. The whole reason why logical replication was outside
core PG for so long is that replication systems have differing and
mutually incompatible goals. A good API can support all of those goals;
a user-level tool, no matter how good, can't.

And, frankly, once the API is built, how hard will it be to write a
script which does the simplest replication approach (replay all
statements on slave)?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#90David Fetter
david@fetter.org
In reply to: Robert Haas (#75)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 10:09:05AM -0400, Robert Haas wrote:

On Tue, Oct 15, 2013 at 9:17 AM, Andres Freund <andres@2ndquadrant.com> wrote:
User: So, what's new in PostgreSQL 9.4?
Hacker: Well, now we have logical replication!
User: Why is that cool?
Hacker: Well, streaming replication is awesome for HA, but it has
significant limitations. And trigger-based systems are very mature,
but the overhead is high and their lack of core integration makes them
hard to use. With this technology, you can build systems that will
replicate individual tables or even parts of tables, multi-master
systems, and lots of other cool stuff.
User: Wow, that sounds great. How do I use it?
Hacker: Well, first you write an output plugin in C using a special API.
User: Hey, do you know whether the MongoDB guys came to this conference?

Let's try that again.

User: Wow, that sounds great. How do I use it?
Hacker: Well, currently, the output gets dumped as a series of text
files that are designed to be parsed using a scripting language. We
have sample parsers written in Perl and Python that you can use as-is
or hack up to meet your needs.

My version:

Hacker: the output gets dumped as a series of JSON files. We have
docs for this rev of the format and examples of consumers in Perl and
Python you can use as-is or hack up to meet your needs.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#91Peter Geoghegan
pg@heroku.com
In reply to: Robert Haas (#75)
Re: logical changeset generation v6.2

On Tue, Oct 15, 2013 at 7:09 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Let's try that again.

User: Wow, that sounds great. How do I use it?
Hacker: Well, currently, the output gets dumped as a series of text
files that are designed to be parsed using a scripting language. We
have sample parsers written in Perl and Python that you can use as-is
or hack up to meet your needs.

Have you heard of multicorn? Plugin authors can write a wrapper that
spits out JSON or whatever other thing they like, which can be
consumed by non C-hackers.

Now, some users are still going to head for the hills. But at least
from where I sit it sounds a hell of a lot better than the first
answer. We're not going to solve all of the tooling problems around
this technology in one release, for sure. But as far as 95% of our
users are concerned, a C API might as well not exist at all. People
WILL try to machine parse the output of whatever demo plugins we
provide; so I think we should try hard to provide at least one such
plugin that is designed to make that as easy as possible.

I agree that this is important, but I wouldn't like to weigh it too heavily.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#92Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#66)
Re: logical changeset generation v6.4

On Mon, Oct 14, 2013 at 9:12 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached you can find version 6.4 of the patchset:

So I'm still unhappy with the arbitrary logic in what's now patch 1
for choosing the candidate key. On another thread, someone mentioned
that they might want the entire old tuple, and that got me thinking:
there's no particular reason why the user has to want exactly the
columns that exist in some unique, immediate, non-partial index (what
a name). So I have two proposals:

1. Instead of allowing the user to choose the index to be used, or
picking it for them, how about if we let them choose the old-tuple
columns they want logged? This could be a per-column option. If the
primary key can be assumed known and unchanging, then the answer might
be that the user wants *no* old-tuple columns logged. Contrariwise
someone might want everything logged, or anything in the middle.

2. If that seems too complicated, how about just logging the whole old
tuple for version 1?

I'm basically fine with the rest of what's in the first two patches,
but we need to sort out some kind of consensus on this issue.

Thanks,

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#93Merlin Moncure
mmoncure@gmail.com
In reply to: Robert Haas (#92)
Re: logical changeset generation v6.4

On Fri, Oct 18, 2013 at 7:11 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 14, 2013 at 9:12 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached you can find version 6.4 of the patchset:

So I'm still unhappy with the arbitrary logic in what's now patch 1
for choosing the candidate key. On another thread, someone mentioned
that they might want the entire old tuple, and that got me thinking:
there's no particular reason why the user has to want exactly the
columns that exist in some unique, immediate, non-partial index (what
a name). So I have two proposals:

Aside: what's an immediate index? Is this speaking to the constraint?
(immediate vs deferred?)

merlin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#94Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#67)
Re: logical changeset generation v6.2

On 2013-10-14 09:36:03 -0400, Robert Haas wrote:

I thought and implemented that in the beginning. Unfortunately it's not
enough :(. That's probably the issue that took me longest to understand
in this patchseries...

Combocids can only fix the case where a transaction actually has create
a combocid:

1) TX1: INSERT id = 1 at 0/1: (xmin = 1, xmax=Invalid, cmin = 55, cmax = Invalid)
2) TX2: DELETE id = 1 at 0/1: (xmin = 1, xmax=2, cmin = Invalid, cmax = 1)

So, if we're decoding data that needs to lookup those rows in TX1 or TX2
we both times need access to cmin and cmax, but neither transaction will
have created a multixact. That can only be an issue in transaction with
catalog modifications.

Oh, yuck. So that means you have to write an extra WAL record for
EVERY heap insert, update, or delete to a catalog table? OUCH.

So. As it turns out that solution isn't sufficient in the face of VACUUM
FULL and mixed DML/DDL transaction that have not yet been decoded.

To reiterate, as published it works like:
For every modification of catalog tuple (insert, multi_insert, update,
delete) that has influence over visibility issue a record that contains:
* filenode
* ctid
* (cmin, cmax)

When doing a visibility check on a catalog row during decoding of mixed
DML/DDL transaction lookup (cmin, cmax) for that row since we don't
store both for the tuple.

That mostly works great.

The problematic scenario is decoding a transaction that has done mixed
DML/DDL *after* a VACUUM FULL/CLUSTER has been performed. The VACUUM
FULL obviously changes the filenode and the ctid of a tuple, so we
cannot successfully do a lookup based on what we logged before.

I know of the following solutions:
1) Don't allow VACUUM FULL on catalog tables if wal_level = logical.
2) Make VACUUM FULL prevent DDL and then wait till all changestreams
have decoded up to the current point.
3) don't delete the old relfilenode for VACUUM/CLUSTERs of system tables
if there are life decoding slots around, instead delegate that
responsibility to the slot management.
4) Store both (cmin, cmax) for catalog tuples.

I bascially think only 1) and 4) are realistic. And 1) sucks.

I've developed a prototype for 4) and except currently being incredibly
ugly, it seems to be the most promising approach by far. My trick to
store both cmin and cmax is to store cmax in t_hoff managed space when
wal_level = logical.
That even works when changing wal_level from < logical to logical
because only ever need to store both cmin and cmax for transactions that
have decodeable content - which they cannot yet have before wal_level =
logical.

This requires some not so nice things:
* A way to declare we're storing both. I've currently chosen
HEAP_MOVED_OFF | HEAP_MOVED_IN. That sucks.
* A way for heap_form_tuple to know it should add the necessary space to
t_hoff. I've added TupleDesc->tdhaswidecid for it.
* Fiddling with existing checks for HEAP_MOVED{,OFF,IN} to check for
both set at the same time.
* Changing the WAL logging to (optionally?) transport the current
CommandId instead of always resetting it InvalidCommandId.

The benefits are:
* Working VACUUM FULL
* Much simpler tqual.c logic, everything is stored in the row itself. No
hash or something like that built.
* No more need to log (relfilenode, cmin, cmax) separately from heap
changes itself anymore.

In the end, the costs are that individual catalog rows are 4 bytes
bigger iff wal_level = logical. That seems acceptable.

Some questions remain:
* Better idea for a flag than HEAP_MOVED_OFF | HEAP_MOVED_IN
* Should we just unconditionally log the current CommandId or make it
conditional. We have plenty of flag space to signal whether it's
present, but it's just 4 bytes.

Comments?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#95Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#92)
Re: logical changeset generation v6.4

On 2013-10-18 08:11:29 -0400, Robert Haas wrote:

On Mon, Oct 14, 2013 at 9:12 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached you can find version 6.4 of the patchset:

So I'm still unhappy with the arbitrary logic in what's now patch 1
for choosing the candidate key. On another thread, someone mentioned
that they might want the entire old tuple, and that got me thinking:
there's no particular reason why the user has to want exactly the
columns that exist in some unique, immediate, non-partial index (what
a name). So I have two proposals:

1. Instead of allowing the user to choose the index to be used, or
picking it for them, how about if we let them choose the old-tuple
columns they want logged? This could be a per-column option. If the
primary key can be assumed known and unchanging, then the answer might
be that the user wants *no* old-tuple columns logged. Contrariwise
someone might want everything logged, or anything in the middle.

I definitely can see the usecase for logging anything or nothing,
arbitrary column select seems to be too complicated for now.

2. If that seems too complicated, how about just logging the whole old
tuple for version 1?

I think that'd make the patch much less useful because it bloats WAL
unnecessarily for the primary user (replication) of it. I'd rather go
for primary keys only if that proves to be the contentious point.

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)
* primary key
* candidate key with the smallest oid

Including the candidate key will help people using changeset extration
for auditing that do not have primary key. That really isn't an
infrequent usecase.

I've chosen REPLICA IDENTITY; NOTHIN; FULL; because those are all
existing keywords, and afaics shouldn't generate any conflicts. On a
green field we probably name them differently, but ...

Comments?

Greetings,

Andres Freund

PS: candidate key implies a key which is: immediate (aka not deferred),
unique, non-partial and only contains NOT NULL columns.

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#96Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#94)
Re: logical changeset generation v6.2

On Fri, Oct 18, 2013 at 2:26 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I know of the following solutions:
1) Don't allow VACUUM FULL on catalog tables if wal_level = logical.
2) Make VACUUM FULL prevent DDL and then wait till all changestreams
have decoded up to the current point.
3) don't delete the old relfilenode for VACUUM/CLUSTERs of system tables
if there are life decoding slots around, instead delegate that
responsibility to the slot management.
4) Store both (cmin, cmax) for catalog tuples.

I bascially think only 1) and 4) are realistic. And 1) sucks.

I've developed a prototype for 4) and except currently being incredibly
ugly, it seems to be the most promising approach by far. My trick to
store both cmin and cmax is to store cmax in t_hoff managed space when
wal_level = logical.

In my opinion, (4) is too ugly to consider. I think that if we start
playing games like this, we're opening up the doors to lots of subtle
bugs and future architectural pain that will be with us for many, many
years to come. I believe we will bitterly regret any foray into this
area.

It has long seemed to me to be a shame that we don't have some system
for allowing old relfilenodes to stick around until they are no longer
in use. If we had that, we might be able to allow utilities like
CLUSTER or VACUUM FULL to permit concurrent read access to the table.
I realize that what people really want is to let those things run
while allowing concurrent *write* access to the table, but a bird in
the hand is worth two in the bush. What we're really talking about
here is applying MVCC to filesystem actions: instead of removing the
old relfilenode(s) immediately, we do it when they're no longer
referenced by anyone, just as we don't remove old tuples immediately,
but rather when they are no longer referenced by anyone. The details
are tricky, though: we can allow write access to the *new* heap just
as soon as the rewrite is finished, but anyone who is still looking at
the *old* heap can't ever upgrade their AccessShareLock to anything
higher, or hilarity will ensue. Also, if they lock some *other*
relation and AcceptInvalidationMessages(), their relcache entry for
the rewritten relation will get rebuilt, and that's bound to work out
poorly. The net-net here is that I think (3) is an attractive
solution, but I don't know that we can make it work in a reasonable
amount of time.

I don't think I understand exactly what you have in mind for (2); can
you elaborate? I have always thought that having a
WaitForDecodingToCatchUp() primitive was a good way of handling
changes that were otherwise too difficult to track our way through. I
am not sure you're doing that at all right now, which in some sense I
guess is fine, but I haven't really understood your aversion to this
solution. There are some locking issues to be worked out here, but
the problems don't seem altogether intractable.

(1) is basically deciding not to fix the problem. I don't think
that's acceptable.

I don't have another idea right at the moment.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#97Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#95)
Re: logical changeset generation v6.4

On Fri, Oct 18, 2013 at 2:50 PM, Andres Freund <andres@2ndquadrant.com> wrote:

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)
* primary key
* candidate key with the smallest oid

Including the candidate key will help people using changeset extration
for auditing that do not have primary key. That really isn't an
infrequent usecase.

I've chosen REPLICA IDENTITY; NOTHIN; FULL; because those are all
existing keywords, and afaics shouldn't generate any conflicts. On a
green field we probably name them differently, but ...

I'm really pretty much dead set against the "candidate key with the
smallest OID" proposal. I think that's just plain old bad idea. It's
just magical behavior which will result in users being surprised and
unhappy. I don't think there's really a problem with saying, hey, if
you configure changeset extraction and you don't configure a replica
identity, then you don't get any columns from the old tuple. If you
don't like that, change the configuration. It's always nice to spare
users unnecessary configuration, of course, but trying to make things
simpler than they really are tends to hurt more than it helps.

On the naming, I find REPLICA IDENTITY to be pretty good. We've
already places where we're using the REPLICA keyword to indicate
places where we've got core support intended to dovetail with external
replication solutions, and this seems to fit that paradigm nicely.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#97)
Re: logical changeset generation v6.4

On 2013-10-21 09:40:13 -0400, Robert Haas wrote:

On Fri, Oct 18, 2013 at 2:50 PM, Andres Freund <andres@2ndquadrant.com> wrote:

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)
* primary key
* candidate key with the smallest oid

Including the candidate key will help people using changeset extration
for auditing that do not have primary key. That really isn't an
infrequent usecase.

I've chosen REPLICA IDENTITY; NOTHIN; FULL; because those are all
existing keywords, and afaics shouldn't generate any conflicts. On a
green field we probably name them differently, but ...

I'm really pretty much dead set against the "candidate key with the
smallest OID" proposal. I think that's just plain old bad idea. It's
just magical behavior which will result in users being surprised and
unhappy. I don't think there's really a problem with saying, hey, if
you configure changeset extraction and you don't configure a replica
identity, then you don't get any columns from the old tuple.

I have a hard time to understand why you dislike it so much. Think of a
big schema where you want to add auditing via changeset
extraction. Because of problems with reindexing primary key you've just
used candidate keys so far. Why should you go through each of a couple
of hundred tables and explictly choose an index when you just want an
identifier of changed rows?
By nature of it being a candidate key it is *guranteed* to uniquely
identify a row? And you can make the output plugin give you the used
columns/the indexname without a problem.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#99Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#96)
Re: logical changeset generation v6.2

On 2013-10-21 09:32:12 -0400, Robert Haas wrote:

On Fri, Oct 18, 2013 at 2:26 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I know of the following solutions:
1) Don't allow VACUUM FULL on catalog tables if wal_level = logical.
2) Make VACUUM FULL prevent DDL and then wait till all changestreams
have decoded up to the current point.
3) don't delete the old relfilenode for VACUUM/CLUSTERs of system tables
if there are life decoding slots around, instead delegate that
responsibility to the slot management.
4) Store both (cmin, cmax) for catalog tuples.

I bascially think only 1) and 4) are realistic. And 1) sucks.

I've developed a prototype for 4) and except currently being incredibly
ugly, it seems to be the most promising approach by far. My trick to
store both cmin and cmax is to store cmax in t_hoff managed space when
wal_level = logical.

In my opinion, (4) is too ugly to consider. I think that if we start
playing games like this, we're opening up the doors to lots of subtle
bugs and future architectural pain that will be with us for many, many
years to come. I believe we will bitterly regret any foray into this
area.

Hm. After looking at the required code - which you obviously cannot have
yet - it's not actually too bad. Will post a patch implementing it later.

I don't really buy the architectural argument since originally cmin/cmax
*were* both stored. It's not something we're just inventing now. We just
optimized that away but now have discovered that's not always a good
idea and thus don't always use the optimization.

The actual decoding code shrinks by about 200 lines using this logic
which is a hint that it's not a bad idea.

It has long seemed to me to be a shame that we don't have some system
for allowing old relfilenodes to stick around until they are no longer
in use. If we had that, we might be able to allow utilities like
CLUSTER or VACUUM FULL to permit concurrent read access to the table.
I realize that what people really want is to let those things run
while allowing concurrent *write* access to the table, but a bird in
the hand is worth two in the bush. What we're really talking about
here is applying MVCC to filesystem actions: instead of removing the
old relfilenode(s) immediately, we do it when they're no longer
referenced by anyone, just as we don't remove old tuples immediately,
but rather when they are no longer referenced by anyone. The details
are tricky, though: we can allow write access to the *new* heap just
as soon as the rewrite is finished, but anyone who is still looking at
the *old* heap can't ever upgrade their AccessShareLock to anything
higher, or hilarity will ensue. Also, if they lock some *other*
relation and AcceptInvalidationMessages(), their relcache entry for
the rewritten relation will get rebuilt, and that's bound to work out
poorly. The net-net here is that I think (3) is an attractive
solution, but I don't know that we can make it work in a reasonable
amount of time.

I've looked at it before, and I honestly don't have a real clue how to
do it robustly.

I don't think I understand exactly what you have in mind for (2); can
you elaborate? I have always thought that having a
WaitForDecodingToCatchUp() primitive was a good way of handling
changes that were otherwise too difficult to track our way through. I
am not sure you're doing that at all right now, which in some sense I
guess is fine, but I haven't really understood your aversion to this
solution. There are some locking issues to be worked out here, but
the problems don't seem altogether intractable.

So, what we need to do for rewriting catalog tables would be:
1) lock table against writes
2) wait for all in-progress xacts to finish, they could have modified
the table in question (we don't keep locks on system tables)
3) acquire xlog insert pointer
4) wait for all logical decoding actions to read past that pointer
5) upgrade the lock to an access exclusive one
6) perform vacuum full as usual

The lock upgrade hazards in here are the reason I am adverse to the
solution. And I don't see how we can avoid them, since in order for
decoding to catchup it has to be able to read from the
catalog... Otherwise it's easy enough to implement.

(1) is basically deciding not to fix the problem. I don't think
that's acceptable.

I'd like to argue against this, but unfortunately I agree.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#100Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Andres Freund (#95)
Re: logical changeset generation v6.4

On 10/18/2013 08:50 PM, Andres Freund wrote:

On 2013-10-18 08:11:29 -0400, Robert Haas wrote:

...

2. If that seems too complicated, how about just logging the whole old
tuple for version 1?

I think that'd make the patch much less useful because it bloats WAL
unnecessarily for the primary user (replication) of it. I'd rather go
for primary keys only if that proves to be the contentious point.

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)
* primary key
* candidate key with the smallest oid

Including the candidate key will help people using changeset extration
for auditing that do not have primary key. That really isn't an
infrequent usecase.

As I understand it for a table with *no* unique index,
the "candidate key" is the full tuple, so if we get an UPDATE for
it then this should be replicated as
"UPDATE first row matching (NOT DISTINCT FROM) all columns"
which on replay side will be equivalent to
CREATE CURSOR ...; FETCH 1 ...; UPDATE ... WHERE CURRENT...'

I know that this will slow down replication, as you can not use direct
index updates internally - at least not easily - but need to let postgreSQL
actually plan this, but such single row update is no faster on origin
either.

Of course when it is a full-table update on a table with no
indexes, then doing the same one tuple at a time is really slow.

--
Hannu Krosing
PostgreSQL Consultant
Performance, Scalability and High Availability
2ndQuadrant Nordic O�

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#101Andres Freund
andres@2ndquadrant.com
In reply to: Hannu Krosing (#100)
Re: logical changeset generation v6.4

On 2013-10-21 16:40:43 +0200, Hannu Krosing wrote:

On 10/18/2013 08:50 PM, Andres Freund wrote:

On 2013-10-18 08:11:29 -0400, Robert Haas wrote:

...

2. If that seems too complicated, how about just logging the whole old
tuple for version 1?

I think that'd make the patch much less useful because it bloats WAL
unnecessarily for the primary user (replication) of it. I'd rather go
for primary keys only if that proves to be the contentious point.

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)
* primary key
* candidate key with the smallest oid

Including the candidate key will help people using changeset extration
for auditing that do not have primary key. That really isn't an
infrequent usecase.

As I understand it for a table with *no* unique index,
the "candidate key" is the full tuple, so if we get an UPDATE for
it then this should be replicated as
"UPDATE first row matching (NOT DISTINCT FROM) all columns"
which on replay side will be equivalent to
CREATE CURSOR ...; FETCH 1 ...; UPDATE ... WHERE CURRENT...'

No, it's not a candidate key since it's not uniquely identifying a
row. You can play tricks as you describe, but that still doesn't make
the whole row a candidate key.

But anyway, I suggest allowing for logging all columns above...

I know that this will slow down replication, as you can not use direct
index updates internally - at least not easily - but need to let postgreSQL
actually plan this, but such single row update is no faster on origin
either.

That's not actually true. Consider somebody doing something like:
UPDATE big_table_without_indexes SET column = ...;
On the source side that's essentialy O(n). If you replicate on a
row-by-row basis it will be O(n^2) on the replay side.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#102Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#98)
Re: logical changeset generation v6.4

On Mon, Oct 21, 2013 at 9:51 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I have a hard time to understand why you dislike it so much. Think of a
big schema where you want to add auditing via changeset
extraction. Because of problems with reindexing primary key you've just
used candidate keys so far. Why should you go through each of a couple
of hundred tables and explictly choose an index when you just want an
identifier of changed rows?
By nature of it being a candidate key it is *guranteed* to uniquely
identify a row? And you can make the output plugin give you the used
columns/the indexname without a problem.

Sure, well, if a particular user wants to choose candidate keys
essentially at random from among the unique indexes present, there's
nothing to prevent them from writing a script to do that. But
assuming that one unique index is just as good as another is just
wrong. If you pick a "candidate key" that doesn't actually represent
the users' notion of row identity, then your audit log will be
thoroughly useless, even if it does uniquely identify the rows
involved.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#103Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#102)
Re: logical changeset generation v6.4

On 2013-10-21 11:14:37 -0400, Robert Haas wrote:

On Mon, Oct 21, 2013 at 9:51 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I have a hard time to understand why you dislike it so much. Think of a
big schema where you want to add auditing via changeset
extraction. Because of problems with reindexing primary key you've just
used candidate keys so far. Why should you go through each of a couple
of hundred tables and explictly choose an index when you just want an
identifier of changed rows?
By nature of it being a candidate key it is *guranteed* to uniquely
identify a row? And you can make the output plugin give you the used
columns/the indexname without a problem.

Sure, well, if a particular user wants to choose candidate keys
essentially at random from among the unique indexes present, there's
nothing to prevent them from writing a script to do that. But
assuming that one unique index is just as good as another is just
wrong. If you pick a "candidate key" that doesn't actually represent
the users' notion of row identity, then your audit log will be
thoroughly useless, even if it does uniquely identify the rows
involved.

Why? If the columns are specified in the log, by definition the values
will be sufficient to identify a row. Even if a "nicer" key might exist.

Since I seemingly can't convince you, I'll modify things that way for
now as it can easily be changed later, but I still don't see the
problem.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#104Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Andres Freund (#101)
Re: logical changeset generation v6.4

On 10/21/2013 05:06 PM, Andres Freund wrote:

On 2013-10-21 16:40:43 +0200, Hannu Krosing wrote:

On 10/18/2013 08:50 PM, Andres Freund wrote:

On 2013-10-18 08:11:29 -0400, Robert Haas wrote:

...

2. If that seems too complicated, how about just logging the whole old
tuple for version 1?

I think that'd make the patch much less useful because it bloats WAL
unnecessarily for the primary user (replication) of it. I'd rather go
for primary keys only if that proves to be the contentious point.

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)
* primary key
* candidate key with the smallest oid

Including the candidate key will help people using changeset extration
for auditing that do not have primary key. That really isn't an
infrequent usecase.

As I understand it for a table with *no* unique index,
the "candidate key" is the full tuple, so if we get an UPDATE for
it then this should be replicated as
"UPDATE first row matching (NOT DISTINCT FROM) all columns"
which on replay side will be equivalent to
CREATE CURSOR ...; FETCH 1 ...; UPDATE ... WHERE CURRENT...'

No, it's not a candidate key since it's not uniquely identifying a
row. You can play tricks as you describe, but that still doesn't make
the whole row a candidate key.

But anyway, I suggest allowing for logging all columns above...

I the "all columns" option this ?

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;

for some reason I thought it to be option to either log or not log PK column ...

I know that this will slow down replication, as you can not use direct
index updates internally - at least not easily - but need to let postgreSQL
actually plan this, but such single row update is no faster on origin
either.

That's not actually true. Consider somebody doing something like:
UPDATE big_table_without_indexes SET column = ...;
On the source side that's essentialy O(n). If you replicate on a
row-by-row basis it will be O(n^2) on the replay side.

Probably more like O(n^2 / 2) but yes, this is what I meant with the
sentence
after that ;)

Cheers

--
Hannu Krosing
PostgreSQL Consultant
Performance, Scalability and High Availability
2ndQuadrant Nordic O�

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#105Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#99)
1 attachment(s)
Re: logical changeset generation v6.2

On 2013-10-21 16:15:58 +0200, Andres Freund wrote:

On 2013-10-21 09:32:12 -0400, Robert Haas wrote:

On Fri, Oct 18, 2013 at 2:26 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I know of the following solutions:
1) Don't allow VACUUM FULL on catalog tables if wal_level = logical.
2) Make VACUUM FULL prevent DDL and then wait till all changestreams
have decoded up to the current point.
3) don't delete the old relfilenode for VACUUM/CLUSTERs of system tables
if there are life decoding slots around, instead delegate that
responsibility to the slot management.
4) Store both (cmin, cmax) for catalog tuples.

I bascially think only 1) and 4) are realistic. And 1) sucks.

I've developed a prototype for 4) and except currently being incredibly
ugly, it seems to be the most promising approach by far. My trick to
store both cmin and cmax is to store cmax in t_hoff managed space when
wal_level = logical.

In my opinion, (4) is too ugly to consider. I think that if we start
playing games like this, we're opening up the doors to lots of subtle
bugs and future architectural pain that will be with us for many, many
years to come. I believe we will bitterly regret any foray into this
area.

Hm. After looking at the required code - which you obviously cannot have
yet - it's not actually too bad. Will post a patch implementing it later.

I don't really buy the architectural argument since originally cmin/cmax
*were* both stored. It's not something we're just inventing now. We just
optimized that away but now have discovered that's not always a good
idea and thus don't always use the optimization.

The actual decoding code shrinks by about 200 lines using this logic
which is a hint that it's not a bad idea.

So, here's a preliminary patch to see how this would look. It'd be great
of you comment if you still think it's a completel no-go.

If it were for real, it'd need to be split and some minor things would
need to get adjusted, but I think it's easier to review it seing both
sides at once.

Greetings,

Andres Freund

PS: The patch is ontop of a new git push, but for review that shouldn't
matter.

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Rewrite-CommandId-handling.patchtext/x-patch; charset=us-asciiDownload
>From 8284d22491a35649e84a142bdde9761ecfd33cd7 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 21 Oct 2013 18:57:40 +0200
Subject: [PATCH] wal_decoding: Rewrite CommandId handling

CommandId's in postgres are compressed into one storage field using
the combocid mechanism. This is problematic for changeset extraction
of mixed DDL/DML transaction since we have to look at intermediate
catalog states.

We used to handle that by logging (relfilenode, ctid, cmin, cmax) for
every modified catalog tuple and then looking up the correct cmin,
cmax in an hash over the physical location. That doesn't suffice to
handle VACUUM FREEZE/CLUSTER which change the physical location of a
tuple.
Instead store both cmax, cmin for catalog tuples by increasing t_hoff
of HeapTupleHeaders and storing cmax there. Cmin is stored at the
current location.
We detect such tuples by having HEAP_MOVED_OFF | HEAP_MOVED_IN
set. That combination could not exist yet.
---
 src/backend/access/common/heaptuple.c           |  46 +++
 src/backend/access/common/tupdesc.c             |   9 +-
 src/backend/access/heap/heapam.c                | 443 +++++++++++++++---------
 src/backend/access/heap/rewriteheap.c           |   6 +
 src/backend/access/heap/tuptoaster.c            |  24 ++
 src/backend/access/rmgrdesc/heapdesc.c          |   9 -
 src/backend/access/rmgrdesc/xactdesc.c          |   7 +
 src/backend/access/transam/xact.c               |  47 +++
 src/backend/commands/cluster.c                  |   7 +
 src/backend/replication/logical/decode.c        |  43 ++-
 src/backend/replication/logical/reorderbuffer.c | 227 +-----------
 src/backend/replication/logical/snapbuild.c     |  41 +--
 src/backend/utils/cache/inval.c                 |  21 +-
 src/backend/utils/cache/relcache.c              |  21 ++
 src/backend/utils/time/combocid.c               |  49 ++-
 src/backend/utils/time/tqual.c                  |  74 ++--
 src/include/access/heapam_xlog.h                |  25 +-
 src/include/access/htup_details.h               |  42 ++-
 src/include/access/tupdesc.h                    |   1 +
 src/include/access/xact.h                       |   8 +
 src/include/replication/reorderbuffer.h         |   3 -
 src/include/replication/snapbuild.h             |   4 +-
 src/include/utils/tqual.h                       |  11 +-
 src/tools/pgindent/typedefs.list                |   2 +-
 24 files changed, 601 insertions(+), 569 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index e39b977..9e4297a 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -679,6 +679,9 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 	if (hasnull)
 		len += BITMAPLEN(numberOfAttributes);
 
+	if (tupleDescriptor->tdhaswidecid)
+		len += sizeof(CommandId);
+
 	if (tupleDescriptor->tdhasoid)
 		len += sizeof(Oid);
 
@@ -712,6 +715,8 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 
 	if (tupleDescriptor->tdhasoid)		/* else leave infomask = 0 */
 		td->t_infomask = HEAP_HASOID;
+	if (tupleDescriptor->tdhaswidecid)
+		td->t_infomask |= HEAP_HAS_WIDE_CID;
 
 	heap_fill_tuple(tupleDescriptor,
 					values,
@@ -1537,3 +1542,44 @@ minimal_tuple_from_heap_tuple(HeapTuple htup)
 	result->t_len = len;
 	return result;
 }
+
+/*
+ * HeapTupleHeaderSetCmax
+ * 		Set Cmax of a HeapTupleHeader, regardless wether it's a wide
+ *		(storing cmin and cmax simultanously) tuple or not.
+ */
+extern void
+HeapTupleHeaderSetCmax(HeapTupleHeader tup, CommandId cid, bool iscombo)
+{
+	Assert(!HeapTupleHeaderHasMoved(tup));
+
+	if (tup->t_infomask & HEAP_HAS_WIDE_CID)
+	{
+		CommandId *l;
+
+		/* HeapTupleHeaderAdjustCmax should have set it to false */
+		Assert(!iscombo);
+
+		if (tup->t_infomask & HEAP_HASOID)
+			l = (CommandId *) ((char *)tup +
+								tup->t_hoff -
+								sizeof(Oid) -
+								sizeof(CommandId));
+		else
+			l = (CommandId *) ((char *)tup +
+								tup->t_hoff -
+								sizeof(CommandId));
+		*l = cid;
+		tup->t_infomask &= ~HEAP_COMBOCID;
+	}
+	else if (iscombo)
+	{
+		tup->t_choice.t_heap.t_field3.t_cid = cid;
+		tup->t_infomask |= HEAP_COMBOCID;
+	}
+	else
+	{
+		tup->t_choice.t_heap.t_field3.t_cid = cid;
+		tup->t_infomask &= ~HEAP_COMBOCID;
+	}
+}
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 11c31d8..0bbc337 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -91,6 +91,7 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
 	desc->tdtypeid = RECORDOID;
 	desc->tdtypmod = -1;
 	desc->tdhasoid = hasoid;
+	desc->tdhaswidecid = false;
 	desc->tdrefcount = -1;		/* assume not reference-counted */
 
 	return desc;
@@ -124,6 +125,7 @@ CreateTupleDesc(int natts, bool hasoid, Form_pg_attribute *attrs)
 	desc->tdtypeid = RECORDOID;
 	desc->tdtypmod = -1;
 	desc->tdhasoid = hasoid;
+	desc->tdhaswidecid = false;
 	desc->tdrefcount = -1;		/* assume not reference-counted */
 
 	return desc;
@@ -143,6 +145,7 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 	int			i;
 
 	desc = CreateTemplateTupleDesc(tupdesc->natts, tupdesc->tdhasoid);
+	desc->tdhaswidecid = tupdesc->tdhaswidecid;
 
 	for (i = 0; i < desc->natts; i++)
 	{
@@ -150,9 +153,9 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 		desc->attrs[i]->attnotnull = false;
 		desc->attrs[i]->atthasdef = false;
 	}
-
 	desc->tdtypeid = tupdesc->tdtypeid;
 	desc->tdtypmod = tupdesc->tdtypmod;
+	desc->tdhaswidecid = tupdesc->tdhaswidecid;
 
 	return desc;
 }
@@ -180,6 +183,7 @@ CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts)
 	Assert(moreatts >= 0);
 
 	desc = CreateTemplateTupleDesc(src_natts + moreatts, tupdesc->tdhasoid);
+	desc->tdhaswidecid = tupdesc->tdhaswidecid;
 
 	for (i = 0; i < src_natts; i++)
 	{
@@ -204,6 +208,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 	int			i;
 
 	desc = CreateTemplateTupleDesc(tupdesc->natts, tupdesc->tdhasoid);
+	desc->tdhaswidecid = tupdesc->tdhaswidecid;
 
 	for (i = 0; i < desc->natts; i++)
 	{
@@ -352,6 +357,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 		return false;
 	if (tupdesc1->tdhasoid != tupdesc2->tdhasoid)
 		return false;
+	if (tupdesc1->tdhaswidecid != tupdesc2->tdhaswidecid)
+		return false;
 
 	for (i = 0; i < tupdesc1->natts; i++)
 	{
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 87a752c..e08a122 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -85,7 +85,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
-				HeapTuple newtup, HeapTuple old_idx_tup,
+				HeapTuple newtup, CommandId cid,
+				HeapTuple old_idx_tup,
 				bool all_visible_cleared, bool new_all_visible_cleared);
 static void HeapSatisfiesHOTandKeyUpdate(Relation relation,
 						  Bitmapset *hot_attrs,
@@ -110,7 +111,6 @@ static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static bool ConditionalMultiXactIdWait(MultiXactId multi,
 						   MultiXactStatus status, int *remaining,
 						   uint16 infomask);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
 static HeapTuple ExtractKeyTuple(Relation rel, HeapTuple tup);
 
 
@@ -2116,11 +2116,11 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		xl_heap_insert xlrec;
 		xl_heap_header xlhdr;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[4];
+		XLogRecData	rdata[5];
 		Page		page = BufferGetPage(buffer);
 		uint8		info = XLOG_HEAP_INSERT;
 		bool		need_tuple_data;
-
+		int			last_rdata;
 		/*
 		 * For logical replication, we need the tuple even if we're doing a
 		 * full page write, so make sure to log it separately. (XXX We could
@@ -2130,8 +2130,6 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 * properly decode, so log that as well.
 		 */
 		need_tuple_data = RelationIsLogicallyLogged(relation);
-		if (RelationIsAccessibleInLogicalDecoding(relation))
-			log_heap_new_cid(relation, heaptup);
 
 		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.target.node = relation->rd_node;
@@ -2163,6 +2161,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		rdata[2].buffer_std = true;
 		rdata[2].next = NULL;
 
+		last_rdata = 2;
+
 		/*
 		 * Make a separate rdata entry for the tuple's buffer if we're
 		 * doing logical decoding, so that an eventual FPW doesn't
@@ -2170,18 +2170,36 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 */
 		if (need_tuple_data)
 		{
-			rdata[2].next = &(rdata[3]);
+			rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+			last_rdata += 1;
 
-			rdata[3].data = NULL;
-			rdata[3].len = 0;
-			rdata[3].buffer = buffer;
-			rdata[3].buffer_std = true;
-			rdata[3].next = NULL;
+			rdata[last_rdata].data = NULL;
+			rdata[last_rdata].len = 0;
+			rdata[last_rdata].buffer = buffer;
+			rdata[last_rdata].buffer_std = true;
+			rdata[last_rdata].next = NULL;
 
 			xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
 		}
 
 		/*
+		 * Log the current commandid for catalog tuples when logical
+		 * decoding is enabled.
+		 */
+		if (RelationIsAccessibleInLogicalDecoding(relation))
+		{
+			rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+			last_rdata += 1;
+
+			rdata[last_rdata].data = (void *) &cid;
+			rdata[last_rdata].len = sizeof(CommandId);
+			rdata[last_rdata].buffer = InvalidBuffer;
+			rdata[last_rdata].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_CID;
+		}
+
+		/*
 		 * If this is the single and first tuple on page, we can reinit the
 		 * page instead of restoring the whole thing.  Set flag, and hide
 		 * buffer references from XLogInsert.
@@ -2190,7 +2208,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 		{
 			info |= XLOG_HEAP_INIT_PAGE;
-			rdata[1].buffer = rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
+			rdata[1].buffer = rdata[2].buffer = rdata[3].buffer =
+				rdata[4].buffer = InvalidBuffer;
 		}
 
 		recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -2228,6 +2247,47 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 }
 
 /*
+ * Convert a normal HeapTuple storing only either cmin, cmax or a
+ * combocid (c.f. combocid.c) into one storing both, cmin and
+ * cmax. That's required for decoding mixed DDL/DML transactions.
+ */
+static HeapTuple
+heap_prepare_system_tuple(Relation relation, HeapTuple tup)
+{
+	TupleDesc	tupleDesc;
+	bool		isnull[MaxHeapAttributeNumber];
+	Datum		values[MaxHeapAttributeNumber];
+	HeapTuple	newtup;
+	size_t		hoff;
+
+	Assert(!HeapTupleHeaderHasWideCid(tup->t_data));
+
+	tupleDesc = RelationGetDescr(relation);
+
+	Assert(tupleDesc->tdhaswidecid);
+
+	heap_deform_tuple(tup, tupleDesc, values, isnull);
+	newtup = heap_form_tuple(tupleDesc, values, isnull);
+
+	Assert(HeapTupleHeaderHasWideCid(newtup->t_data));
+
+	hoff = newtup->t_data->t_hoff;
+	memcpy(newtup->t_data, tup->t_data, offsetof(HeapTupleHeaderData, t_bits));
+	newtup->t_data->t_hoff = hoff;
+
+	newtup->t_data->t_infomask |= HEAP_HAS_WIDE_CID;
+
+	if (tup->t_data->t_infomask & HEAP_HASOID)
+		HeapTupleHeaderSetOid(newtup->t_data, HeapTupleHeaderGetOid(tup->t_data));
+
+	HeapTupleHeaderSetCmin(newtup->t_data, HeapTupleHeaderGetRawCommandId(tup->t_data));
+
+	newtup->t_tableOid = tup->t_tableOid;
+
+	return newtup;
+}
+
+/*
  * Subroutine for heap_insert(). Prepares a tuple for insertion. This sets the
  * tuple header fields, assigns an OID, and toasts the tuple if necessary.
  * Returns a toasted version of the tuple if it was toasted, or the original
@@ -2262,8 +2322,16 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 		Assert(!(tup->t_data->t_infomask & HEAP_HASOID));
 	}
 
-	tup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
-	tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
+	if (RelationIsAccessibleInLogicalDecoding(relation) &&
+	    !HeapTupleHeaderHasWideCid(tup->t_data))
+	{
+		if (IsSystemRelation(relation))
+			elog(WARNING, "to be inserted system tuple does not have wide cids");
+		tup = heap_prepare_system_tuple(relation, tup);
+	}
+
+	tup->t_data->t_infomask &= ~HEAP_XACT_MASK;
+	tup->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;
 	tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
 	if (options & HEAP_INSERT_FROZEN)
 	{
@@ -2272,6 +2340,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 	}
 	else
 		HeapTupleHeaderSetXmin(tup->t_data, xid);
+	HeapTupleHeaderSetCmax(tup->t_data, InvalidCommandId, false);
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
 	HeapTupleHeaderSetXmax(tup->t_data, 0);		/* for cleanliness */
 	tup->t_tableOid = RelationGetRelid(relation);
@@ -2317,7 +2386,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 	bool		needwal;
 	Size		saveFreeSpace;
 	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
 
 	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
 	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
@@ -2404,12 +2472,13 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 		{
 			XLogRecPtr	recptr;
 			xl_heap_multi_insert *xlrec;
-			XLogRecData rdata[3];
+			XLogRecData	rdata[4];
 			uint8		info = XLOG_HEAP2_MULTI_INSERT;
 			char	   *tupledata;
 			int			totaldatalen;
 			char	   *scratchptr = scratch;
 			bool		init;
+			int			last_rdata;
 
 			/*
 			 * If the page was previously empty, we can reinit the page
@@ -2466,13 +2535,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 					   datalen);
 				tuphdr->datalen = datalen;
 				scratchptr += datalen;
-
-				/*
-				 * We don't use heap_multi_insert for catalog tuples yet, but
-				 * better be prepared...
-				 */
-				if (need_cids)
-					log_heap_new_cid(relation, heaptup);
 			}
 			totaldatalen = scratchptr - tupledata;
 			Assert((scratchptr - scratch) < BLCKSZ);
@@ -2488,6 +2550,8 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			rdata[1].buffer_std = true;
 			rdata[1].next = NULL;
 
+			last_rdata = 1;
+
 			/*
 			 * Make a separate rdata entry for the tuple's buffer if
 			 * we're doing logical decoding, so that an eventual FPW
@@ -2495,23 +2559,42 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			 */
 			if (need_tuple_data)
 			{
-				rdata[1].next = &(rdata[2]);
-
-				rdata[2].data = NULL;
-				rdata[2].len = 0;
-				rdata[2].buffer = buffer;
-				rdata[2].buffer_std = true;
-				rdata[2].next = NULL;
+				rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+				last_rdata++;
+
+				rdata[last_rdata].data = NULL;
+				rdata[last_rdata].len = 0;
+				rdata[last_rdata].buffer = buffer;
+				rdata[last_rdata].buffer_std = true;
+				rdata[last_rdata].next = NULL;
 				xlrec->flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
 			}
 
 			/*
+			 * Log the current CommandId for catalog tuples when
+			 * logical decoding is enabled.
+			 */
+			if (RelationIsAccessibleInLogicalDecoding(relation))
+			{
+				rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+				last_rdata += 1;
+
+				rdata[last_rdata].data = (void *) &cid;
+				rdata[last_rdata].len = sizeof(CommandId);
+				rdata[last_rdata].buffer = InvalidBuffer;
+				rdata[last_rdata].next = NULL;
+
+				xlrec->flags |= XLOG_HEAP_CONTAINS_CID;
+			}
+
+			/*
 			 * If we're going to reinitialize the whole page using the WAL
 			 * record, hide buffer reference from XLogInsert.
 			 */
 			if (init)
 			{
-				rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
+				rdata[1].buffer = rdata[2].buffer = rdata[3].buffer
+					= InvalidBuffer;
 				info |= XLOG_HEAP_INIT_PAGE;
 			}
 
@@ -2672,6 +2755,13 @@ heap_delete(Relation relation, ItemPointer tid,
 	tp.t_len = ItemIdGetLength(lp);
 	tp.t_self = *tid;
 
+	if (RelationIsAccessibleInLogicalDecoding(relation) &&
+	    !HeapTupleHeaderHasWideCid(tp.t_data) &&
+	    TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tp.t_data)))
+	{
+		elog(ERROR, "to be delete tuple does not have wide cids");
+	}
+
 l1:
 	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
 
@@ -2848,7 +2938,8 @@ l1:
 							  &new_xmax, &new_infomask, &new_infomask2);
 
 	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	HeapTupleHeaderClearMoved(tp.t_data);
 	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	tp.t_data->t_infomask |= new_infomask;
 	tp.t_data->t_infomask2 |= new_infomask2;
@@ -2865,11 +2956,8 @@ l1:
 	{
 		xl_heap_delete xlrec;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[4];
-
-		/* For logical decode we need combocids to properly decode the catalog */
-		if (RelationIsAccessibleInLogicalDecoding(relation))
-			log_heap_new_cid(relation, &tp);
+		XLogRecData	rdata[5];
+		int			last_rdata;
 
 		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
@@ -2888,6 +2976,8 @@ l1:
 		rdata[1].buffer_std = true;
 		rdata[1].next = NULL;
 
+		last_rdata = 1;
+
 		/*
 		 * Log primary key of the deleted tuple
 		 */
@@ -2914,6 +3004,24 @@ l1:
 			rdata[3].next = NULL;
 
 			xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+			last_rdata = 3;
+		}
+
+		/*
+		 * Log the current commandid for catalog tuples when logical
+		 * decoding is enabled.
+		 */
+		if (RelationIsAccessibleInLogicalDecoding(relation))
+		{
+			rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+			last_rdata += 1;
+
+			rdata[last_rdata].data = (void *) &cid;
+			rdata[last_rdata].len = sizeof(CommandId);
+			rdata[last_rdata].buffer = InvalidBuffer;
+			rdata[last_rdata].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_CID;
 		}
 
 		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE, rdata);
@@ -3143,6 +3251,13 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		Assert(!(newtup->t_data->t_infomask & HEAP_HASOID));
 	}
 
+	if (RelationIsAccessibleInLogicalDecoding(relation) &&
+	    !HeapTupleHeaderHasWideCid(oldtup.t_data) &&
+	    TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(oldtup.t_data)))
+	{
+		elog(ERROR, "to be updated tuple does not have wide cids");
+	}
+
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3182,6 +3297,17 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	}
 
 	/*
+	 * Note: below this point, heaptup is the data we actually intend
+	 * to store into the relation; newtup is the caller's original
+	 * untoasted data.
+	 */
+	if (RelationIsAccessibleInLogicalDecoding(relation) &&
+	    !HeapTupleHeaderHasWideCid(newtup->t_data))
+		heaptup = heap_prepare_system_tuple(relation, newtup);
+	else
+		heaptup = newtup;
+
+	/*
 	 * Note: beyond this point, use oldtup not otid to refer to old tuple.
 	 * otid may very well point at newtup->t_self, which we will overwrite
 	 * with the new tuple's location, so there's great risk of confusion if we
@@ -3462,13 +3588,15 @@ l2:
 	 * Prepare the new tuple with the appropriate initial values of Xmin and
 	 * Xmax, as well as initial infomask bits as computed above.
 	 */
-	newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
-	newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
-	HeapTupleHeaderSetXmin(newtup->t_data, xid);
-	HeapTupleHeaderSetCmin(newtup->t_data, cid);
-	newtup->t_data->t_infomask |= HEAP_UPDATED | infomask_new_tuple;
-	newtup->t_data->t_infomask2 |= infomask2_new_tuple;
-	HeapTupleHeaderSetXmax(newtup->t_data, xmax_new_tuple);
+	heaptup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
+	heaptup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
+	HeapTupleHeaderSetXmin(heaptup->t_data, xid);
+	/* overwrite if we're not using wide cids */
+	HeapTupleHeaderSetCmax(heaptup->t_data, InvalidCommandId, false);
+	HeapTupleHeaderSetCmin(heaptup->t_data, cid);
+	heaptup->t_data->t_infomask |= HEAP_UPDATED | infomask_new_tuple;
+	heaptup->t_data->t_infomask2 |= infomask2_new_tuple;
+	HeapTupleHeaderSetXmax(heaptup->t_data, xmax_new_tuple);
 
 	/*
 	 * Replace cid with a combo cid if necessary.  Note that we already put
@@ -3492,22 +3620,24 @@ l2:
 	{
 		/* toast table entries should never be recursively toasted */
 		Assert(!HeapTupleHasExternal(&oldtup));
-		Assert(!HeapTupleHasExternal(newtup));
+		Assert(!HeapTupleHasExternal(heaptup));
 		need_toast = false;
 	}
 	else
 		need_toast = (HeapTupleHasExternal(&oldtup) ||
-					  HeapTupleHasExternal(newtup) ||
-					  newtup->t_len > TOAST_TUPLE_THRESHOLD);
+					  HeapTupleHasExternal(heaptup) ||
+					  heaptup->t_len > TOAST_TUPLE_THRESHOLD);
 
 	pagefree = PageGetHeapFreeSpace(page);
 
-	newtupsize = MAXALIGN(newtup->t_len);
+	newtupsize = MAXALIGN(heaptup->t_len);
 
 	if (need_toast || newtupsize > pagefree)
 	{
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		HeapTupleHeaderClearMoved(oldtup.t_data);
+
 		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		HeapTupleClearHotUpdated(&oldtup);
 		/* ... and store info about transaction updating this tuple */
@@ -3523,19 +3653,13 @@ l2:
 
 		/*
 		 * Let the toaster do its thing, if needed.
-		 *
-		 * Note: below this point, heaptup is the data we actually intend to
-		 * store into the relation; newtup is the caller's original untoasted
-		 * data.
 		 */
 		if (need_toast)
 		{
 			/* Note we always use WAL and FSM during updates */
-			heaptup = toast_insert_or_update(relation, newtup, &oldtup, 0);
+			heaptup = toast_insert_or_update(relation, heaptup, &oldtup, 0);
 			newtupsize = MAXALIGN(heaptup->t_len);
 		}
-		else
-			heaptup = newtup;
 
 		/*
 		 * Now, do we need a new page for the tuple, or not?  This is a bit
@@ -3591,7 +3715,6 @@ l2:
 		/* No TOAST work needed, and it'll fit on same page */
 		already_marked = false;
 		newbuf = buffer;
-		heaptup = newtup;
 	}
 
 	/*
@@ -3669,7 +3792,9 @@ l2:
 	if (!already_marked)
 	{
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		HeapTupleHeaderClearMoved(oldtup.t_data);
+
 		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		/* ... and store info about transaction updating this tuple */
 		Assert(TransactionIdIsValid(xmax_old_tuple));
@@ -3707,16 +3832,9 @@ l2:
 	{
 		XLogRecPtr	recptr;
 
-		/* For logical decode we need combocids to properly decode the catalog */
-		if (RelationIsAccessibleInLogicalDecoding(relation))
-		{
-			log_heap_new_cid(relation, &oldtup);
-			log_heap_new_cid(relation, heaptup);
-		}
-
 		recptr = log_heap_update(relation, buffer,
 								 newbuf, &oldtup, heaptup,
-								 old_idx_tuple,
+								 cid, old_idx_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
 		if (newbuf != buffer)
@@ -5306,7 +5424,7 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
 	 * Old-style VACUUM FULL is gone, but we have to keep this code as long as
 	 * we support having MOVED_OFF/MOVED_IN tuples in the database.
 	 */
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderHasMoved(tuple))
 	{
 		xid = HeapTupleHeaderGetXvac(tuple);
 		if (TransactionIdIsNormal(xid) &&
@@ -5317,7 +5435,7 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
 			 * have failed; whereas a non-dead MOVED_IN tuple must mean the
 			 * xvac transaction succeeded.
 			 */
-			if (tuple->t_infomask & HEAP_MOVED_OFF)
+			if (HeapTupleHeaderHasMovedOff(tuple))
 				HeapTupleHeaderSetXvac(tuple, InvalidTransactionId);
 			else
 				HeapTupleHeaderSetXvac(tuple, FrozenTransactionId);
@@ -5642,7 +5760,7 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 		}
 	}
 
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderHasMoved(tuple))
 	{
 		xid = HeapTupleHeaderGetXvac(tuple);
 		if (TransactionIdIsNormal(xid) &&
@@ -5733,7 +5851,7 @@ HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 	TransactionId xmax = HeapTupleHeaderGetUpdateXid(tuple);
 	TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderHasMoved(tuple))
 	{
 		if (TransactionIdPrecedes(*latestRemovedXid, xvac))
 			*latestRemovedXid = xvac;
@@ -5982,7 +6100,7 @@ log_heap_visible(RelFileNode rnode, Buffer heap_buffer, Buffer vm_buffer,
 static XLogRecPtr
 log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
-				HeapTuple idx_tuple,
+				CommandId cid, HeapTuple idx_tuple,
 				bool all_visible_cleared, bool new_all_visible_cleared)
 {
 	xl_heap_update xlrec;
@@ -5990,9 +6108,10 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	xl_heap_header_len xlhdr_idx;
 	uint8		info;
 	XLogRecPtr	recptr;
-	XLogRecData rdata[7];
+	XLogRecData	rdata[8];
 	Page		page = BufferGetPage(newbuf);
 	bool		need_tuple_data = RelationIsLogicallyLogged(reln);
+	int			last_rdata;
 
 	/* Caller should not call me on a non-WAL-logged relation */
 	Assert(RelationNeedsWAL(reln));
@@ -6050,19 +6169,22 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	rdata[3].buffer_std = true;
 	rdata[3].next = NULL;
 
+	last_rdata = 3;
+
 	/*
 	 * separate storage for the buffer reference of the new page in the
 	 * wal_level >= logical case
 	*/
 	if(need_tuple_data)
 	{
-		rdata[3].next = &(rdata[4]);
+		rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+		last_rdata++;
 
-		rdata[4].data = NULL,
-		rdata[4].len = 0;
-		rdata[4].buffer = newbuf;
-		rdata[4].buffer_std = true;
-		rdata[4].next = NULL;
+		rdata[last_rdata].data = NULL,
+		rdata[last_rdata].len = 0;
+		rdata[last_rdata].buffer = newbuf;
+		rdata[last_rdata].buffer_std = true;
+		rdata[last_rdata].next = NULL;
 		xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
 
 		/* candidate key changed and we have a candidate key */
@@ -6074,24 +6196,45 @@ log_heap_update(Relation reln, Buffer oldbuf,
 			xlhdr_idx.header.t_hoff = idx_tuple->t_data->t_hoff;
 			xlhdr_idx.t_len = idx_tuple->t_len;
 
-			rdata[4].next = &(rdata[5]);
-			rdata[5].data = (char *) &xlhdr_idx;
-			rdata[5].len = SizeOfHeapHeaderLen;
-			rdata[5].buffer = InvalidBuffer;
-			rdata[5].next = &(rdata[6]);
+			rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+			last_rdata++;
+
+			rdata[last_rdata].data = (char *) &xlhdr_idx;
+			rdata[last_rdata].len = SizeOfHeapHeaderLen;
+			rdata[last_rdata].buffer = InvalidBuffer;
+
+			rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+			last_rdata++;
 
 			/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
-			rdata[6].data = (char *) idx_tuple->t_data
+			rdata[last_rdata].data = (char *) idx_tuple->t_data
 				+ offsetof(HeapTupleHeaderData, t_bits);
-			rdata[6].len = idx_tuple->t_len
+			rdata[last_rdata].len = idx_tuple->t_len
 				- offsetof(HeapTupleHeaderData, t_bits);
-			rdata[6].buffer = InvalidBuffer;
-			rdata[6].next = NULL;
+			rdata[last_rdata].buffer = InvalidBuffer;
+			rdata[last_rdata].next = NULL;
 
 			xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
 		}
 	}
 
+	/*
+	 * Log the current commandid for catalog tuples when logical
+	 * decoding is enabled.
+	 */
+	if (RelationIsAccessibleInLogicalDecoding(reln))
+	{
+		rdata[last_rdata].next = &(rdata[last_rdata + 1]);
+		last_rdata += 1;
+
+		rdata[last_rdata].data = (void *) &cid;
+		rdata[last_rdata].len = sizeof(CommandId);
+		rdata[last_rdata].buffer = InvalidBuffer;
+		rdata[last_rdata].next = NULL;
+
+		xlrec.flags |= XLOG_HEAP_CONTAINS_CID;
+	}
+
 	/* If new tuple is the single and first tuple on page... */
 	if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
 		PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
@@ -6209,75 +6352,6 @@ log_newpage_buffer(Buffer buffer)
 }
 
 /*
- * Perform XLogInsert of a XLOG_HEAP2_NEW_CID record
- *
- * This is only used in wal_level >= WAL_LEVEL_LOGICAL
- */
-static XLogRecPtr
-log_heap_new_cid(Relation relation, HeapTuple tup)
-{
-	xl_heap_new_cid xlrec;
-
-	XLogRecPtr	recptr;
-	XLogRecData rdata[1];
-	HeapTupleHeader hdr = tup->t_data;
-
-	Assert(ItemPointerIsValid(&tup->t_self));
-	Assert(tup->t_tableOid != InvalidOid);
-
-	xlrec.top_xid = GetTopTransactionId();
-	xlrec.target.node = relation->rd_node;
-	xlrec.target.tid = tup->t_self;
-
-	/*
-	 * if the tuple got inserted & deleted in the same TX we definitely have a
-	 * combocid, set cmin and cmax.
-	 */
-	if (hdr->t_infomask & HEAP_COMBOCID)
-	{
-		Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID));
-		Assert(!(hdr->t_infomask & HEAP_XMIN_INVALID));
-		xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
-		xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
-		xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
-	}
-	/* No combocid, so only cmin or cmax can be set by this TX */
-	else
-	{
-		/*
-		 * tuple inserted.
-		 *
-		 * We need to check for LOCK ONLY because multixacts might be
-		 * transferred to the new tuple in case of FOR KEY SHARE
-		 * updates.
-		 */
-		if (hdr->t_infomask & HEAP_XMAX_INVALID ||
-		    hdr->t_infomask & HEAP_XMAX_LOCK_ONLY)
-		{
-			xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr);
-			xlrec.cmax = InvalidCommandId;
-		}
-		/* tuple from a different tx updated or deleted */
-		else
-		{
-			xlrec.cmin = InvalidCommandId;
-			xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr);
-
-		}
-		xlrec.combocid = InvalidCommandId;
-	}
-
-	rdata[0].data = (char *) &xlrec;
-	rdata[0].len = SizeOfHeapNewCid;
-	rdata[0].buffer = InvalidBuffer;
-	rdata[0].next = NULL;
-
-	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID, rdata);
-
-	return recptr;
-}
-
-/*
  * Build a heap tuple representing the primary or candidate key of the
  * the passed in tuple tp.
  *
@@ -6683,6 +6757,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	ItemId		lp = NULL;
 	HeapTupleHeader htup;
 	BlockNumber blkno;
+	CommandId	cid = FirstCommandId;
 
 	blkno = ItemPointerGetBlockNumber(&(xlrec->target.tid));
 
@@ -6711,6 +6786,13 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	buffer = XLogReadBuffer(xlrec->target.node, blkno, false);
 	if (!BufferIsValid(buffer))
 		return;
+
+	/* CommandId is stored at the end of record data */
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_CID)
+		memcpy(&cid,
+		       (char *) xlrec + record->xl_len - sizeof(CommandId),
+		       sizeof(CommandId));
+
 	page = (Page) BufferGetPage(buffer);
 
 	if (lsn <= PageGetLSN(page))	/* changes are applied */
@@ -6728,13 +6810,15 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 
 	htup = (HeapTupleHeader) PageGetItem(page, lp);
 
-	htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	htup->t_infomask &= ~HEAP_XMAX_BITS;
+	HeapTupleHeaderClearMoved(htup);
+
 	htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	HeapTupleHeaderClearHotUpdated(htup);
 	fix_infomask_from_infobits(xlrec->infobits_set,
 							   &htup->t_infomask, &htup->t_infomask2);
 	HeapTupleHeaderSetXmax(htup, xlrec->xmax);
-	HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
+	HeapTupleHeaderSetCmax(htup, cid, false);
 
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
@@ -6766,6 +6850,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 	uint32		newlen;
 	Size		freespace;
 	BlockNumber blkno;
+	CommandId	cid = FirstCommandId;
 
 	blkno = ItemPointerGetBlockNumber(&(xlrec->target.tid));
 
@@ -6818,6 +6903,17 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 		elog(PANIC, "heap_insert_redo: invalid max offset number");
 
 	newlen = record->xl_len - SizeOfHeapInsert - SizeOfHeapHeader;
+
+	/* CommandId is stored at the end of record data */
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_CID)
+	{
+		memcpy(&cid,
+		       (char *) xlrec + record->xl_len - sizeof(CommandId),
+		       sizeof(CommandId));
+		/* adjust length of the tuple by the space used for the cid */
+		newlen -= sizeof(CommandId);
+	}
+
 	Assert(newlen <= MaxHeapTupleSize);
 	memcpy((char *) &xlhdr,
 		   (char *) xlrec + SizeOfHeapInsert,
@@ -6833,7 +6929,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 	htup->t_infomask = xlhdr.t_infomask;
 	htup->t_hoff = xlhdr.t_hoff;
 	HeapTupleHeaderSetXmin(htup, record->xl_xid);
-	HeapTupleHeaderSetCmin(htup, FirstCommandId);
+	HeapTupleHeaderSetCmin(htup, cid);
 	htup->t_ctid = xlrec->target.tid;
 
 	offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
@@ -6884,6 +6980,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 	BlockNumber blkno;
 	int			i;
 	bool		isinit = (record->xl_info & XLOG_HEAP_INIT_PAGE) != 0;
+	CommandId	cid = FirstCommandId;
 
 	/*
 	 * Insertion doesn't overwrite MVCC data, so no conflict processing is
@@ -6925,6 +7022,12 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 		return;
 	}
 
+	/* CommandId is stored at the end of record data */
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_CID)
+		memcpy(&cid,
+		       (char *) xlrec + record->xl_len - sizeof(CommandId),
+		       sizeof(CommandId));
+
 	if (isinit)
 	{
 		buffer = XLogReadBuffer(xlrec->node, blkno, true);
@@ -6977,7 +7080,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 		htup->t_infomask = xlhdr->t_infomask;
 		htup->t_hoff = xlhdr->t_hoff;
 		HeapTupleHeaderSetXmin(htup, record->xl_xid);
-		HeapTupleHeaderSetCmin(htup, FirstCommandId);
+		HeapTupleHeaderSetCmin(htup, cid);
 		ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
 		ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
 
@@ -7033,6 +7136,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	int			hsize;
 	uint32		newlen;
 	Size		freespace;
+	CommandId	cid = FirstCommandId;
 
 	/*
 	 * The visibility map may need to be fixed even if the heap page is
@@ -7072,6 +7176,12 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 		goto newt;
 	}
 
+	/* CommandId is stored at the end of record data */
+	if (xlrec->flags & XLOG_HEAP_CONTAINS_CID)
+		memcpy(&cid,
+		       (char *) xlrec + record->xl_len - sizeof(CommandId),
+		       sizeof(CommandId));
+
 	/* Deal with old tuple version */
 
 	obuffer = XLogReadBuffer(xlrec->target.node,
@@ -7100,7 +7210,8 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 
 	htup = (HeapTupleHeader) PageGetItem(page, lp);
 
-	htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	htup->t_infomask &= ~HEAP_XMAX_BITS;
+	HeapTupleHeaderClearMoved(htup);
 	htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	if (hot_update)
 		HeapTupleHeaderSetHotUpdated(htup);
@@ -7109,7 +7220,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	fix_infomask_from_infobits(xlrec->old_infobits_set, &htup->t_infomask,
 							   &htup->t_infomask2);
 	HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
-	HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
+	HeapTupleHeaderSetCmax(htup, cid, false);
 	/* Set forward chain link in t_ctid */
 	htup->t_ctid = xlrec->newtid;
 
@@ -7217,7 +7328,8 @@ newsame:;
 	htup->t_hoff = xlhdr.header.t_hoff;
 
 	HeapTupleHeaderSetXmin(htup, record->xl_xid);
-	HeapTupleHeaderSetCmin(htup, FirstCommandId);
+	HeapTupleHeaderSetCmin(htup, cid);
+	HeapTupleHeaderSetCmax(htup, InvalidCommandId, false);
 	HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
 	/* Make sure there is no forward chain link in t_ctid */
 	htup->t_ctid = xlrec->newtid;
@@ -7477,9 +7589,6 @@ heap2_redo(XLogRecPtr lsn, XLogRecord *record)
 		case XLOG_HEAP2_LOCK_UPDATED:
 			heap_xlog_lock_updated(lsn, record);
 			break;
-		case XLOG_HEAP2_NEW_CID:
-			/* nothing to do on a real replay, only during logical decoding */
-			break;
 		default:
 			elog(PANIC, "heap2_redo: unknown op code %u", info);
 	}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 951894c..7a43030 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -342,6 +342,12 @@ rewrite_heap_tuple(RewriteState state,
 	new_tuple->t_data->t_infomask |=
 		old_tuple->t_data->t_infomask & HEAP_XACT_MASK;
 
+	if (HeapTupleHeaderHasWideCid(old_tuple->t_data) && HeapTupleHeaderHasWideCid(new_tuple->t_data))
+	{
+		HeapTupleHeaderSetCmin(new_tuple->t_data, HeapTupleHeaderGetCmin(old_tuple->t_data));
+		HeapTupleHeaderSetCmax(new_tuple->t_data, HeapTupleHeaderGetCmax(old_tuple->t_data), false);
+	}
+
 	/*
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * very-old xmin or xmax, so that future VACUUM effort can be saved.
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 6728469..20d329d 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -668,6 +668,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	hoff = offsetof(HeapTupleHeaderData, t_bits);
 	if (has_nulls)
 		hoff += BITMAPLEN(numAttrs);
+	if (HeapTupleHeaderHasWideCid(newtup->t_data))
+		hoff += sizeof(CommandId);
 	if (newtup->t_data->t_infomask & HEAP_HASOID)
 		hoff += sizeof(Oid);
 	hoff = MAXALIGN(hoff);
@@ -954,6 +956,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		new_header_len = offsetof(HeapTupleHeaderData, t_bits);
 		if (has_nulls)
 			new_header_len += BITMAPLEN(numAttrs);
+		if (HeapTupleHeaderHasWideCid(olddata))
+			new_header_len += sizeof(CommandId);
 		if (olddata->t_infomask & HEAP_HASOID)
 			new_header_len += sizeof(Oid);
 		new_header_len = MAXALIGN(new_header_len);
@@ -977,6 +981,12 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		memcpy(new_data, olddata, offsetof(HeapTupleHeaderData, t_bits));
 		HeapTupleHeaderSetNatts(new_data, numAttrs);
 		new_data->t_hoff = new_header_len;
+		if (HeapTupleHeaderHasWideCid(olddata))
+		{
+			new_data->t_infomask |= HEAP_HAS_WIDE_CID;
+			HeapTupleHeaderSetCmin(new_data, HeapTupleHeaderGetCmin(olddata));
+			HeapTupleHeaderSetCmax(new_data, HeapTupleHeaderGetCmax(olddata), false);
+		}
 		if (olddata->t_infomask & HEAP_HASOID)
 			HeapTupleHeaderSetOid(new_data, HeapTupleHeaderGetOid(olddata));
 
@@ -1067,6 +1077,12 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 	 * point of copying visibility info, just in case anybody looks at those
 	 * fields in a syscache entry.
 	 */
+	if (tupleDesc->tdhaswidecid && HeapTupleHeaderHasWideCid(tup->t_data))
+	{
+		new_tuple->t_data->t_infomask |= HEAP_HAS_WIDE_CID;
+		HeapTupleHeaderSetCmin(new_tuple->t_data, HeapTupleHeaderGetCmin(tup->t_data));
+		HeapTupleHeaderSetCmax(new_tuple->t_data, HeapTupleHeaderGetCmax(tup->t_data), false);
+	}
 	if (tupleDesc->tdhasoid)
 		HeapTupleSetOid(new_tuple, HeapTupleGetOid(tup));
 
@@ -1192,6 +1208,8 @@ toast_flatten_tuple_attribute(Datum value,
 	new_header_len = offsetof(HeapTupleHeaderData, t_bits);
 	if (has_nulls)
 		new_header_len += BITMAPLEN(numAttrs);
+	if (HeapTupleHeaderHasWideCid(olddata))
+		new_header_len += sizeof(CommandId);
 	if (olddata->t_infomask & HEAP_HASOID)
 		new_header_len += sizeof(Oid);
 	new_header_len = MAXALIGN(new_header_len);
@@ -1207,6 +1225,12 @@ toast_flatten_tuple_attribute(Datum value,
 	memcpy(new_data, olddata, offsetof(HeapTupleHeaderData, t_bits));
 	HeapTupleHeaderSetNatts(new_data, numAttrs);
 	new_data->t_hoff = new_header_len;
+	if (HeapTupleHeaderHasWideCid(olddata))
+	{
+		new_data->t_infomask |= HEAP_HAS_WIDE_CID;
+		HeapTupleHeaderSetCmin(new_data, HeapTupleHeaderGetCmin(olddata));
+		HeapTupleHeaderSetCmax(new_data, HeapTupleHeaderGetCmax(olddata), false);
+	}
 	if (olddata->t_infomask & HEAP_HASOID)
 		HeapTupleHeaderSetOid(new_data, HeapTupleHeaderGetOid(olddata));
 
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index c750fef..bc8b985 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -184,15 +184,6 @@ heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
 						 xlrec->infobits_set);
 		out_target(buf, &(xlrec->target));
 	}
-	else if (info == XLOG_HEAP2_NEW_CID)
-	{
-		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
-
-		appendStringInfo(buf, "new_cid: ");
-		out_target(buf, &(xlrec->target));
-		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
-						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
-	}
 	else
 		appendStringInfo(buf, "UNKNOWN");
 }
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index 7670b60..4c510ab 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -193,6 +193,13 @@ xact_desc(StringInfo buf, uint8 xl_info, char *rec)
 		appendStringInfo(buf, "xid assignment xtop %u: ", xlrec->xtop);
 		xact_desc_assignment(buf, xlrec);
 	}
+	else if (info == XLOG_XACT_CCI)
+	{
+		xl_xact_cci *xlrec = (xl_xact_cci *) rec;
+
+		appendStringInfo(buf, "catalog cci: %u, xtop: %u",
+		                 xlrec->command_id, xlrec->top_xid);
+	}
 	else
 		appendStringInfo(buf, "UNKNOWN");
 }
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 0942dbb..2281dd7 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -196,6 +196,7 @@ static TransactionState CurrentTransactionState = &TopTransactionStateData;
 static SubTransactionId currentSubTransactionId;
 static CommandId currentCommandId;
 static bool currentCommandIdUsed;
+static bool currentCommandModifiedCatalog;
 
 /*
  * xactStartTimestamp is the value of transaction_timestamp().
@@ -258,6 +259,7 @@ static void AtAbort_Memory(void);
 static void AtCleanup_Memory(void);
 static void AtAbort_ResourceOwner(void);
 static void AtCCI_LocalCache(void);
+static void AtCCI_Logical(void);
 static void AtCommit_Memory(void);
 static void AtStart_Cache(void);
 static void AtStart_Memory(void);
@@ -643,6 +645,12 @@ GetCurrentCommandId(bool used)
 	return currentCommandId;
 }
 
+void
+CommandModifiedCatalog(void)
+{
+	currentCommandModifiedCatalog = true;
+}
+
 /*
  *	GetCurrentTransactionStartTimestamp
  */
@@ -819,6 +827,12 @@ CommandCounterIncrement(void)
 		 * don't think a command that queued inval messages was read-only.)
 		 */
 		AtCCI_LocalCache();
+
+		/*
+		 * Tell logical decoding about potential DDL in this
+		 * command.
+		 */
+		AtCCI_Logical();
 	}
 }
 
@@ -1263,6 +1277,34 @@ AtCCI_LocalCache(void)
 }
 
 /*
+ *	AtCCI_Logical
+ */
+static void
+AtCCI_Logical(void)
+{
+	XLogRecData rdata;
+	xl_xact_cci	xlrec;
+
+	if (!currentCommandModifiedCatalog)
+		return;
+
+	if (!XLogLogicalInfoActive())
+		return;
+
+	currentCommandModifiedCatalog = false;
+
+	xlrec.top_xid = GetTopTransactionIdIfAny();
+	xlrec.command_id = currentCommandId;
+
+	rdata.data = (char *) (&xlrec);
+	rdata.len = sizeof(xlrec);
+	rdata.buffer = InvalidBuffer;
+	rdata.next = NULL;
+
+	(void) XLogInsert(RM_XACT_ID, XLOG_XACT_CCI, &rdata);
+}
+
+/*
  *	AtCommit_Memory
  */
 static void
@@ -1762,6 +1804,7 @@ StartTransaction(void)
 	currentSubTransactionId = TopSubTransactionId;
 	currentCommandId = FirstCommandId;
 	currentCommandIdUsed = false;
+	currentCommandModifiedCatalog = false;
 
 	/*
 	 * initialize reported xid accounting
@@ -4906,6 +4949,10 @@ xact_redo(XLogRecPtr lsn, XLogRecord *record)
 			ProcArrayApplyXidAssignment(xlrec->xtop,
 										xlrec->nsubxacts, xlrec->xsub);
 	}
+	else if (info == XLOG_XACT_CCI)
+	{
+		/* nothing to do during normal replay, only logical decoding needs this */
+	}
 	else
 		elog(PANIC, "xact_redo: unknown op code %u", info);
 }
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index f3b4409..c560f66 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -786,6 +786,13 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 	 */
 	oldTupDesc = RelationGetDescr(OldHeap);
 	newTupDesc = RelationGetDescr(NewHeap);
+
+	/*
+	 * tupdesc for temporary relation doesn't get the right value
+	 * assigned, fix temporarily
+	 */
+	newTupDesc->tdhaswidecid = oldTupDesc->tdhaswidecid;
+
 	Assert(newTupDesc->natts == oldTupDesc->natts);
 
 	/* Preallocate values/isnull arrays */
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 033b07d..10d61ee 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -282,6 +282,18 @@ DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 			 * as well.
 			 */
 			break;
+		case XLOG_XACT_CCI:
+			{
+				xl_xact_cci *xlrec;
+				xlrec = (xl_xact_cci *) buf->record_data;
+
+				/* boring */
+				if (TransactionIdIsValid(xlrec->top_xid))
+					SnapBuildProcessNewCid(builder, r->xl_xid, buf->origptr, xlrec);
+
+				break;
+			}
+			break;
 		default:
 			break;
 	}
@@ -338,15 +350,6 @@ DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 			if (SnapBuildProcessChange(builder, xid, buf->origptr))
 				DecodeMultiInsert(ctx, buf);
 			break;
-		case XLOG_HEAP2_NEW_CID:
-			{
-				xl_heap_new_cid *xlrec;
-				xlrec = (xl_heap_new_cid *) buf->record_data;
-				SnapBuildProcessNewCid(builder, xid, buf->origptr, xlrec);
-
-				break;
-			}
-
 		/*
 		 * Everything else here is just low level physical stuff we're
 		 * not interested in.
@@ -529,12 +532,16 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
 	{
-		Assert(r->xl_len > (SizeOfHeapInsert + SizeOfHeapHeader));
+		Size len = r->xl_len - SizeOfHeapInsert;
+
+		/* adjust length of the tuple by the space used for the cid */
+		if (xlrec->flags & XLOG_HEAP_CONTAINS_CID)
+			len -= sizeof(CommandId);
 
 		change->tp.newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
 
 		DecodeXLogTuple((char *) xlrec + SizeOfHeapInsert,
-						r->xl_len - SizeOfHeapInsert,
+						len,
 						change->tp.newtuple);
 	}
 
@@ -571,10 +578,12 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
 	{
-		Assert(r->xl_len > (SizeOfHeapUpdate + SizeOfHeapHeaderLen));
-
 		change->tp.newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
 
+		/*
+		 * no need to adjust length as in DecodeInsert here,
+		 * xl_heap_header_len contains the length explicitly.
+		 */
 		DecodeXLogTuple(data,
 						xlhdr->t_len + SizeOfHeapHeader,
 						change->tp.newtuple);
@@ -625,12 +634,16 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	/* old primary key stored */
 	if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY)
 	{
-		Assert(r->xl_len > (SizeOfHeapDelete + SizeOfHeapHeader));
+		Size len = r->xl_len - SizeOfHeapDelete;
 
 		change->tp.oldtuple = ReorderBufferGetTupleBuf(ctx->reorder);
 
+		/* adjust length of the tuple by the space used for the cid */
+		if (xlrec->flags & XLOG_HEAP_CONTAINS_CID)
+			len -= sizeof(CommandId);
+
 		DecodeXLogTuple((char *) xlrec + SizeOfHeapDelete,
-						r->xl_len - SizeOfHeapDelete,
+						len,
 						change->tp.oldtuple);
 	}
 	ReorderBufferQueueChange(ctx->reorder, r->xl_xid, buf->origptr, change);
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index b613d3d..0142742 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -92,8 +92,7 @@ typedef enum
 	REORDER_BUFFER_CHANGE_INTERNAL_UPDATE,
 	REORDER_BUFFER_CHANGE_INTERNAL_DELETE,
 	REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
-	REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
-	REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID
+	REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID
 } ReorderBufferChangeTypeInternal;
 
 /* entry for a hash table we use to map from xid to our transaction state */
@@ -103,21 +102,6 @@ typedef struct ReorderBufferTXNByIdEnt
 	ReorderBufferTXN *txn;
 } ReorderBufferTXNByIdEnt;
 
-/* data structures for (relfilenode, ctid) => (cmin, cmax) mapping */
-typedef struct ReorderBufferTupleCidKey
-{
-	RelFileNode relnode;
-	ItemPointerData tid;
-} ReorderBufferTupleCidKey;
-
-typedef struct ReorderBufferTupleCidEnt
-{
-	ReorderBufferTupleCidKey key;
-	CommandId	cmin;
-	CommandId	cmax;
-	CommandId	combocid;		/* just for debugging */
-} ReorderBufferTupleCidEnt;
-
 /* k-way in-order change iteration support structures */
 typedef struct ReorderBufferIterTXNEntry
 {
@@ -316,7 +300,6 @@ ReorderBufferGetTXN(ReorderBuffer *rb)
 	memset(txn, 0, sizeof(ReorderBufferTXN));
 
 	dlist_init(&txn->changes);
-	dlist_init(&txn->tuplecids);
 	dlist_init(&txn->subtxns);
 
 	return txn;
@@ -336,12 +319,6 @@ ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
 		rb->by_txn_last_txn = NULL;
 	}
 
-	if (txn->tuplecid_hash != NULL)
-	{
-		hash_destroy(txn->tuplecid_hash);
-		txn->tuplecid_hash = NULL;
-	}
-
 	if (txn->invalidations)
 	{
 		pfree(txn->invalidations);
@@ -419,8 +396,6 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
 			break;
 		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
 			break;
-		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
-			break;
 	}
 
 	if (rb->nr_cached_changes < max_cached_changes)
@@ -1050,19 +1025,6 @@ ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
 		ReorderBufferReturnChange(rb, change);
 	}
 
-	/*
-	 * cleanup the tuplecids we stored timetravel access. They are always
-	 * stored in the toplevel transaction.
-	 */
-	dlist_foreach_modify(iter, &txn->tuplecids)
-	{
-		ReorderBufferChange *change;
-
-		change = dlist_container(ReorderBufferChange, node, iter.cur);
-		Assert(change->action_internal == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
-		ReorderBufferReturnChange(rb, change);
-	}
-
 	if (txn->base_snapshot != NULL)
 	{
 		SnapBuildSnapDecRefcount(txn->base_snapshot);
@@ -1097,80 +1059,6 @@ ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
 }
 
 /*
- * Build a hash with a (relfilenode, ctid) -> (cmin, cmax) mapping for use by
- * tqual.c's HeapTupleSatisfiesMVCCDuringDecoding.
- */
-static void
-ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
-{
-	dlist_iter	iter;
-	HASHCTL		hash_ctl;
-
-	if (!txn->has_catalog_changes || dlist_is_empty(&txn->tuplecids))
-		return;
-
-	memset(&hash_ctl, 0, sizeof(hash_ctl));
-
-	hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
-	hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
-	hash_ctl.hash = tag_hash;
-	hash_ctl.hcxt = rb->context;
-
-	/*
-	 * create the hash with the exact number of to-be-stored tuplecids from
-	 * the start
-	 */
-	txn->tuplecid_hash =
-		hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
-					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
-
-	dlist_foreach(iter, &txn->tuplecids)
-	{
-		ReorderBufferTupleCidKey key;
-		ReorderBufferTupleCidEnt *ent;
-		bool		found;
-		ReorderBufferChange *change;
-
-		change = dlist_container(ReorderBufferChange, node, iter.cur);
-
-		Assert(change->action_internal == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
-
-		/* be careful about padding */
-		memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
-
-		key.relnode = change->tuplecid.node;
-
-		ItemPointerCopy(&change->tuplecid.tid,
-						&key.tid);
-
-		ent = (ReorderBufferTupleCidEnt *)
-			hash_search(txn->tuplecid_hash,
-						(void *) &key,
-						HASH_ENTER | HASH_FIND,
-						&found);
-		if (!found)
-		{
-			ent->cmin = change->tuplecid.cmin;
-			ent->cmax = change->tuplecid.cmax;
-			ent->combocid = change->tuplecid.combocid;
-		}
-		else
-		{
-			Assert(ent->cmin == change->tuplecid.cmin);
-			Assert(ent->cmax == InvalidCommandId ||
-				   ent->cmax == change->tuplecid.cmax);
-
-			/*
-			 * if the tuple got valid in this transaction and now got deleted
-			 * we already have a valid cmin stored. The cmax will be
-			 * InvalidCommandId though.
-			 */
-			ent->cmax = change->tuplecid.cmax;
-		}
-	}
-}
-
-/*
  * Copy a provided snapshot so we can modify it privately. This is needed so
  * that catalog modifying transactions can look into intermediate catalog
  * states.
@@ -1278,10 +1166,8 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, XLogRecPtr commit_lsn,
 
 	snapshot_now = txn->base_snapshot;
 
-	ReorderBufferBuildTupleCidHash(rb, txn);
-
 	/* setup initial snapshot */
-	SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+	SetupDecodingSnapshots(snapshot_now);
 
 	PG_TRY();
 	{
@@ -1391,7 +1277,7 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, XLogRecPtr commit_lsn,
 
 
 					/* and start with the new one */
-					SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+					SetupDecodingSnapshots(snapshot_now);
 					break;
 
 				case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
@@ -1409,7 +1295,7 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, XLogRecPtr commit_lsn,
 						snapshot_now->curcid = command_id;
 
 						RevertFromDecodingSnapshots();
-						SetupDecodingSnapshots(snapshot_now, txn->tuplecid_hash);
+						SetupDecodingSnapshots(snapshot_now);
 					}
 
 					/*
@@ -1419,10 +1305,6 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, XLogRecPtr commit_lsn,
 					ReorderBufferExecuteInvalidations(rb, txn);
 
 					break;
-
-				case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
-					elog(ERROR, "tuplecid value in normal queue");
-					break;
 			}
 		}
 
@@ -1568,33 +1450,6 @@ ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
 	ReorderBufferQueueChange(rb, xid, lsn, change);
 }
 
-
-/*
- * Add new (relfilenode, tid) -> (cmin, cmax) mappings.
- */
-void
-ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
-							 XLogRecPtr lsn, RelFileNode node,
-							 ItemPointerData tid, CommandId cmin,
-							 CommandId cmax, CommandId combocid)
-{
-	ReorderBufferChange *change = ReorderBufferGetChange(rb);
-	ReorderBufferTXN *txn;
-
-	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
-
-	change->tuplecid.node = node;
-	change->tuplecid.tid = tid;
-	change->tuplecid.cmin = cmin;
-	change->tuplecid.cmax = cmax;
-	change->tuplecid.combocid = combocid;
-	change->lsn = lsn;
-	change->action_internal = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
-
-	dlist_push_tail(&txn->tuplecids, &change->node);
-	txn->ntuplecids++;
-}
-
 /*
  * Setup the invalidation of the toplevel transaction.
  *
@@ -1803,9 +1658,6 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
 		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
 			/* ReorderBufferChange contains everything important */
 			break;
-		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
-			/* ReorderBufferChange contains everything important */
-			break;
 	}
 
 	ondisk->size = sz;
@@ -2093,7 +1945,6 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
 			}
 			/* nothing needs to be done */
 		case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-		case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
 			break;
 	}
 
@@ -2468,73 +2319,3 @@ ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
 	hash_destroy(txn->toast_hash);
 	txn->toast_hash = NULL;
 }
-
-
-/*
- * Visibility support routines
- */
-
-/*-------------------------------------------------------------------------
- * Lookup actual cmin/cmax values during timetravel access. We can't always
- * rely on stored cmin/cmax values because of two scenarios:
- *
- * * A tuple got changed multiple times during a single transaction and thus
- *	 has got a combocid. Combocid's are only valid for the duration of a single
- *	 transaction.
- * * A tuple with a cmin but no cmax (and thus no combocid) got deleted/updated
- *	 in another transaction than the one which created it which we are looking
- *	 at right now. As only one of cmin, cmax or combocid is actually stored in
- *	 the heap we don't have access to the the value we need anymore.
- *
- * To resolve those problems we have a per-transaction hash of (cmin, cmax)
- * tuples keyed by (relfilenode, ctid) which contains the actual (cmin, cmax)
- * values. That also takes care of combocids by simply not caring about them at
- * all. As we have the real cmin/cmax values thats enough.
- *
- * As we only care about catalog tuples here the overhead of this hashtable
- * should be acceptable.
- * -------------------------------------------------------------------------
- */
-extern bool
-ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
-							  HeapTuple htup, Buffer buffer,
-							  CommandId *cmin, CommandId *cmax)
-{
-	ReorderBufferTupleCidKey key;
-	ReorderBufferTupleCidEnt *ent;
-	ForkNumber	forkno;
-	BlockNumber blockno;
-
-	/* be careful about padding */
-	memset(&key, 0, sizeof(key));
-
-	Assert(!BufferIsLocal(buffer));
-
-	/*
-	 * get relfilenode from the buffer, no convenient way to access it other
-	 * than that.
-	 */
-	BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
-
-	/* tuples can only be in the main fork */
-	Assert(forkno == MAIN_FORKNUM);
-	Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
-
-	ItemPointerCopy(&htup->t_self,
-					&key.tid);
-
-	ent = (ReorderBufferTupleCidEnt *)
-		hash_search(tuplecid_data,
-					(void *) &key,
-					HASH_FIND,
-					NULL);
-
-	if (ent == NULL)
-		return false;
-
-	if (cmin)
-		*cmin = ent->cmin;
-	if (cmax)
-		*cmax = ent->cmax;
-	return true;
-}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 6c44614..b9ead6b 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -623,46 +623,27 @@ SnapBuildProcessChange(SnapBuild *builder, TransactionId xid, XLogRecPtr lsn)
 }
 
 /*
- * Do CommandId/ComboCid handling after reading a xl_heap_new_cid record. This
- * implies that a transaction has done some for of write to system catalogs.
+ * Do CommandId handling after reading a xl_xactcco record. This
+ * implies that a transaction has done some for of write to system
+ * catalogs.
  */
 void
 SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
-					   XLogRecPtr lsn, xl_heap_new_cid *xlrec)
+					   XLogRecPtr lsn, xl_xact_cci *xlrec)
 {
-	CommandId	cid;
 
 	/*
-	 * we only log new_cid's if a catalog tuple was modified, so mark
-	 * the transaction as containing catalog modifications
+	 * we only log CCIs if a catalog tuple was modified, so mark the
+	 * transaction as containing catalog modifications
 	 */
-	ReorderBufferXidSetCatalogChanges(builder->reorder, xid,lsn);
-
-	ReorderBufferAddNewTupleCids(builder->reorder, xlrec->top_xid, lsn,
-								 xlrec->target.node, xlrec->target.tid,
-								 xlrec->cmin, xlrec->cmax,
-								 xlrec->combocid);
-
-	/* figure out new command id */
-	if (xlrec->cmin != InvalidCommandId &&
-		xlrec->cmax != InvalidCommandId)
-		cid = Max(xlrec->cmin, xlrec->cmax);
-	else if (xlrec->cmax != InvalidCommandId)
-		cid = xlrec->cmax;
-	else if (xlrec->cmin != InvalidCommandId)
-		cid = xlrec->cmin;
-	else
-	{
-		cid = InvalidCommandId;		/* silence compiler */
-		elog(ERROR, "broken arrow, no cid?");
-	}
+	ReorderBufferXidSetCatalogChanges(builder->reorder, xid, lsn);
 
 	/*
-	 * FIXME: potential race condition here: if multiple snapshots were running
-	 * & generating changes in the same transaction on the source side this
-	 * could be problematic. But this cannot happen for system catalogs, right?
+	 * Add note to the toplevel TX that we should use a new CommandId
+	 * to decode henceforth.
 	 */
-	ReorderBufferAddNewCommandId(builder->reorder, xid, lsn, cid + 1);
+	ReorderBufferAddNewCommandId(builder->reorder, xlrec->top_xid,
+	                             lsn, xlrec->command_id);
 }
 
 /*
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 015970a..9125616 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -1039,18 +1039,25 @@ CacheInvalidateHeapTuple(Relation relation,
 		return;
 
 	/*
-	 * We only need to worry about invalidation for tuples that are in system
-	 * relations; user-relation tuples are never in catcaches and can't affect
-	 * the relcache either.
+	 * TOAST tuples can be ignored here. Note that TOAST tables are
+	 * considered system relations so they are not filtered by the
+	 * below test for system relations.
 	 */
-	if (!IsSystemRelation(relation))
+	if (IsToastRelation(relation))
 		return;
 
 	/*
-	 * TOAST tuples can likewise be ignored here. Note that TOAST tables are
-	 * considered system relations so they are not filtered by the above test.
+	 * Remember whether our transaction has modified the catalog.
 	 */
-	if (IsToastRelation(relation))
+	if (IsSystemRelation(relation) || RelationIsUsedAsCatalogTable(relation))
+		CommandModifiedCatalog();
+
+	/*
+	 * We only need to worry about invalidation for tuples that are in system
+	 * relations; user-relation tuples are never in catcaches and can't affect
+	 * the relcache either.
+	 */
+	if (!IsSystemRelation(relation))
 		return;
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 50c2a7b..f73d51c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -455,6 +455,13 @@ RelationBuildTupleDesc(Relation relation)
 	relation->rd_att->tdtypmod = -1;	/* unnecessary, but... */
 	relation->rd_att->tdhasoid = relation->rd_rel->relhasoids;
 
+	/*
+	 * Updated for user defined tables in RelationBuildDesc, we
+	 * haven't parsed reloptions yet, so we cannot do it here.
+	 */
+	relation->rd_att->tdhaswidecid =
+		 XLogLogicalInfoActive() && IsSystemRelation(relation);
+
 	constr = (TupleConstr *) MemoryContextAlloc(CacheMemoryContext,
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
@@ -942,6 +949,13 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	RelationParseRelOptions(relation, pg_class_tuple);
 
 	/*
+	 * Now we have parsed reloptions, can check whether we are a
+	 * catalog table.
+	 */
+	if (RelationIsUsedAsCatalogTable(relation))
+		relation->rd_att->tdhaswidecid = true;
+
+	/*
 	 * initialize the relation lock manager information
 	 */
 	RelationInitLockInfo(relation);		/* see lmgr.c */
@@ -1502,6 +1516,9 @@ formrdesc(const char *relationName, Oid relationReltype,
 	relation->rd_att->tdtypeid = relationReltype;
 	relation->rd_att->tdtypmod = -1;	/* unnecessary, but... */
 
+	/* has to be a system relation, otherwise formrdesc isn't used */
+	relation->rd_att->tdhaswidecid = XLogLogicalInfoActive();
+
 	/*
 	 * initialize tuple desc info
 	 */
@@ -3168,6 +3185,10 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/* only system relations need to be fixed here */
+		if (XLogLogicalInfoActive() && IsSystemRelation(relation))
+			relation->rd_att->tdhaswidecid = true;
+
 		/*
 		 * Fix data that isn't saved in relcache cache file.
 		 *
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index 923355d..1575e96 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -104,27 +104,52 @@ HeapTupleHeaderGetCmin(HeapTupleHeader tup)
 {
 	CommandId	cid = HeapTupleHeaderGetRawCommandId(tup);
 
-	Assert(!(tup->t_infomask & HEAP_MOVED));
-	Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
+	Assert(!HeapTupleHeaderHasMoved(tup));
 
-	if (tup->t_infomask & HEAP_COMBOCID)
+	if (HeapTupleHeaderHasWideCid(tup))
+	{
+		return cid;
+	}
+	else if (tup->t_infomask & HEAP_COMBOCID)
+	{
+		Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
 		return GetRealCmin(cid);
+	}
 	else
+	{
+		Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
 		return cid;
+	}
 }
 
 CommandId
 HeapTupleHeaderGetCmax(HeapTupleHeader tup)
 {
-	CommandId	cid = HeapTupleHeaderGetRawCommandId(tup);
+	Assert(!HeapTupleHeaderHasMoved(tup));
 
-	Assert(!(tup->t_infomask & HEAP_MOVED));
-	Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
+	if (HeapTupleHeaderHasWideCid(tup))
+	{
+		if (tup->t_infomask & HEAP_HASOID)
+			return  *(CommandId *) ((char *)tup +
+								tup->t_hoff -
+								sizeof(Oid) -
+								sizeof(CommandId));
+		else
+			return *(CommandId *) ((char *)tup +
+								tup->t_hoff -
+								sizeof(Oid));
 
-	if (tup->t_infomask & HEAP_COMBOCID)
-		return GetRealCmax(cid);
+	}
+	else if (tup->t_infomask & HEAP_COMBOCID)
+	{
+		Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
+		return GetRealCmax(HeapTupleHeaderGetRawCommandId(tup));
+	}
 	else
-		return cid;
+	{
+		Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
+		return HeapTupleHeaderGetRawCommandId(tup);
+	}
 }
 
 /*
@@ -151,7 +176,11 @@ HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
 	 * Test for HEAP_XMIN_COMMITTED first, because it's cheaper than a
 	 * TransactionIdIsCurrentTransactionId call.
 	 */
-	if (!(tup->t_infomask & HEAP_XMIN_COMMITTED) &&
+	if (HeapTupleHeaderHasWideCid(tup))
+	{
+		*iscombo = false;
+	}
+	else if (!(tup->t_infomask & HEAP_XMIN_COMMITTED) &&
 		TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)))
 	{
 		CommandId	cmin = HeapTupleHeaderGetCmin(tup);
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index bfc9bd7..ce70760 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -73,8 +73,6 @@ SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
 SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
 
 static Snapshot TimetravelSnapshot;
-/* (table, ctid) => (cmin, cmax) mapping during timetravel */
-static HTAB *tuplecid_data = NULL;
 static int timetravel_suspended = 0;
 
 
@@ -182,7 +180,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderHasMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -201,7 +199,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderHasMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -368,7 +366,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderHasMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -387,7 +385,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderHasMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -453,7 +451,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
 			return HeapTupleInvisible;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderHasMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -472,7 +470,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderHasMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -681,7 +679,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderHasMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -700,7 +698,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderHasMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -871,7 +869,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderHasMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -890,7 +888,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderHasMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -1073,7 +1071,7 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 		if (tuple->t_infomask & HEAP_XMIN_INVALID)
 			return HEAPTUPLE_DEAD;
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		else if (HeapTupleHeaderHasMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -1091,7 +1089,7 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 						InvalidTransactionId);
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderHasMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -1545,28 +1543,7 @@ HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup, Snapshot snapshot,
     /* check if its one of our txids, toplevel is also in there */
 	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
 	{
-		CommandId cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId cmax = InvalidCommandId;
-
-		/*
-		 * If another transaction deleted this tuple or if cmin/cmax is stored
-		 * in a combocid we need to to lookup the actual values externally. We
-		 * need to do so in the deleted case because the deletion will have
-		 * overwritten the cmin value when setting cmax (c.f. combocid.c).
-		 */
-		if ((!(tuple->t_infomask & HEAP_XMAX_INVALID) &&
-			 !TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt)) ||
-			tuple->t_infomask & HEAP_COMBOCID
-			)
-		{
-			bool resolved;
-
-			resolved = ResolveCminCmaxDuringDecoding(tuplecid_data, htup,
-													 buffer, &cmin, &cmax);
-
-			if (!resolved)
-				elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-		}
+		CommandId cmin = HeapTupleHeaderGetCmin(tuple);
 
 		Assert(cmin != InvalidCommandId);
 
@@ -1622,21 +1599,16 @@ HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup, Snapshot snapshot,
     /* check if its one of our txids, toplevel is also in there */
 	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
 	{
-		CommandId cmin;
-		CommandId cmax = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId cmax;
 
-		/* Lookup actual cmin/cmax values */
-		if (tuple->t_infomask & HEAP_COMBOCID)
+		if (HeapTupleHeaderHasWideCid(tuple))
+			cmax = HeapTupleHeaderGetCmax(tuple);
+		else
 		{
-			bool resolved;
-
-			resolved = ResolveCminCmaxDuringDecoding(tuplecid_data, htup,
-													 buffer, &cmin, &cmax);
-
-			if (!resolved)
-				elog(ERROR, "could not resolve combocid to cmax");
+			Assert(!TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt));
+			Assert(TransactionIdPrecedes(xmin, snapshot->xmin));
+			cmax = HeapTupleHeaderGetRawCommandId(tuple);
 		}
-
 		Assert(cmax != InvalidCommandId);
 
 		if (cmax >= snapshot->curcid)
@@ -1675,7 +1647,7 @@ HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup, Snapshot snapshot,
  * Needed for after-the-fact WAL decoding.
  */
 void
-SetupDecodingSnapshots(Snapshot timetravel_snapshot, HTAB *tuplecids)
+SetupDecodingSnapshots(Snapshot timetravel_snapshot)
 {
 	/* prevent recursively setting up decoding snapshots */
 	Assert(CatalogSnapshotData.satisfies != RedirectSatisfiesMVCC);
@@ -1690,9 +1662,6 @@ SetupDecodingSnapshots(Snapshot timetravel_snapshot, HTAB *tuplecids)
 	/* setup the timetravel snapshot */
 	TimetravelSnapshot = timetravel_snapshot;
 
-	/* setup (cmin, cmax) lookup hash */
-	tuplecid_data = tuplecids;
-
 	timetravel_suspended = 0;
 }
 
@@ -1706,7 +1675,6 @@ RevertFromDecodingSnapshots(void)
 	Assert(timetravel_suspended == 0);
 
 	TimetravelSnapshot = NULL;
-	tuplecid_data = NULL;
 
 	/* rally to restore sanity and/or boredom */
 	CatalogSnapshotData.satisfies = HeapTupleSatisfiesMVCC;
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index ba4bc3d..a76d63c 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -55,7 +55,6 @@
 #define XLOG_HEAP2_VISIBLE		0x40
 #define XLOG_HEAP2_MULTI_INSERT 0x50
 #define XLOG_HEAP2_LOCK_UPDATED 0x60
-#define XLOG_HEAP2_NEW_CID		0x70
 
 /*
  * xl_heap_* ->flag values
@@ -67,6 +66,7 @@
 #define XLOG_HEAP_CONTAINS_OLD_TUPLE		(1<<2)
 #define XLOG_HEAP_CONTAINS_OLD_KEY			(1<<3)
 #define XLOG_HEAP_CONTAINS_NEW_TUPLE		(1<<4)
+#define XLOG_HEAP_CONTAINS_CID			(1<<5)
 
 /*
  * All what we need to find changed tuple
@@ -280,29 +280,6 @@ typedef struct xl_heap_visible
 
 #define SizeOfHeapVisible (offsetof(xl_heap_visible, cutoff_xid) + sizeof(TransactionId))
 
-typedef struct xl_heap_new_cid
-{
-	/*
-	 * store toplevel xid so we don't have to merge cids from different
-	 * transactions
-	 */
-	TransactionId top_xid;
-	CommandId cmin;
-	CommandId cmax;
-	/*
-	 * don't really need the combocid since we have the actual values
-	 * right in this struct, but the padding makes it free and its
-	 * useful for debugging.
-	 */
-	CommandId combocid;
-	/*
-	 * Store the relfilenode/ctid pair to facilitate lookups.
-	 */
-	xl_heaptid target;
-} xl_heap_new_cid;
-
-#define SizeOfHeapNewCid (offsetof(xl_heap_new_cid, target) + SizeOfHeapTid)
-
 extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 									   TransactionId *latestRemovedXid);
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 0a832e9..e46238c 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -186,8 +186,12 @@ struct HeapTupleHeaderData
 										 * VACUUM FULL; kept for binary
 										 * upgrade support */
 #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
+/*
+ * both bits set, use HeapTupleHeaderHasWideCid to test.
+ */
+#define HEAP_HAS_WIDE_CID		HEAP_MOVED
 
-#define HEAP_XACT_MASK			0xFFF0	/* visibility-related bits */
+#define HEAP_XACT_MASK			(0xFFF0 & ~HEAP_MOVED)	/* visibility-related bits */
 
 /*
  * A tuple is only locked (i.e. not updated by its Xmax) if the
@@ -295,25 +299,14 @@ struct HeapTupleHeaderData
 /* SetCmin is reasonably simple since we never need a combo CID */
 #define HeapTupleHeaderSetCmin(tup, cid) \
 do { \
-	Assert(!((tup)->t_infomask & HEAP_MOVED)); \
+	Assert(!HeapTupleHeaderHasMoved(tup)); \
 	(tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
 	(tup)->t_infomask &= ~HEAP_COMBOCID; \
 } while (0)
 
-/* SetCmax must be used after HeapTupleHeaderAdjustCmax; see combocid.c */
-#define HeapTupleHeaderSetCmax(tup, cid, iscombo) \
-do { \
-	Assert(!((tup)->t_infomask & HEAP_MOVED)); \
-	(tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
-	if (iscombo) \
-		(tup)->t_infomask |= HEAP_COMBOCID; \
-	else \
-		(tup)->t_infomask &= ~HEAP_COMBOCID; \
-} while (0)
-
 #define HeapTupleHeaderGetXvac(tup) \
 ( \
-	((tup)->t_infomask & HEAP_MOVED) ? \
+	HeapTupleHeaderHasMoved(tup) ? \
 		(tup)->t_choice.t_heap.t_field3.t_xvac \
 	: \
 		InvalidTransactionId \
@@ -425,6 +418,25 @@ do { \
 	(tup)->t_infomask2 = ((tup)->t_infomask2 & ~HEAP_NATTS_MASK) | (natts) \
 )
 
+#define HeapTupleHeaderHasWideCid(tup) \
+	(((tup)->t_infomask & HEAP_HAS_WIDE_CID) == HEAP_HAS_WIDE_CID)
+
+#define HeapTupleHeaderHasMoved(tup) \
+	(!HeapTupleHeaderHasWideCid(tup) && ((tup)->t_infomask & HEAP_MOVED))
+
+#define HeapTupleHeaderHasMovedOff(tup) \
+	(((tup)->t_infomask & HEAP_MOVED_OFF) && \
+	 !((tup)->t_infomask & HEAP_MOVED_IN))
+
+#define HeapTupleHeaderHasMovedIn(tup) \
+	(((tup)->t_infomask & HEAP_MOVED_IN) && \
+	 !((tup)->t_infomask & HEAP_MOVED_OFF))
+
+#define HeapTupleHeaderClearMoved(tup) \
+do { \
+	if (HeapTupleHeaderHasMoved(tup)) \
+		(tup)->t_infomask &= ~HEAP_MOVED; \
+} while (0)
 
 /*
  * BITMAPLEN(NATTS) -
@@ -707,4 +719,6 @@ extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup);
 extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
 extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
 
+extern void HeapTupleHeaderSetCmax(HeapTupleHeader tup, CommandId cid, bool iscombo);
+
 #endif   /* HTUP_DETAILS_H */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 49226b7..80eab88 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -77,6 +77,7 @@ typedef struct tupleDesc
 	Oid			tdtypeid;		/* composite type ID for tuple type */
 	int32		tdtypmod;		/* typmod for tuple type */
 	bool		tdhasoid;		/* tuple has oid attribute in its header */
+	bool		tdhaswidecid;	/* stores both cmin and cmax */
 	int			tdrefcount;		/* reference count, or -1 if not counting */
 }	*TupleDesc;
 
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 96502ce..cb7916d 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -111,6 +111,7 @@ typedef void (*SubXactCallback) (SubXactEvent event, SubTransactionId mySubid,
 #define XLOG_XACT_ABORT_PREPARED	0x40
 #define XLOG_XACT_ASSIGNMENT		0x50
 #define XLOG_XACT_COMMIT_COMPACT	0x60
+#define XLOG_XACT_CCI				0x70
 
 typedef struct xl_xact_assignment
 {
@@ -148,6 +149,12 @@ typedef struct xl_xact_commit
 
 #define MinSizeOfXactCommit offsetof(xl_xact_commit, xnodes)
 
+typedef struct xl_xact_cci
+{
+	TransactionId top_xid;
+	CommandId	command_id;
+} xl_xact_cci;
+
 /*
  * These flags are set in the xinfo fields of WAL commit records,
  * indicating a variety of additional actions that need to occur
@@ -225,6 +232,7 @@ extern void SetCurrentStatementStartTimestamp(void);
 extern int	GetCurrentTransactionNestLevel(void);
 extern bool TransactionIdIsCurrentTransactionId(TransactionId xid);
 extern void CommandCounterIncrement(void);
+extern void CommandModifiedCatalog(void);
 extern void ForceSyncCommit(void);
 extern void StartTransactionCommand(void);
 extern void CommitTransactionCommand(void);
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
index 39fd25b..84e282b 100644
--- a/src/include/replication/reorderbuffer.h
+++ b/src/include/replication/reorderbuffer.h
@@ -330,9 +330,6 @@ void		ReorderBufferSetBaseSnapshot(ReorderBuffer *, TransactionId, XLogRecPtr ls
 void		ReorderBufferAddSnapshot(ReorderBuffer *, TransactionId, XLogRecPtr lsn, struct SnapshotData *snap);
 void ReorderBufferAddNewCommandId(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
 							 CommandId cid);
-void ReorderBufferAddNewTupleCids(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
-							 RelFileNode node, ItemPointerData pt,
-						 CommandId cmin, CommandId cmax, CommandId combocid);
 void ReorderBufferAddInvalidations(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
 							  Size nmsgs, SharedInvalidationMessage *msgs);
 bool		ReorderBufferIsXidKnown(ReorderBuffer *, TransactionId xid);
diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h
index bd72973..a69d794 100644
--- a/src/include/replication/snapbuild.h
+++ b/src/include/replication/snapbuild.h
@@ -64,7 +64,7 @@ extern SnapBuildState SnapBuildCurrentState(SnapBuild *snapstate);
 extern bool SnapBuildXactNeedsSkip(SnapBuild *snapstate, XLogRecPtr ptr);
 
 /* don't want to include heapam_xlog.h */
-struct xl_heap_new_cid;
+struct xl_xact_cci;
 struct xl_running_xacts;
 
 extern void SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn,
@@ -76,7 +76,7 @@ extern void SnapBuildAbortTxn(SnapBuild *builder, XLogRecPtr lsn,
 extern bool SnapBuildProcessChange(SnapBuild *builder, TransactionId xid,
 								   XLogRecPtr lsn);
 extern void SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
-								   XLogRecPtr lsn, struct xl_heap_new_cid *cid);
+								   XLogRecPtr lsn, struct xl_xact_cci *cid);
 extern void SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn,
 										 struct xl_running_xacts *running);
 extern void SnapBuildSerializationPoint(SnapBuild *builder, XLogRecPtr lsn);
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index dfb190b..f1175fa 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -89,20 +89,11 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 
 /* Support for catalog timetravel */
-struct HTAB;
 extern bool HeapTupleSatisfiesMVCCDuringDecoding(HeapTuple htup,
                                                  Snapshot snapshot, Buffer buffer);
-extern void SetupDecodingSnapshots(Snapshot snapshot_now, struct HTAB *tuplecids);
+extern void SetupDecodingSnapshots(Snapshot snapshot_now);
 extern void RevertFromDecodingSnapshots(void);
 extern void SuspendDecodingSnapshots(void);
 extern void UnSuspendDecodingSnapshots(void);
 extern bool DecodingSnapshotsActive(void);
-
-/*
- * To avoid leaking to much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data, HeapTuple htup,
-										  Buffer buffer,
-										  CommandId *cmin, CommandId *cmax);
 #endif   /* TQUAL_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 648caa0..04a91ff 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2463,7 +2463,6 @@ xl_heap_insert
 xl_heap_lock
 xl_heap_lock_updated
 xl_heap_multi_insert
-xl_heap_new_cid
 xl_heap_newpage
 xl_heap_update
 xl_heap_visible
@@ -2486,6 +2485,7 @@ xl_tblspc_drop_rec
 xl_xact_abort
 xl_xact_abort_prepared
 xl_xact_assignment
+xl_xact_cci
 xl_xact_commit
 xl_xact_commit_compact
 xl_xact_commit_prepared
-- 
1.8.4.21.g992c386.dirty

#106Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#95)
Re: logical changeset generation v6.4

On 2013-10-18 20:50:58 +0200, Andres Freund wrote:

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)

Current draft is:
ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL|DEFAULT
ALTER TABLE ... REPLICA IDENTITY USING INDEX ...;

which leaves the door open for

ALTER TABLE ... REPLICA IDENTITY USING '(' column_name_list ')';

Does anybody have a strong feeling about requiring support for CREATE
TABLE for this?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#107Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#105)
Re: logical changeset generation v6.2

On Mon, Oct 21, 2013 at 1:52 PM, Andres Freund <andres@2ndquadrant.com> wrote:

In my opinion, (4) is too ugly to consider. I think that if we start
playing games like this, we're opening up the doors to lots of subtle
bugs and future architectural pain that will be with us for many, many
years to come. I believe we will bitterly regret any foray into this
area.

Hm. After looking at the required code - which you obviously cannot have
yet - it's not actually too bad. Will post a patch implementing it later.

I don't really buy the architectural argument since originally cmin/cmax
*were* both stored. It's not something we're just inventing now. We just
optimized that away but now have discovered that's not always a good
idea and thus don't always use the optimization.

The actual decoding code shrinks by about 200 lines using this logic
which is a hint that it's not a bad idea.

So, here's a preliminary patch to see how this would look. It'd be great
of you comment if you still think it's a completel no-go.

If it were for real, it'd need to be split and some minor things would
need to get adjusted, but I think it's easier to review it seing both
sides at once.

I think it's a complete no-go. Consider, e.g., the comment for
MaxTupleAttributeNumber, which you've blithely falsified. Even if you
update the comment and the value, I'm not inspired by the idea of
subtracting 32 from that number; even if it weren't already too small,
it would break pg_upgrade for any users who are on the edge. Things
aren't looking too good for anything that uses HeapTupleFields,
either; consider rewrite_heap_tuple().

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#108Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#107)
Re: logical changeset generation v6.2

On 2013-10-21 14:22:17 -0400, Robert Haas wrote:

On Mon, Oct 21, 2013 at 1:52 PM, Andres Freund <andres@2ndquadrant.com> wrote:

In my opinion, (4) is too ugly to consider. I think that if we start
playing games like this, we're opening up the doors to lots of subtle
bugs and future architectural pain that will be with us for many, many
years to come. I believe we will bitterly regret any foray into this
area.

Hm. After looking at the required code - which you obviously cannot have
yet - it's not actually too bad. Will post a patch implementing it later.

I don't really buy the architectural argument since originally cmin/cmax
*were* both stored. It's not something we're just inventing now. We just
optimized that away but now have discovered that's not always a good
idea and thus don't always use the optimization.

The actual decoding code shrinks by about 200 lines using this logic
which is a hint that it's not a bad idea.

So, here's a preliminary patch to see how this would look. It'd be great
of you comment if you still think it's a completel no-go.

If it were for real, it'd need to be split and some minor things would
need to get adjusted, but I think it's easier to review it seing both
sides at once.

I think it's a complete no-go. Consider, e.g., the comment for
MaxTupleAttributeNumber, which you've blithely falsified. Even if you
update the comment and the value, I'm not inspired by the idea of
subtracting 32 from that number; even if it weren't already too small,
it would break pg_upgrade for any users who are on the edge.

Well, we only need to support it for (user_)catalog tables. So
pg_upgrade isn't a problem. And I don't really see a problem restricting
the number of columns for such tables.

Things
aren't looking too good for anything that uses HeapTupleFields,
either; consider rewrite_heap_tuple().

Well, that currently works, by copying cmax. Since rewriting triggered
the change, I am pretty sure I've actually tested & hit that path...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#109Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#108)
Re: logical changeset generation v6.2

On Mon, Oct 21, 2013 at 2:27 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I think it's a complete no-go. Consider, e.g., the comment for
MaxTupleAttributeNumber, which you've blithely falsified. Even if you
update the comment and the value, I'm not inspired by the idea of
subtracting 32 from that number; even if it weren't already too small,
it would break pg_upgrade for any users who are on the edge.

Well, we only need to support it for (user_)catalog tables. So
pg_upgrade isn't a problem. And I don't really see a problem restricting
the number of columns for such tables.

Inch by inch, this patch set is trying to make catalog tables more and
more different from regular tables. I think that's a direction we're
going to regret. I can almost believe that no great harm will come to
us from giving the two different xmin horizons, as previously
discussed, though I'm still not 100% happy about that. Can't both
have something be a catalog table AND replicate it? Ick, but OK.

But changing the on disk format strikes me as crossing some sort of
bright line. That means that you're going to have two different code
paths in a lot of important cases, one for catalog tables and one for
non-catalog tables, and the one that's only taken for catalog tables
will be rather lightly traveled. And then you've got user catalog
tables, and the possibility that something that wasn't formerly a user
catalog table might become one, or visca versa. Even if you can flush
out every bug that exists today, this is a recipe for future bugs.

Things
aren't looking too good for anything that uses HeapTupleFields,
either; consider rewrite_heap_tuple().

Well, that currently works, by copying cmax. Since rewriting triggered
the change, I am pretty sure I've actually tested & hit that path...

No offense, but that's a laughable statement. If that path works,
it's mostly if not entirely by accident. You've fundamentally changed
the heap tuple format, and that code doesn't know about it, even
though it's deeply in bed with assumptions about the old format. I
think this is a pretty clear indication as to what's wrong with this
approach: a lot of stuff will not care, but the stuff that does care
will be hard to find, and future incremental modifications either to
that code or to the hidden data before the t_hoff pointer could break
stuff that formerly worked. We rejected early drafts of sepgsql RLS
cold because they changed the tuple format, and I don't see why we
shouldn't do exactly the same thing here.

But just suppose for a minute that we'd accepted that proposal and
then took this one, too. And let's suppose we also accept the next
proposal that, like that one and this one, jams something more into
the heap tuple header. At that point you'll have potentially as many
as 8 different maximum-number-of-attributes values for tuples, though
maybe not quite that many in practice if not all of those features can
be used together. The macros that are needed to extract the various
values from the heap tuple will be nightmarishly complex, and we'll
have eaten up all (or more than all) of our remaining bit-space in the
infomask. Maybe all of that sounds all right to you, but to me it
sounds like a big mess.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#110Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#109)
Re: logical changeset generation v6.2

On 2013-10-21 14:50:54 -0400, Robert Haas wrote:

On Mon, Oct 21, 2013 at 2:27 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I think it's a complete no-go. Consider, e.g., the comment for
MaxTupleAttributeNumber, which you've blithely falsified. Even if you
update the comment and the value, I'm not inspired by the idea of
subtracting 32 from that number; even if it weren't already too small,
it would break pg_upgrade for any users who are on the edge.

Well, we only need to support it for (user_)catalog tables. So
pg_upgrade isn't a problem. And I don't really see a problem restricting
the number of columns for such tables.

Inch by inch, this patch set is trying to make catalog tables more and
more different from regular tables. I think that's a direction we're
going to regret.

I can understand that.

Can't both have something be a catalog table AND replicate it? Ick,
but OK.

User catalog tables are replicated normally.

If we want we can replicate system catalog tables as well, ok, it's
about a days worth of work. I just don't see what the use case would
be and I am afraid it'd be used as a poor man's system table trigger.

But changing the on disk format strikes me as crossing some sort of
bright line. That means that you're going to have two different code
paths in a lot of important cases, one for catalog tables and one for
non-catalog tables, and the one that's only taken for catalog tables
will be rather lightly traveled.

But there really isn't that much difference in the code paths, is there?
If you look at it, minus the mechanical changes around bitmasks it
really mostly just a couple of added
HeapTupleHeaderSetCmin/HeapTupleHeaderSetCmax calls + wal logging the
current cid.

And then you've got user catalog tables, and the possibility that
something that wasn't formerly a user catalog table might become one,
or visca versa.

That's not really a problem - we only need cmin/cmax for catalog tables
when decoding DML that happened in the same transaction as DDL. The wide
cids could easily be removed when we freeze tuples or such.
Even something like:
BEGIN;
CREATE TABLE catalog_t(...);
INSERT INTO catalog_t ...;
UPDATE catalog_t ...
ALTER TABLE catalog_t SET (user_catalog_table = true);
INSERT INTO decoded_table (..);
INSERT INTO decoded_table (..);
UPDATE catalog_t SET ...;
INSERT INTO decoded_table (..);
COMMIT;
will work correctly.

Even if you can flush out every bug that exists today, this is a
recipe for future bugs.

I don't really forsee many new codepaths that care about the visibility
bits in tuple headers themselves. When has the last one of those been
added/changed? I'd bet it was 9.0 with the vacuum full/cluster merge,
and three lines to be added are surely the smallest problem of that.

Things
aren't looking too good for anything that uses HeapTupleFields,
either; consider rewrite_heap_tuple().

Well, that currently works, by copying cmax. Since rewriting triggered
the change, I am pretty sure I've actually tested & hit that path...

No offense, but that's a laughable statement. If that path works,
it's mostly if not entirely by accident. You've fundamentally changed
the heap tuple format, and that code doesn't know about it, even
though it's deeply in bed with assumptions about the old format.

Hm? rewrite_heap_tuple() grew code to handle that case, it's not an
accident that it works.

if (HeapTupleHeaderHasWideCid(old_tuple->t_data)
&& HeapTupleHeaderHasWideCid(new_tuple->t_data))
{
HeapTupleHeaderSetCmin(new_tuple->t_data,
HeapTupleHeaderGetCmin(old_tuple->t_data));
HeapTupleHeaderSetCmax(new_tuple->t_data,
HeapTupleHeaderGetCmax(old_tuple->t_data), false);
}

Note that code handling HeapTupleHeaders outside of heapam.c, tqual.c et
al. shouldn't need to care about the changes at all.
And all the code that needs to care *already* has special-cased code
around visibility. It's relatively easy to find by grepping for
HEAP_XACT_MASK. We've so far accepted that several places need to change
if we change the visibility rules. And I don't see how we easily could
get away from that.
Patches like e.g. lsn-ranges freezing will require *gobs* more
widespread changes. And have much higher chances of breaking stuff
imo. And it's still worthwile to do them.

I
think this is a pretty clear indication as to what's wrong with this
approach: a lot of stuff will not care, but the stuff that does care
will be hard to find, and future incremental modifications either to
that code or to the hidden data before the t_hoff pointer could break
stuff that formerly worked. We rejected early drafts of sepgsql RLS
cold because they changed the tuple format, and I don't see why we
shouldn't do exactly the same thing here.

Ok, I have a hard time to argue against that. I either skipped or forgot
that discussion.
I quickly searched and looked at the patch at:
497D7055.9090806@ak.jp.nec.com From a quick look that would have grown
much more invasive - and lots more places would have had to care. For
the proposed patch it really is only
heap_(insert|multi_insert|update|delete|rewrite_tuple) that need to
care. We could remove the tupledesc changes if we dont care about some
added malloc/memcpy'ing during DDL, the code to handle tuples without
that space is already there.

But just suppose for a minute that we'd accepted that proposal and
then took this one, too. And let's suppose we also accept the next
proposal that, like that one and this one, jams something more into
the heap tuple header. At that point you'll have potentially as many
as 8 different maximum-number-of-attributes values for tuples, though
maybe not quite that many in practice if not all of those features can
be used together.

Well, I can understand that argument. Unfortunately. But I still claim
that this is scaling back an optimization that we've previously applied
more widely.

Note also that we actually have quite some slop in
MaxTupleAttributeNumber and MaxHeapAttributeNumber (which is the one
that matters here). They e.g. haven't been adjusted when 8.3 decided
*not* to store both cmin and cmax anymore and they *already* had slop
before. So I am not convinced that we actually need to change our
current limits.

The macros that are needed to extract the various
values from the heap tuple will be nightmarishly complex, and we'll
have eaten up all (or more than all) of our remaining bit-space in the
infomask.

Why would the macros for extracting values from heap tuples need to
change? Those shouldn't be affected by what I proposed?

I think this approach would have lower maintenance overhead in
comparison to the previous solution of Handling CommandIds because it's
actually much simpler. But I am definitely ready to try other ideas.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#111Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#110)
Re: logical changeset generation v6.2

Hi,

On 2013-10-21 21:36:02 +0200, Andres Freund wrote:

I think this approach would have lower maintenance overhead in
comparison to the previous solution of Handling CommandIds because it's
actually much simpler.

Btw, I think the new approach would allow for *easier* modifications
about future code caring about visibility. Since the physical location
doesn't matter anymore it'd be much more friendly towards things like
an online compacting VACUUM and such.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#112Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#106)
1 attachment(s)
Re: logical changeset generation v6.4

On 2013-10-21 20:16:29 +0200, Andres Freund wrote:

On 2013-10-18 20:50:58 +0200, Andres Freund wrote:

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)

Current draft is:
ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL|DEFAULT
ALTER TABLE ... REPLICA IDENTITY USING INDEX ...;

which leaves the door open for

ALTER TABLE ... REPLICA IDENTITY USING '(' column_name_list ')';

Does anybody have a strong feeling about requiring support for CREATE
TABLE for this?

Attached is a patch ontop of master implementing this syntax. It's not
wired up to the changeset extraction patch yet as I am not sure whether
others agree about the storage.

pg_class grew a 'relreplident' char, storing:
* 'd' default
* 'n' nothing
* 'f' full
* 'i' index with indisreplident set, or default if index has been
dropped
pg_index grew a 'indisreplident' bool indicating it is set as the
replica identity for a replident = 'i' relation.

Both changes shouldn't change the width of the affected relations, they
should reuse existing padding.

Does somebody prefer a different storage?

pg_dump support, psql completion, regression tests and minimal docs
included.

I am not 100% clear what the best way to handle
ALTER TABLE some_table REPLICA IDENTITY USING INDEX someindex;
DROP INDEX someindex;
is. Currently that's supposed to have the same effect as having
relreplident = 'd'.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Add-command-to-set-the-REPLICA-IDENTITY-for-a-table.patchtext/x-patch; charset=us-asciiDownload
>From 7d228ca56ccd37bd17591e57a6d95fe428e5dc55 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 22 Oct 2013 15:44:15 +0200
Subject: [PATCH] Add command to set the REPLICA IDENTITY for a table

The chosen REPLICA IDENTITY is used to determine which columns should
be used by logical replication solution to identify rows in UPDATE and
DELETE.
---
 doc/src/sgml/catalogs.sgml                     |  24 +++
 doc/src/sgml/ref/alter_table.sgml              |  25 +++
 src/backend/catalog/heap.c                     |   1 +
 src/backend/catalog/index.c                    |   1 +
 src/backend/commands/tablecmds.c               | 243 +++++++++++++++++++++++++
 src/backend/nodes/copyfuncs.c                  |  14 ++
 src/backend/nodes/equalfuncs.c                 |  12 ++
 src/backend/parser/gram.y                      |  40 ++++
 src/backend/utils/cache/relcache.c             |   9 +
 src/bin/pg_dump/pg_dump.c                      | 143 +++++++++++++--
 src/bin/pg_dump/pg_dump.h                      |   2 +
 src/bin/psql/describe.c                        |  58 +++++-
 src/bin/psql/tab-complete.c                    |  31 +++-
 src/include/catalog/pg_class.h                 |  32 +++-
 src/include/catalog/pg_index.h                 |  16 +-
 src/include/nodes/nodes.h                      |   1 +
 src/include/nodes/parsenodes.h                 |   8 +
 src/test/regress/expected/replica_identity.out | 183 +++++++++++++++++++
 src/test/regress/parallel_schedule             |   2 +-
 src/test/regress/serial_schedule               |   1 +
 src/test/regress/sql/replica_identity.sql      |  79 ++++++++
 21 files changed, 884 insertions(+), 41 deletions(-)
 create mode 100644 src/test/regress/expected/replica_identity.out
 create mode 100644 src/test/regress/sql/replica_identity.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9af4697..fbd7e22 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1863,6 +1863,19 @@
      </row>
 
      <row>
+      <entry><structfield>relreplident</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Columns used to form <quote>replica identity</> for rows:
+       <literal>d</> = default selection,
+       <literal>n</> = nothing,
+       <literal>f</> = all columns
+       <literal>i</> = index with indisreplident set, or default
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -3658,6 +3671,17 @@
      </row>
 
      <row>
+      <entry><structfield>indisreplident</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       If true this index has been chosen as <quote>replica identity</>
+       using <command>ALTER TABLE ... REPLICA IDENTITY USING INDEX
+       ...</>
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>indkey</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2609d4a..c95e235 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,6 +69,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    REPLICA IDENTITY {DEFAULT | NOTHING | FULL | USING INDEX <replaceable class="PARAMETER">index_name</replaceable>}
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -580,6 +581,30 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>REPLICA IDENTITY</literal></term>
+    <listitem>
+     <para>
+      This form changes which columns logical replication solutions
+      use to identify the rows affected by <command>UPDATE</>
+      or <command>DELETE</>.
+     </para>
+     <para>
+      If set to NOTHING no part of the old row will be logged. If FULL
+      is specified the entire old row will be logged. <literal>USING
+      INDEX</> can choose an existing index in which case the columns
+      of that index will be used.
+     </para>
+     <para>
+      Indexes elegible as for <literal>REPLICA IDENTITY</> have to
+      be <literal>UNIQUE</>, may not be partial (have
+      a <literal>WHERE</> clause), may not be <literal>DEFERRABLE</>
+      and they have to only consist out existing columns set
+      to <literal>NOT NULL</>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 64ca312..a910f81 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -793,6 +793,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
+	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index e48e0b5..6b143cf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -614,6 +614,7 @@ UpdateIndexRelation(Oid indexoid,
 	/* we set isvalid and isready the same way */
 	values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid);
 	values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
+	values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
 	values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
 	values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
 	values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9008a79..d3ec7d6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -399,6 +399,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2809,6 +2810,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrigUser:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
+			case AT_ReplicaIdentity:
 				cmd_lockmode = ShareRowExclusiveLock;
 				break;
 
@@ -3140,6 +3142,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 				cmd->subtype = AT_ValidateConstraintRecurse;
 			pass = AT_PASS_MISC;
 			break;
+		case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
+			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+			pass = AT_PASS_MISC;
+			/* This command never recurses */
+			/* No command-specific prep needed */
+			break;
 		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
 		case AT_EnableAlwaysTrig:
 		case AT_EnableReplicaTrig:
@@ -3440,6 +3448,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_ReplicaIdentity:
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -10016,6 +10027,238 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * relation_mark_replica_identity: Update a table's replica identity
+ *
+ * Iff ri_type = _INDEX, indexOid has to be the Oid of a suitable
+ * index, otherwise InvalidOid has to be passed
+ */
+static void
+relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid, bool is_internal)
+{
+	Relation	pg_index;
+	Relation	pg_class;
+	HeapTuple	pg_class_tuple;
+	HeapTuple	pg_index_tuple;
+	Form_pg_class pg_class_form;
+	Form_pg_index pg_index_form;
+
+	ListCell   *index;
+
+	/* get the pg_class tuple first */
+	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+	pg_class_tuple = SearchSysCacheCopy1(RELOID,
+	                                     ObjectIdGetDatum(RelationGetRelid(rel)));
+	if (!HeapTupleIsValid(pg_class_tuple))
+		elog(ERROR, "cache lookup failed for relation \"%s\"",
+		     RelationGetRelationName(rel));
+
+	pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
+
+	/*
+	 * Only write if the catalog state differs from what we want. We
+	 * cannot return here though, the user might have chosen a
+	 * different index than last time.
+	 */
+	if (pg_class_form->relreplident != ri_type)
+	{
+		pg_class_form->relreplident = ri_type;
+		simple_heap_update(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
+		CatalogUpdateIndexes(pg_class, pg_class_tuple);
+	}
+
+	heap_close(pg_class, RowExclusiveLock);
+	heap_freetuple(pg_class_tuple);
+
+	/*
+	 * If the correct index is already marked as replica identity
+	 * provider, no need to do anything.
+	 */
+	if (OidIsValid(indexOid))
+	{
+		Assert(ri_type == REPLICA_IDENTITY_INDEX);
+
+		pg_index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
+		if (!HeapTupleIsValid(pg_index_tuple))
+			elog(ERROR, "cache lookup failed for index %u", indexOid);
+		pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
+
+		if (pg_index_form->indisreplident)
+		{
+			ReleaseSysCache(pg_index_tuple);
+			return;
+		}
+		ReleaseSysCache(pg_index_tuple);
+	}
+
+	/*
+	 * Check each index of the relation and set/clear the bit as needed.
+	 */
+	pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+
+	foreach(index, RelationGetIndexList(rel))
+	{
+		Oid			thisIndexOid = lfirst_oid(index);
+		bool		dirty = false;
+
+		pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
+										 ObjectIdGetDatum(thisIndexOid));
+		if (!HeapTupleIsValid(pg_index_tuple))
+			elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
+		pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
+
+		/*
+		 * Unset the bit if set.  We know it's wrong because we checked this
+		 * earlier.
+		 */
+		if (pg_index_form->indisreplident)
+		{
+			dirty = true;
+			pg_index_form->indisreplident = false;
+		}
+		else if (thisIndexOid == indexOid)
+		{
+			dirty = true;
+			pg_index_form->indisreplident = true;
+		}
+
+		if (dirty)
+		{
+			simple_heap_update(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
+			CatalogUpdateIndexes(pg_index, pg_index_tuple);
+			InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
+			                             InvalidOid, is_internal);
+		}
+		heap_freetuple(pg_index_tuple);
+	}
+
+	heap_close(pg_index, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> REPLICA IDENTITY ...
+ */
+static void
+ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode)
+{
+	Oid			indexOid;
+	Relation	indexRel;
+	int			key;
+
+	if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
+	{
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		return;
+	}
+	else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
+	{
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		return;
+	}
+	else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
+	{
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		return;
+	}
+	else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
+	{
+		/* fallthrough */;
+	}
+	else
+		elog(ERROR, "unexpected identity type %u", stmt->identity_type);
+
+
+	/* Check that the index exists */
+	indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
+	if (!OidIsValid(indexOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("index \"%s\" for table \"%s\" does not exist",
+						stmt->name, RelationGetRelationName(rel))));
+
+	indexRel = index_open(indexOid, ShareLock);
+
+	/*
+	 * Check that index is in fact an index on the relation we're
+	 * changing
+	 */
+	if (indexRel->rd_index == NULL ||
+		indexRel->rd_index->indrelid != RelationGetRelid(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not an index for table \"%s\"",
+						RelationGetRelationName(indexRel),
+						RelationGetRelationName(rel))));
+	/* Index AM must allow declaring indexes unique */
+	if (!indexRel->rd_am->amcanunique)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot use index \"%s\" as replica identity because access method does not support uniqueness",
+						RelationGetRelationName(indexRel))));
+	/* obviously cannot rely on non-unique indexes as a key */
+	if (!indexRel->rd_index->indisunique)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot use non-unique index \"%s\" as replica identity",
+						RelationGetRelationName(indexRel))));
+	/* deferred indexes are not guaranteed to be always unique */
+	if (!indexRel->rd_index->indimmediate)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot use non-immediate index \"%s\" as replica identity",
+						RelationGetRelationName(indexRel))));
+	/* expression indexes aren't supported */
+	if (RelationGetIndexExpressions(indexRel) != NIL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot use expression index \"%s\" as replica identity",
+						RelationGetRelationName(indexRel))));
+	/* predicate indexes aren't supported */
+	if (RelationGetIndexPredicate(indexRel) != NIL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot use partial index \"%s\" as replica identity",
+						RelationGetRelationName(indexRel))));
+	/* if the index isn't valid for querying, it might not be unique */
+	if (!IndexIsValid(indexRel->rd_index))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot cluster on invalid index \"%s\"",
+						RelationGetRelationName(indexRel))));
+
+	/* check index for nullable "columns" */
+	for (key = 0; key < indexRel->rd_index->indnatts; key++)
+	{
+		int16 attno = indexRel->rd_index->indkey.values[key];
+		Form_pg_attribute attr;
+
+		/* oid is always NOT NULL */
+		if (attno == ObjectIdAttributeNumber)
+			continue;
+
+		/* other internal column, huh? */
+		if (attno <= 0)
+			elog(ERROR, "internal column %u in unique index \"%s\"",
+			     attno, RelationGetRelationName(indexRel));
+
+		attr = rel->rd_att->attrs[attno - 1];
+		if (!attr->attnotnull)
+		{
+			ereport(ERROR,
+			        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+			         errmsg("cannot use index \"%s\" as replica identity, column \"%s\" is nullable",
+			                RelationGetRelationName(indexRel),
+			                NameStr(attr->attname))));
+		}
+	}
+
+	/* index survived all checks, we can mark it as identity provider */
+
+	relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
+
+	index_close(indexRel, NoLock);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65f3b98..1733da6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3270,6 +3270,17 @@ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from)
 	return newnode;
 }
 
+static ReplicaIdentityStmt *
+_copyReplicaIdentityStmt(const ReplicaIdentityStmt *from)
+{
+	ReplicaIdentityStmt *newnode = makeNode(ReplicaIdentityStmt);
+
+	COPY_SCALAR_FIELD(identity_type);
+	COPY_STRING_FIELD(name);
+
+	return newnode;
+}
+
 static CreateSeqStmt *
 _copyCreateSeqStmt(const CreateSeqStmt *from)
 {
@@ -4343,6 +4354,9 @@ copyObject(const void *from)
 		case T_RefreshMatViewStmt:
 			retval = _copyRefreshMatViewStmt(from);
 			break;
+		case T_ReplicaIdentityStmt:
+			retval = _copyReplicaIdentityStmt(from);
+			break;
 		case T_CreateSeqStmt:
 			retval = _copyCreateSeqStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4c9b05e..7b29812 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1538,6 +1538,15 @@ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt *
 }
 
 static bool
+_equalReplicaIdentityStmt(const ReplicaIdentityStmt *a, const ReplicaIdentityStmt *b)
+{
+	COMPARE_SCALAR_FIELD(identity_type);
+	COMPARE_STRING_FIELD(name);
+
+	return true;
+}
+
+static bool
 _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
 {
 	COMPARE_NODE_FIELD(sequence);
@@ -2813,6 +2822,9 @@ equal(const void *a, const void *b)
 		case T_RefreshMatViewStmt:
 			retval = _equalRefreshMatViewStmt(a, b);
 			break;
+		case T_ReplicaIdentityStmt:
+			retval = _equalReplicaIdentityStmt(a, b);
+			break;
 		case T_CreateSeqStmt:
 			retval = _equalCreateSeqStmt(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 363c603..8dc4b1c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -255,6 +255,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	add_drop opt_asc_desc opt_nulls_order
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
+	   replica_identity
 %type <list>	alter_table_cmds alter_type_cmds
 
 %type <dbehavior>	opt_drop_behavior
@@ -2178,6 +2179,14 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> REPLICA IDENTITY  */
+			| REPLICA IDENTITY_P replica_identity
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ReplicaIdentity;
+					n->def = $3;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2215,6 +2224,37 @@ alter_using:
 			| /* EMPTY */				{ $$ = NULL; }
 		;
 
+replica_identity:
+			NOTHING
+				{
+					ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+					n->identity_type = REPLICA_IDENTITY_NOTHING;
+					n->name = NULL;
+					$$ = (Node *) n;
+				}
+			| FULL
+				{
+					ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+					n->identity_type = REPLICA_IDENTITY_FULL;
+					n->name = NULL;
+					$$ = (Node *) n;
+				}
+			| DEFAULT
+				{
+					ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+					n->identity_type = REPLICA_IDENTITY_DEFAULT;
+					n->name = NULL;
+					$$ = (Node *) n;
+				}
+			| USING INDEX name
+				{
+					ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+					n->identity_type = REPLICA_IDENTITY_INDEX;
+					n->name = $3;
+					$$ = (Node *) n;
+				}
+;
+
 reloptions:
 			'(' reloption_list ')'					{ $$ = $2; }
 		;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index f73d51c..3219c29 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1496,6 +1496,8 @@ formrdesc(const char *relationName, Oid relationReltype,
 	/* ... and they're always populated, too */
 	relation->rd_rel->relispopulated = true;
 
+	relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+
 	relation->rd_rel->relpages = 0;
 	relation->rd_rel->reltuples = 0;
 	relation->rd_rel->relallvisible = 0;
@@ -2713,6 +2715,13 @@ RelationBuildLocalRelation(const char *relname,
 	else
 		rel->rd_rel->relispopulated = true;
 
+	/* system relations and non-table objects don't have one */
+	if (!IsSystemNamespace(relnamespace) &&
+	    (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
+	else
+		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+
 	/*
 	 * Insert relation physical and logical identifiers (OIDs) into the right
 	 * places.	For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 01c63b1..c61e383 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4221,6 +4221,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_toastfrozenxid;
 	int			i_relpersistence;
 	int			i_relispopulated;
+	int			i_relreplident;
 	int			i_owning_tab;
 	int			i_owning_col;
 	int			i_reltablespace;
@@ -4253,7 +4254,7 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90300)
+	if (fout->remoteVersion >= 90400)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -4268,7 +4269,46 @@ getTables(Archive *fout, int *numTables)
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
-						  "c.relpages, "
+						  "c.relreplident, c.relpages, "
+						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+						  "d.refobjid AS owning_tab, "
+						  "d.refobjsubid AS owning_col, "
+						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+						"array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, "
+						  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+							   "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "FROM pg_class c "
+						  "LEFT JOIN pg_depend d ON "
+						  "(c.relkind = '%c' AND "
+						  "d.classid = c.tableoid AND d.objid = c.oid AND "
+						  "d.objsubid = 0 AND "
+						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
+					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * Left join to pick up dependency info linking sequences to their
+		 * owning column, if any (note this dependency is AUTO as of 8.2)
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT c.tableoid, c.oid, c.relname, "
+						  "c.relacl, c.relkind, c.relnamespace, "
+						  "(%s c.relowner) AS rolname, "
+						  "c.relchecks, c.relhastriggers, "
+						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "'d' AS relreplident, c.relpages, "
 						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4307,7 +4347,7 @@ getTables(Archive *fout, int *numTables)
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, 't' as relispopulated, "
-						  "c.relpages, "
+						  "'d' AS relreplident, c.relpages, "
 						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4344,7 +4384,7 @@ getTables(Archive *fout, int *numTables)
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "c.relpages, "
+						  "'d' AS relreplident, c.relpages, "
 						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4380,7 +4420,7 @@ getTables(Archive *fout, int *numTables)
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "c.relpages, "
+						  "'d' AS relreplident, c.relpages, "
 						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4416,7 +4456,7 @@ getTables(Archive *fout, int *numTables)
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "c.relpages, "
+						  "'d' AS relreplident, c.relpages, "
 						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4453,7 +4493,7 @@ getTables(Archive *fout, int *numTables)
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "relpages, "
+						  "'d' AS relreplident, relpages, "
 						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4489,7 +4529,7 @@ getTables(Archive *fout, int *numTables)
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "relpages, "
+						  "'d' AS relreplident, relpages, "
 						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4521,7 +4561,7 @@ getTables(Archive *fout, int *numTables)
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "relpages, "
+						  "'d' AS relreplident, relpages, "
 						  "NULL AS reloftype, "
 						  "NULL::oid AS owning_tab, "
 						  "NULL::int4 AS owning_col, "
@@ -4548,7 +4588,7 @@ getTables(Archive *fout, int *numTables)
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "relpages, "
+						  "'d' AS relreplident, relpages, "
 						  "NULL AS reloftype, "
 						  "NULL::oid AS owning_tab, "
 						  "NULL::int4 AS owning_col, "
@@ -4585,7 +4625,7 @@ getTables(Archive *fout, int *numTables)
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
-						  "0 AS relpages, "
+						  "'d' AS relreplident, 0 AS relpages, "
 						  "NULL AS reloftype, "
 						  "NULL::oid AS owning_tab, "
 						  "NULL::int4 AS owning_col, "
@@ -4634,6 +4674,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastfrozenxid = PQfnumber(res, "tfrozenxid");
 	i_relpersistence = PQfnumber(res, "relpersistence");
 	i_relispopulated = PQfnumber(res, "relispopulated");
+	i_relreplident = PQfnumber(res, "relreplident");
 	i_relpages = PQfnumber(res, "relpages");
 	i_owning_tab = PQfnumber(res, "owning_tab");
 	i_owning_col = PQfnumber(res, "owning_col");
@@ -4678,6 +4719,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
+		tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
 		tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
 		tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
 		tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
@@ -4863,6 +4905,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_indnkeys,
 				i_indkey,
 				i_indisclustered,
+				i_indisreplident,
 				i_contype,
 				i_conname,
 				i_condeferrable,
@@ -4909,7 +4952,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		 * is not.
 		 */
 		resetPQExpBuffer(query);
-		if (fout->remoteVersion >= 90000)
+		if (fout->remoteVersion >= 90400)
 		{
 			/*
 			 * the test on indisready is necessary in 9.2, and harmless in
@@ -4921,7 +4964,38 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 					 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 							  "t.relnatts AS indnkeys, "
 							  "i.indkey, i.indisclustered, "
-							  "t.relpages, "
+							  "i.indisreplident, t.relpages, "
+							  "c.contype, c.conname, "
+							  "c.condeferrable, c.condeferred, "
+							  "c.tableoid AS contableoid, "
+							  "c.oid AS conoid, "
+				  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
+							"array_to_string(t.reloptions, ', ') AS options "
+							  "FROM pg_catalog.pg_index i "
+					  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
+							  "LEFT JOIN pg_catalog.pg_constraint c "
+							  "ON (i.indrelid = c.conrelid AND "
+							  "i.indexrelid = c.conindid AND "
+							  "c.contype IN ('p','u','x')) "
+							  "WHERE i.indrelid = '%u'::pg_catalog.oid "
+							  "AND i.indisvalid AND i.indisready "
+							  "ORDER BY indexname",
+							  tbinfo->dobj.catId.oid);
+		}
+		else if (fout->remoteVersion >= 90000)
+		{
+			/*
+			 * the test on indisready is necessary in 9.2, and harmless in
+			 * earlier/later versions
+			 */
+			appendPQExpBuffer(query,
+							  "SELECT t.tableoid, t.oid, "
+							  "t.relname AS indexname, "
+					 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+							  "t.relnatts AS indnkeys, "
+							  "i.indkey, i.indisclustered, "
+							  "false AS indisreplident, t.relpages, "
 							  "c.contype, c.conname, "
 							  "c.condeferrable, c.condeferred, "
 							  "c.tableoid AS contableoid, "
@@ -4948,7 +5022,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 					 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 							  "t.relnatts AS indnkeys, "
 							  "i.indkey, i.indisclustered, "
-							  "t.relpages, "
+							  "false AS indisreplident, t.relpages, "
 							  "c.contype, c.conname, "
 							  "c.condeferrable, c.condeferred, "
 							  "c.tableoid AS contableoid, "
@@ -4978,7 +5052,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 					 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 							  "t.relnatts AS indnkeys, "
 							  "i.indkey, i.indisclustered, "
-							  "t.relpages, "
+							  "false AS indisreplident, t.relpages, "
 							  "c.contype, c.conname, "
 							  "c.condeferrable, c.condeferred, "
 							  "c.tableoid AS contableoid, "
@@ -5007,7 +5081,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 					 "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
 							  "t.relnatts AS indnkeys, "
 							  "i.indkey, i.indisclustered, "
-							  "t.relpages, "
+							  "false AS indisreplident, t.relpages, "
 							  "c.contype, c.conname, "
 							  "c.condeferrable, c.condeferred, "
 							  "c.tableoid AS contableoid, "
@@ -5036,7 +5110,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "pg_get_indexdef(i.indexrelid) AS indexdef, "
 							  "t.relnatts AS indnkeys, "
 							  "i.indkey, false AS indisclustered, "
-							  "t.relpages, "
+							  "false AS indisreplident, t.relpages, "
 							  "CASE WHEN i.indisprimary THEN 'p'::char "
 							  "ELSE '0'::char END AS contype, "
 							  "t.relname AS conname, "
@@ -5063,7 +5137,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "pg_get_indexdef(i.indexrelid) AS indexdef, "
 							  "t.relnatts AS indnkeys, "
 							  "i.indkey, false AS indisclustered, "
-							  "t.relpages, "
+							  "false AS indisreplident, t.relpages, "
 							  "CASE WHEN i.indisprimary THEN 'p'::char "
 							  "ELSE '0'::char END AS contype, "
 							  "t.relname AS conname, "
@@ -5092,6 +5166,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indnkeys = PQfnumber(res, "indnkeys");
 		i_indkey = PQfnumber(res, "indkey");
 		i_indisclustered = PQfnumber(res, "indisclustered");
+		i_indisreplident = PQfnumber(res, "indisreplident");
 		i_relpages = PQfnumber(res, "relpages");
 		i_contype = PQfnumber(res, "contype");
 		i_conname = PQfnumber(res, "conname");
@@ -5135,6 +5210,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			parseOidArray(PQgetvalue(res, j, i_indkey),
 						  indxinfo[j].indkeys, INDEX_MAX_KEYS);
 			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
+			indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
 			indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
 			contype = *(PQgetvalue(res, j, i_contype));
 
@@ -13408,6 +13484,28 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		}
 	}
 
+	/*
+	 * dump properties we only have ALTER TABLE syntax for
+	 */
+	if ((tbinfo->relkind == RELKIND_RELATION || tbinfo->relkind == RELKIND_MATVIEW) &&
+	    tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
+	{
+		if (tbinfo->relreplident == REPLICA_IDENTITY_INDEX)
+		{
+			/* nothing to do, will be set when the index is dumped */
+		}
+		else if (tbinfo->relreplident == REPLICA_IDENTITY_NOTHING)
+		{
+			appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY NOTHING;\n",
+			                  fmtId(tbinfo->dobj.name));
+		}
+		else if (tbinfo->relreplident == REPLICA_IDENTITY_FULL)
+		{
+			appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY FULL;\n",
+			                  fmtId(tbinfo->dobj.name));
+		}
+	}
+
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
 
@@ -13579,6 +13677,15 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
 							  fmtId(indxinfo->dobj.name));
 		}
 
+		/* If the index is clustered, we need to record that. */
+		if (indxinfo->indisreplident)
+		{
+			appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY USING",
+			                  fmtId(tbinfo->dobj.name));
+			appendPQExpBuffer(q, " INDEX %s;\n",
+			                  fmtId(indxinfo->dobj.name));
+		}
+
 		/*
 		 * DROP must be fully qualified in case same name appears in
 		 * pg_catalog
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2c5971c..915e82c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -237,6 +237,7 @@ typedef struct _tableInfo
 	char		relkind;
 	char		relpersistence; /* relation persistence */
 	bool		relispopulated; /* relation is populated */
+	bool		relreplident;	/* replica identifier */
 	char	   *reltablespace;	/* relation tablespace */
 	char	   *reloptions;		/* options specified by WITH (...) */
 	char	   *checkoption;	/* WITH CHECK OPTION */
@@ -315,6 +316,7 @@ typedef struct _indxInfo
 	int			indnkeys;
 	Oid		   *indkeys;
 	bool		indisclustered;
+	bool		indisreplident;
 	/* if there is an associated constraint object, its dumpId: */
 	DumpId		indexconstraint;
 	int			relpages;		/* relpages of the underlying table */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ed1c5fd..d5f827d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1156,6 +1156,7 @@ describeOneTableDetails(const char *schemaname,
 		char	   *reloptions;
 		char	   *reloftype;
 		char		relpersistence;
+		char		relreplident;
 	}			tableinfo;
 	bool		show_modifiers = false;
 	bool		retval;
@@ -1171,7 +1172,24 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 90100)
+	if (pset.sversion >= 90400)
+	{
+		printfPQExpBuffer(&buf,
+			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, c.relhasoids, "
+						  "%s, c.reltablespace, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+						  "c.relpersistence, c.relreplident\n"
+						  "FROM pg_catalog.pg_class c\n "
+		   "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 90100)
 	{
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1276,6 +1294,8 @@ describeOneTableDetails(const char *schemaname,
 		pg_strdup(PQgetvalue(res, 0, 8)) : NULL;
 	tableinfo.relpersistence = (pset.sversion >= 90100) ?
 		*(PQgetvalue(res, 0, 9)) : 0;
+	tableinfo.relreplident = (pset.sversion >= 90400) ?
+		*(PQgetvalue(res, 0, 10)) : 'd';
 	PQclear(res);
 	res = NULL;
 
@@ -1589,6 +1609,12 @@ describeOneTableDetails(const char *schemaname,
 		else
 			appendPQExpBuffer(&buf,
 						"  false AS condeferrable, false AS condeferred,\n");
+
+		if (pset.sversion >= 90400)
+			appendPQExpBuffer(&buf, "i.indisidentity,\n");
+		else
+			appendPQExpBuffer(&buf, "false AS indisidentity,\n");
+
 		appendPQExpBuffer(&buf, "  a.amname, c2.relname, "
 					  "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n"
 						  "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n"
@@ -1612,9 +1638,10 @@ describeOneTableDetails(const char *schemaname,
 			char	   *indisvalid = PQgetvalue(result, 0, 3);
 			char	   *deferrable = PQgetvalue(result, 0, 4);
 			char	   *deferred = PQgetvalue(result, 0, 5);
-			char	   *indamname = PQgetvalue(result, 0, 6);
-			char	   *indtable = PQgetvalue(result, 0, 7);
-			char	   *indpred = PQgetvalue(result, 0, 8);
+			char	   *indisidentity = PQgetvalue(result, 0, 6);
+			char	   *indamname = PQgetvalue(result, 0, 7);
+			char	   *indtable = PQgetvalue(result, 0, 8);
+			char	   *indpred = PQgetvalue(result, 0, 9);
 
 			if (strcmp(indisprimary, "t") == 0)
 				printfPQExpBuffer(&tmpbuf, _("primary key, "));
@@ -1643,6 +1670,9 @@ describeOneTableDetails(const char *schemaname,
 			if (strcmp(deferred, "t") == 0)
 				appendPQExpBuffer(&tmpbuf, _(", initially deferred"));
 
+			if (strcmp(indisidentity, "t") == 0)
+				appendPQExpBuffer(&tmpbuf, _(", replica identity"));
+
 			printTableAddFooter(&cont, tmpbuf.data);
 			add_tablespace_footer(&cont, tableinfo.relkind,
 								  tableinfo.tablespace, true);
@@ -1713,6 +1743,10 @@ describeOneTableDetails(const char *schemaname,
 				appendPQExpBuffer(&buf,
 								  "null AS constraintdef, null AS contype, "
 							 "false AS condeferrable, false AS condeferred");
+			if (pset.sversion >= 90400)
+				appendPQExpBuffer(&buf, ", i.indisreplident");
+			else
+				appendPQExpBuffer(&buf, ", false AS indisreplident");
 			if (pset.sversion >= 80000)
 				appendPQExpBuffer(&buf, ", c2.reltablespace");
 			appendPQExpBuffer(&buf,
@@ -1783,12 +1817,15 @@ describeOneTableDetails(const char *schemaname,
 					if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
 						appendPQExpBuffer(&buf, " INVALID");
 
+					if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
+						appendPQExpBuffer(&buf, " REPLICA IDENT");
+
 					printTableAddFooter(&cont, buf.data);
 
 					/* Print tablespace of the index on the same line */
 					if (pset.sversion >= 80000)
 						add_tablespace_footer(&cont, 'i',
-										   atooid(PQgetvalue(result, i, 10)),
+										   atooid(PQgetvalue(result, i, 11)),
 											  false);
 				}
 			}
@@ -2273,6 +2310,17 @@ describeOneTableDetails(const char *schemaname,
 			printTableAddFooter(&cont, buf.data);
 		}
 
+		if ((tableinfo.relkind == 'r' || tableinfo.relkind == 'm') &&
+		    tableinfo.relreplident != 'd' && tableinfo.relreplident != 'i')
+		{
+			const char *s = _("Replica Identity");
+
+			printfPQExpBuffer(&buf, "%s: %s",
+			                  s,
+			                  tableinfo.relreplident == 'n' ? "NOTHING" : "FULL");
+			printTableAddFooter(&cont, buf.data);
+		}
+
 		/* OIDs, if verbose and not a materialized view */
 		if (verbose && tableinfo.relkind != 'm')
 		{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ae8f837..206971f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1371,7 +1371,7 @@ psql_completion(char *text, int start, int end)
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
 			"NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET",
-		"VALIDATE CONSTRAINT", NULL};
+		 "VALIDATE CONSTRAINT", "REPLICA IDENTITY", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
@@ -1616,6 +1616,35 @@ psql_completion(char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
+	else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
+			 pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
+			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
+	         pg_strcasecmp(prev_wd, "INDEX") == 0)
+	{
+		completion_info_charp = prev5_wd;
+		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+	}
+	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
+			 pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
+			 pg_strcasecmp(prev_wd, "USING") == 0)
+	{
+		COMPLETE_WITH_CONST("INDEX");
+	}
+	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+			 pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
+			 pg_strcasecmp(prev_wd, "IDENTITY") == 0)
+	{
+		static const char *const list_REPLICAID[] =
+		{"FULL", "NOTHING", "DEFAULT", "USING", NULL};
+
+		COMPLETE_WITH_LIST(list_REPLICAID);
+	}
+	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+			 pg_strcasecmp(prev_wd, "REPLICA") == 0)
+	{
+		COMPLETE_WITH_CONST("IDENTITY");
+	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 49c4f6f..a1fee11 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -66,6 +66,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	bool		relispopulated; /* matview currently holds query results */
+	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -93,7 +94,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					28
+#define Natts_pg_class					29
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -118,10 +119,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhastriggers	22
 #define Anum_pg_class_relhassubclass	23
 #define Anum_pg_class_relispopulated	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relminmxid		26
-#define Anum_pg_class_relacl			27
-#define Anum_pg_class_reloptions		28
+#define Anum_pg_class_relreplident		25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relminmxid		27
+#define Anum_pg_class_relacl			28
+#define Anum_pg_class_reloptions		29
 
 /* ----------------
  *		initial contents of pg_class
@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 
 
@@ -159,4 +161,16 @@ DESCR("");
 #define		  RELPERSISTENCE_UNLOGGED	'u'		/* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't'		/* temporary table */
 
+/* default selection for replica identity (primary key or nothing) */
+#define		  REPLICA_IDENTITY_DEFAULT	'd'
+/* no replica identity is logged for this relation */
+#define		  REPLICA_IDENTITY_NOTHING	'n'
+/* all columns are loged as replica identity */
+#define		  REPLICA_IDENTITY_FULL		'f'
+/*
+ * an explicitly chosen candidate key's columns are used as identity;
+ * will still be set if the index has been dropped, in that case it
+ * has the same meaning as 'd'
+ */
+#define		  REPLICA_IDENTITY_INDEX	'i'
 #endif   /* PG_CLASS_H */
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index 8d1d415..b31d9d7 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -42,6 +42,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
 	bool		indcheckxmin;	/* must we wait for xmin to be old? */
 	bool		indisready;		/* is this index ready for inserts? */
 	bool		indislive;		/* is this index alive at all? */
+	bool		indisreplident;	/* is this index the identity for replication? */
 
 	/* variable-length fields start here, but we allow direct access to indkey */
 	int2vector	indkey;			/* column numbers of indexed cols, or 0 */
@@ -69,7 +70,7 @@ typedef FormData_pg_index *Form_pg_index;
  *		compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index					18
+#define Natts_pg_index					19
 #define Anum_pg_index_indexrelid		1
 #define Anum_pg_index_indrelid			2
 #define Anum_pg_index_indnatts			3
@@ -82,12 +83,13 @@ typedef FormData_pg_index *Form_pg_index;
 #define Anum_pg_index_indcheckxmin		10
 #define Anum_pg_index_indisready		11
 #define Anum_pg_index_indislive			12
-#define Anum_pg_index_indkey			13
-#define Anum_pg_index_indcollation		14
-#define Anum_pg_index_indclass			15
-#define Anum_pg_index_indoption			16
-#define Anum_pg_index_indexprs			17
-#define Anum_pg_index_indpred			18
+#define Anum_pg_index_indisreplident	13
+#define Anum_pg_index_indkey			14
+#define Anum_pg_index_indcollation		15
+#define Anum_pg_index_indclass			16
+#define Anum_pg_index_indoption			17
+#define Anum_pg_index_indexprs			18
+#define Anum_pg_index_indpred			19
 
 /*
  * Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 360f98c..ae95cce 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -362,6 +362,7 @@ typedef enum NodeTag
 	T_CreateEventTrigStmt,
 	T_AlterEventTrigStmt,
 	T_RefreshMatViewStmt,
+	T_ReplicaIdentityStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e5235cb..952fbb3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1284,9 +1284,17 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
+typedef struct ReplicaIdentityStmt
+{
+	NodeTag		type;
+	char		identity_type;
+	char	   *name;
+} ReplicaIdentityStmt;
+
 typedef struct AlterTableCmd	/* one subcommand of an ALTER TABLE */
 {
 	NodeTag		type;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
new file mode 100644
index 0000000..04c7685
--- /dev/null
+++ b/src/test/regress/expected/replica_identity.out
@@ -0,0 +1,183 @@
+CREATE TABLE test_replica_identity (
+       id serial primary key,
+       keya text not null,
+       keyb text not null,
+       nonkey text,
+       CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE,
+       CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb)
+);
+CREATE TABLE test_replica_identity_othertable (id serial primary key);
+CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey);
+CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey);
+CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3));
+CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3';
+-- default is 'd'/DEFAULT for user created tables
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+-- but 'none' for system tables
+SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+----
+-- Make sure we detect inelegible indexes
+----
+-- fail, not unique
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab;
+ERROR:  cannot use non-unique index "test_replica_identity_keyab" as replica identity
+-- fail, not a candidate key, nullable column
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey;
+ERROR:  cannot use index "test_replica_identity_nonkey" as replica identity, column "nonkey" is nullable
+-- fail, hash indexes cannot do uniqueness
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash;
+ERROR:  cannot use index "test_replica_identity_hash" as replica identity because access method does not support uniqueness
+-- fail, expression index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr;
+ERROR:  cannot use expression index "test_replica_identity_expr" as replica identity
+-- fail, partial index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial;
+ERROR:  cannot use partial index "test_replica_identity_partial" as replica identity
+-- fail, not our index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey;
+ERROR:  "test_replica_identity_othertable_pkey" is not an index for table "test_replica_identity"
+-- fail, deferrable
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer;
+ERROR:  cannot use non-immediate index "test_replica_identity_unique_defer" as replica identity
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+----
+-- Make sure index cases succeeed
+----
+-- succeed, primary key
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ i
+(1 row)
+
+\d test_replica_identity
+                         Table "public.test_replica_identity"
+ Column |  Type   |                             Modifiers                              
+--------+---------+--------------------------------------------------------------------
+ id     | integer | not null default nextval('test_replica_identity_id_seq'::regclass)
+ keya   | text    | not null
+ keyb   | text    | not null
+ nonkey | text    | 
+Indexes:
+    "test_replica_identity_pkey" PRIMARY KEY, btree (id) REPLICA IDENT
+    "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+    "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb)
+    "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+    "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+    "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+    "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+    "test_replica_identity_hash" hash (nonkey)
+    "test_replica_identity_keyab" btree (keya, keyb)
+
+-- succeed, nondeferrable unique constraint over nonullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;
+-- succeed unique index over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ i
+(1 row)
+
+\d test_replica_identity
+                         Table "public.test_replica_identity"
+ Column |  Type   |                             Modifiers                              
+--------+---------+--------------------------------------------------------------------
+ id     | integer | not null default nextval('test_replica_identity_id_seq'::regclass)
+ keya   | text    | not null
+ keyb   | text    | not null
+ nonkey | text    | 
+Indexes:
+    "test_replica_identity_pkey" PRIMARY KEY, btree (id)
+    "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+    "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) REPLICA IDENT
+    "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+    "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+    "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+    "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+    "test_replica_identity_hash" hash (nonkey)
+    "test_replica_identity_keyab" btree (keya, keyb)
+
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+ count 
+-------
+     1
+(1 row)
+
+----
+-- Make sure non index cases work
+----
+ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+ count 
+-------
+     0
+(1 row)
+
+ALTER TABLE test_replica_identity REPLICA IDENTITY FULL;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
+\d test_replica_identity
+                         Table "public.test_replica_identity"
+ Column |  Type   |                             Modifiers                              
+--------+---------+--------------------------------------------------------------------
+ id     | integer | not null default nextval('test_replica_identity_id_seq'::regclass)
+ keya   | text    | not null
+ keyb   | text    | not null
+ nonkey | text    | 
+Indexes:
+    "test_replica_identity_pkey" PRIMARY KEY, btree (id)
+    "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+    "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb)
+    "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+    "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+    "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+    "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+    "test_replica_identity_hash" hash (nonkey)
+    "test_replica_identity_keyab" btree (keya, keyb)
+Replica Identity: FULL
+
+ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity_othertable;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1c1491c..4269026 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -88,7 +88,7 @@ test: privileges security_label collate matview lock
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic misc psql async
+test: alter_generic misc psql async replica_identity
 
 # rules cannot run concurrently with any test that creates a view
 test: rules
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index c4d451a..1573ade 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -102,6 +102,7 @@ test: alter_generic
 test: misc
 test: psql
 test: async
+test: replica_identity
 test: rules
 test: event_trigger
 test: select_views
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
new file mode 100644
index 0000000..9d2e9a6
--- /dev/null
+++ b/src/test/regress/sql/replica_identity.sql
@@ -0,0 +1,79 @@
+CREATE TABLE test_replica_identity (
+       id serial primary key,
+       keya text not null,
+       keyb text not null,
+       nonkey text,
+       CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE,
+       CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb)
+);
+
+CREATE TABLE test_replica_identity_othertable (id serial primary key);
+
+CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey);
+CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey);
+CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3));
+CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3';
+
+-- default is 'd'/DEFAULT for user created tables
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+-- but 'none' for system tables
+SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass;
+SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass;
+
+----
+-- Make sure we detect inelegible indexes
+----
+
+-- fail, not unique
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab;
+-- fail, not a candidate key, nullable column
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey;
+-- fail, hash indexes cannot do uniqueness
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash;
+-- fail, expression index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr;
+-- fail, partial index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial;
+-- fail, not our index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey;
+-- fail, deferrable
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer;
+
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+
+----
+-- Make sure index cases succeeed
+----
+
+-- succeed, primary key
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+
+-- succeed, nondeferrable unique constraint over nonullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;
+
+-- succeed unique index over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+
+----
+-- Make sure non index cases work
+----
+ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+
+ALTER TABLE test_replica_identity REPLICA IDENTITY FULL;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+
+DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity_othertable;
-- 
1.8.4.21.g992c386.dirty

#113Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#94)
Re: logical changeset generation v6.2

On Fri, Oct 18, 2013 at 2:26 PM, Andres Freund <andres@2ndquadrant.com> wrote:

So. As it turns out that solution isn't sufficient in the face of VACUUM
FULL and mixed DML/DDL transaction that have not yet been decoded.

To reiterate, as published it works like:
For every modification of catalog tuple (insert, multi_insert, update,
delete) that has influence over visibility issue a record that contains:
* filenode
* ctid
* (cmin, cmax)

When doing a visibility check on a catalog row during decoding of mixed
DML/DDL transaction lookup (cmin, cmax) for that row since we don't
store both for the tuple.

That mostly works great.

The problematic scenario is decoding a transaction that has done mixed
DML/DDL *after* a VACUUM FULL/CLUSTER has been performed. The VACUUM
FULL obviously changes the filenode and the ctid of a tuple, so we
cannot successfully do a lookup based on what we logged before.

So I have a new idea for handling this problem, which seems obvious in
retrospect. What if we make the VACUUM FULL or CLUSTER log the old
CTID -> new CTID mappings? This would only need to be done for
catalog tables, and maybe could be skipped for tuples whose XIDs are
old enough that we know those transactions must already be decoded.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#114Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#112)
Re: logical changeset generation v6.4

On Tue, Oct 22, 2013 at 10:07 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-21 20:16:29 +0200, Andres Freund wrote:

On 2013-10-18 20:50:58 +0200, Andres Freund wrote:

How about modifying the selection to go from:
* all rows if ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL;
* index chosen by ALTER TABLE ... REPLICA IDENTITY USING indexname
* [later, maybe] ALTER TABLE ... REPLICA IDENTITY (cola, colb)

Current draft is:
ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL|DEFAULT
ALTER TABLE ... REPLICA IDENTITY USING INDEX ...;

which leaves the door open for

ALTER TABLE ... REPLICA IDENTITY USING '(' column_name_list ')';

Does anybody have a strong feeling about requiring support for CREATE
TABLE for this?

Attached is a patch ontop of master implementing this syntax. It's not
wired up to the changeset extraction patch yet as I am not sure whether
others agree about the storage.

pg_class grew a 'relreplident' char, storing:
* 'd' default
* 'n' nothing
* 'f' full
* 'i' index with indisreplident set, or default if index has been
dropped
pg_index grew a 'indisreplident' bool indicating it is set as the
replica identity for a replident = 'i' relation.

Both changes shouldn't change the width of the affected relations, they
should reuse existing padding.

Does somebody prefer a different storage?

I had imagined that the storage might consist simply of a pg_attribute
boolean. So full would turn them all on, null would turn them all of,
etc. But that does make it hard to implement the "whatever the pkey
happens to be right now" behavior, so maybe your idea is better.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#115Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#113)
Re: logical changeset generation v6.2

On 2013-10-22 10:52:48 -0400, Robert Haas wrote:

On Fri, Oct 18, 2013 at 2:26 PM, Andres Freund <andres@2ndquadrant.com> wrote:

So. As it turns out that solution isn't sufficient in the face of VACUUM
FULL and mixed DML/DDL transaction that have not yet been decoded.

To reiterate, as published it works like:
For every modification of catalog tuple (insert, multi_insert, update,
delete) that has influence over visibility issue a record that contains:
* filenode
* ctid
* (cmin, cmax)

When doing a visibility check on a catalog row during decoding of mixed
DML/DDL transaction lookup (cmin, cmax) for that row since we don't
store both for the tuple.

That mostly works great.

The problematic scenario is decoding a transaction that has done mixed
DML/DDL *after* a VACUUM FULL/CLUSTER has been performed. The VACUUM
FULL obviously changes the filenode and the ctid of a tuple, so we
cannot successfully do a lookup based on what we logged before.

So I have a new idea for handling this problem, which seems obvious in
retrospect. What if we make the VACUUM FULL or CLUSTER log the old
CTID -> new CTID mappings? This would only need to be done for
catalog tables, and maybe could be skipped for tuples whose XIDs are
old enough that we know those transactions must already be decoded.

Ah. If it only were so simple ;). That was my first idea, and after I'd
bragged in an 2ndq internal chat that I'd found a simple idea I
obviously had to realize it doesn't work.

Consider:
INIT_LOGICAL_REPLICATION;
CREATE TABLE foo(...);
BEGIN;
INSERT INTO foo;
ALTER TABLE foo ...;
INSERT INTO foo;
COMMIT TX 3;
VACUUM FULL pg_class;
START_LOGICAL_REPLICATION;

When we decode tx 3 we haven't yet read the mapping from the vacuum
freeze. That scenario can happen either because decoding was stopped for
a moment, or because decoding couldn't keep up (slow connection,
whatever).

There also can be nasty variations where the VACUUM FULL happens while a
transaction is writing data since we don't hold locks on system
relations for very long.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#116Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#115)
Re: logical changeset generation v6.2

On Tue, Oct 22, 2013 at 11:02 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-22 10:52:48 -0400, Robert Haas wrote:

On Fri, Oct 18, 2013 at 2:26 PM, Andres Freund <andres@2ndquadrant.com> wrote:

So. As it turns out that solution isn't sufficient in the face of VACUUM
FULL and mixed DML/DDL transaction that have not yet been decoded.

To reiterate, as published it works like:
For every modification of catalog tuple (insert, multi_insert, update,
delete) that has influence over visibility issue a record that contains:
* filenode
* ctid
* (cmin, cmax)

When doing a visibility check on a catalog row during decoding of mixed
DML/DDL transaction lookup (cmin, cmax) for that row since we don't
store both for the tuple.

That mostly works great.

The problematic scenario is decoding a transaction that has done mixed
DML/DDL *after* a VACUUM FULL/CLUSTER has been performed. The VACUUM
FULL obviously changes the filenode and the ctid of a tuple, so we
cannot successfully do a lookup based on what we logged before.

So I have a new idea for handling this problem, which seems obvious in
retrospect. What if we make the VACUUM FULL or CLUSTER log the old
CTID -> new CTID mappings? This would only need to be done for
catalog tables, and maybe could be skipped for tuples whose XIDs are
old enough that we know those transactions must already be decoded.

Ah. If it only were so simple ;). That was my first idea, and after I'd
bragged in an 2ndq internal chat that I'd found a simple idea I
obviously had to realize it doesn't work.

Consider:
INIT_LOGICAL_REPLICATION;
CREATE TABLE foo(...);
BEGIN;
INSERT INTO foo;
ALTER TABLE foo ...;
INSERT INTO foo;
COMMIT TX 3;
VACUUM FULL pg_class;
START_LOGICAL_REPLICATION;

When we decode tx 3 we haven't yet read the mapping from the vacuum
freeze. That scenario can happen either because decoding was stopped for
a moment, or because decoding couldn't keep up (slow connection,
whatever).

That strikes me as a flaw in the implementation rather than the idea.
You're presupposing a patch where the necessary information is
available in WAL yet you don't make use of it at the proper time. It
seems to me that you have to think of the CTID map as tied to a
relfilenode; if you try to use one relfilenode's map with a different
relfilenode, it's obviously not going to work. So don't do that.

/me looks innocent.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#117Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#94)
Re: logical changeset generation v6.2

On 2013-10-18 20:26:16 +0200, Andres Freund wrote:

4) Store both (cmin, cmax) for catalog tuples.

BTW: That would have the nice side-effect of delivering the basis of
what you need to do parallel sort in a transaction that previously has
performed DDL.

Currently you cannot do anything in parallel after DDL, even if you only
scan the table in one backend, because operators et al. have to do
catalog lookups which you can't do consistently since cmin/cmax aren't
available in both.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#118Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Andres Freund (#117)
Re: logical changeset generation v6.2

On 22.10.2013 19:12, Andres Freund wrote:

On 2013-10-18 20:26:16 +0200, Andres Freund wrote:

4) Store both (cmin, cmax) for catalog tuples.

BTW: That would have the nice side-effect of delivering the basis of
what you need to do parallel sort in a transaction that previously has
performed DDL.

Currently you cannot do anything in parallel after DDL, even if you only
scan the table in one backend, because operators et al. have to do
catalog lookups which you can't do consistently since cmin/cmax aren't
available in both.

Parallel workers will need cmin/cmax for user tables too, to know which
tuples are visible to the snapshot.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#119Andres Freund
andres@2ndquadrant.com
In reply to: Heikki Linnakangas (#118)
Re: logical changeset generation v6.2

On 2013-10-22 19:19:19 +0300, Heikki Linnakangas wrote:

On 22.10.2013 19:12, Andres Freund wrote:

On 2013-10-18 20:26:16 +0200, Andres Freund wrote:

4) Store both (cmin, cmax) for catalog tuples.

BTW: That would have the nice side-effect of delivering the basis of
what you need to do parallel sort in a transaction that previously has
performed DDL.

Currently you cannot do anything in parallel after DDL, even if you only
scan the table in one backend, because operators et al. have to do
catalog lookups which you can't do consistently since cmin/cmax aren't
available in both.

Parallel workers will need cmin/cmax for user tables too, to know which
tuples are visible to the snapshot.

The existing proposals were mostly about just parallelizing the sort and
similar operations, right? In such scenarios you really need it only for
the catalog.

But we could easily generalize it for user data too. We should even be
able to only use "wide cids" when we a backend needs it it since
inherently it's only needed within a single transaction.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#120Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Andres Freund (#119)
Re: logical changeset generation v6.2

On 22.10.2013 19:23, Andres Freund wrote:

On 2013-10-22 19:19:19 +0300, Heikki Linnakangas wrote:

On 22.10.2013 19:12, Andres Freund wrote:

On 2013-10-18 20:26:16 +0200, Andres Freund wrote:

4) Store both (cmin, cmax) for catalog tuples.

BTW: That would have the nice side-effect of delivering the basis of
what you need to do parallel sort in a transaction that previously has
performed DDL.

Currently you cannot do anything in parallel after DDL, even if you only
scan the table in one backend, because operators et al. have to do
catalog lookups which you can't do consistently since cmin/cmax aren't
available in both.

Parallel workers will need cmin/cmax for user tables too, to know which
tuples are visible to the snapshot.

The existing proposals were mostly about just parallelizing the sort and
similar operations, right? In such scenarios you really need it only for
the catalog.

But we could easily generalize it for user data too. We should even be
able to only use "wide cids" when we a backend needs it it since
inherently it's only needed within a single transaction.

Or just hand over a copy of the combocid map to the worker, along with
the snapshot. Seems a lot simpler than this wide cids business..

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#121Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#120)
Re: logical changeset generation v6.2

On Tue, Oct 22, 2013 at 12:25 PM, Heikki Linnakangas
<hlinnakangas@vmware.com> wrote:

Or just hand over a copy of the combocid map to the worker, along with the
snapshot. Seems a lot simpler than this wide cids business..

Yes, that's what Noah and I talked about doing. Or possibly even
making the map into a hash table in dynamic shared memory, so that new
combo CIDs could be allocated by any backend in the parallel group.
But that seems hard, so for starters I think we'll only parallelize
read-only operations and just hand over a copy of the map.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#122Andres Freund
andres@2ndquadrant.com
In reply to: Heikki Linnakangas (#120)
Re: logical changeset generation v6.2

On 2013-10-22 19:25:31 +0300, Heikki Linnakangas wrote:

On 22.10.2013 19:23, Andres Freund wrote:

On 2013-10-22 19:19:19 +0300, Heikki Linnakangas wrote:

On 22.10.2013 19:12, Andres Freund wrote:

On 2013-10-18 20:26:16 +0200, Andres Freund wrote:

4) Store both (cmin, cmax) for catalog tuples.

BTW: That would have the nice side-effect of delivering the basis of
what you need to do parallel sort in a transaction that previously has
performed DDL.

Currently you cannot do anything in parallel after DDL, even if you only
scan the table in one backend, because operators et al. have to do
catalog lookups which you can't do consistently since cmin/cmax aren't
available in both.

Parallel workers will need cmin/cmax for user tables too, to know which
tuples are visible to the snapshot.

The existing proposals were mostly about just parallelizing the sort and
similar operations, right? In such scenarios you really need it only for
the catalog.

But we could easily generalize it for user data too. We should even be
able to only use "wide cids" when we a backend needs it it since
inherently it's only needed within a single transaction.

Or just hand over a copy of the combocid map to the worker, along with the
snapshot. Seems a lot simpler than this wide cids business..

That's not sufficient if you want to continue writing in the primary
backend though which isn't an uninteresting thing.

I am not saying that parallel XXX is a sufficient reason for this, just
that it might a be a co-benefactor.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#123Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#116)
Re: logical changeset generation v6.2

On 2013-10-22 11:59:35 -0400, Robert Haas wrote:

So I have a new idea for handling this problem, which seems obvious in
retrospect. What if we make the VACUUM FULL or CLUSTER log the old
CTID -> new CTID mappings? This would only need to be done for
catalog tables, and maybe could be skipped for tuples whose XIDs are
old enough that we know those transactions must already be decoded.

Ah. If it only were so simple ;). That was my first idea, and after I'd
bragged in an 2ndq internal chat that I'd found a simple idea I
obviously had to realize it doesn't work.

Consider:
INIT_LOGICAL_REPLICATION;
CREATE TABLE foo(...);
BEGIN;
INSERT INTO foo;
ALTER TABLE foo ...;
INSERT INTO foo;
COMMIT TX 3;
VACUUM FULL pg_class;
START_LOGICAL_REPLICATION;

When we decode tx 3 we haven't yet read the mapping from the vacuum
freeze. That scenario can happen either because decoding was stopped for
a moment, or because decoding couldn't keep up (slow connection,
whatever).

It seems to me that you have to think of the CTID map as tied to a
relfilenode; if you try to use one relfilenode's map with a different
relfilenode, it's obviously not going to work. So don't do that.

It has to be tied to relfilenode (+ctid) *and* transaction
unfortunately.

That strikes me as a flaw in the implementation rather than the idea.
You're presupposing a patch where the necessary information is
available in WAL yet you don't make use of it at the proper time.

The problem is that the mapping would be somewhere *ahead* from the
transaction/WAL we're currently decoding. We'd need to read ahead till
we find the correct one.
But I think I mainly misunderstood what you proposed. That mapping could
be written besides relfilenode, instead of into the WAL. Then my
imagined problem doesn't exist anymore.

We only would need to write out mappings for tuples modified since the
xmin horizon, so it wouldn't even be *too* bad for bigger relations.

This won't easily work for two+ rewrites because we'd need to apply all
mappings in order and thus would have to keep a history of intermediate
nodes/mappings. But it'd be perfectly doable to simply wait till
decoders are caught up.

I still "feel" that simply storing both cmin, cmax is cleaner, but if
that's not acceptable, I can certainly live with something like this.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#124Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#123)
Re: logical changeset generation v6.2

On Tue, Oct 22, 2013 at 1:08 PM, Andres Freund <andres@2ndquadrant.com> wrote:

It seems to me that you have to think of the CTID map as tied to a
relfilenode; if you try to use one relfilenode's map with a different
relfilenode, it's obviously not going to work. So don't do that.

It has to be tied to relfilenode (+ctid) *and* transaction
unfortunately.

I agree that it does, but it doesn't seem particularly unfortunate to me.

That strikes me as a flaw in the implementation rather than the idea.
You're presupposing a patch where the necessary information is
available in WAL yet you don't make use of it at the proper time.

The problem is that the mapping would be somewhere *ahead* from the
transaction/WAL we're currently decoding. We'd need to read ahead till
we find the correct one.

Yes, I think that's what you need to do.

But I think I mainly misunderstood what you proposed. That mapping could
be written besides relfilenode, instead of into the WAL. Then my
imagined problem doesn't exist anymore.

That's pretty ugly. I think it should be written into WAL.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#125Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#124)
Re: logical changeset generation v6.2

On 2013-10-22 13:57:53 -0400, Robert Haas wrote:

On Tue, Oct 22, 2013 at 1:08 PM, Andres Freund <andres@2ndquadrant.com> wrote:

That strikes me as a flaw in the implementation rather than the idea.
You're presupposing a patch where the necessary information is
available in WAL yet you don't make use of it at the proper time.

The problem is that the mapping would be somewhere *ahead* from the
transaction/WAL we're currently decoding. We'd need to read ahead till
we find the correct one.

Yes, I think that's what you need to do.

My problem with that is that rewrite can be gigabytes into the future.

When reading forward we could either just continue reading data into the
reorderbuffer, but delay replaying all future commits till we found the
currently needed remap. That might have quite the additional
storage/memory cost, but runtime complexity should be the same as normal
decoding.
Or we could individually read ahead for every transaction. But doing so
for every transaction will get rather expensive (rougly O(amount_of_wal^2)).

I think that'd be pretty similar to just disallowing VACUUM
FREEZE/CLUSTER on catalog relations since effectively it'd be to
expensive to use.

But I think I mainly misunderstood what you proposed. That mapping could
be written besides relfilenode, instead of into the WAL. Then my
imagined problem doesn't exist anymore.

That's pretty ugly. I think it should be written into WAL.

It basically has O(1) access, that's why I was thinking about it.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#126Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#125)
Re: logical changeset generation v6.2

On Tue, Oct 22, 2013 at 2:13 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-22 13:57:53 -0400, Robert Haas wrote:

On Tue, Oct 22, 2013 at 1:08 PM, Andres Freund <andres@2ndquadrant.com> wrote:

That strikes me as a flaw in the implementation rather than the idea.
You're presupposing a patch where the necessary information is
available in WAL yet you don't make use of it at the proper time.

The problem is that the mapping would be somewhere *ahead* from the
transaction/WAL we're currently decoding. We'd need to read ahead till
we find the correct one.

Yes, I think that's what you need to do.

My problem with that is that rewrite can be gigabytes into the future.

When reading forward we could either just continue reading data into the
reorderbuffer, but delay replaying all future commits till we found the
currently needed remap. That might have quite the additional
storage/memory cost, but runtime complexity should be the same as normal
decoding.
Or we could individually read ahead for every transaction. But doing so
for every transaction will get rather expensive (rougly O(amount_of_wal^2)).

[ Sorry it's taken me a bit of time to get back to this; other tasks
intervened, and I also just needed some time to let it settle in my
brain. ]

If you read ahead looking for a set of ctid translations from
relfilenode A to relfilenode B, and along the way you happen to
encounter a set of translations from relfilenode C to relfilenode D,
you could stash that set of translations away somewhere, so that if
the next transaction you process needs that set of mappings, it's
already computed. With that approach, you'd never have to pre-read
the same set of WAL files more than once.

But, as I think about it more, that's not very different from your
idea of stashing the translations someplace other than WAL in the
first place. I mean, if the read-ahead thread generates a series of
files in pg_somethingorother that contain those maps, you could have
just written the maps to that directory in the first place. So on
further review I think we could adopt that approach.

However, I'm leery about the idea of using a relation fork for this.
I'm not sure whether that's what you had it mind, but it gives me the
willies. First, it adds distributed overhead to the system, as
previously discussed; and second, I think the accounting may be kind
of tricky, especially in the face of multiple rewrites. I'd be more
inclined to find a separate place to store the mappings. Note that,
AFAICS, there's no real need for the mapping file to be
block-structured, and I believe they'll be written first (with no
readers) and subsequently only read (with no further writes) and
eventually deleted.

One possible objection to this is that it would preclude decoding on a
standby, which seems like a likely enough thing to want to do. So
maybe it's best to WAL-log the changes to the mapping file so that the
standby can reconstruct it if needed.

I think that'd be pretty similar to just disallowing VACUUM
FREEZE/CLUSTER on catalog relations since effectively it'd be to
expensive to use.

This seems unduly pessimistic to me; unless the catalogs are really
darn big, this is a mostly theoretical problem.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#127Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#126)
Re: logical changeset generation v6.2

On 2013-10-24 10:59:21 -0400, Robert Haas wrote:

On Tue, Oct 22, 2013 at 2:13 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-22 13:57:53 -0400, Robert Haas wrote:

On Tue, Oct 22, 2013 at 1:08 PM, Andres Freund <andres@2ndquadrant.com> wrote:

That strikes me as a flaw in the implementation rather than the idea.
You're presupposing a patch where the necessary information is
available in WAL yet you don't make use of it at the proper time.

The problem is that the mapping would be somewhere *ahead* from the
transaction/WAL we're currently decoding. We'd need to read ahead till
we find the correct one.

Yes, I think that's what you need to do.

My problem with that is that rewrite can be gigabytes into the future.

When reading forward we could either just continue reading data into the
reorderbuffer, but delay replaying all future commits till we found the
currently needed remap. That might have quite the additional
storage/memory cost, but runtime complexity should be the same as normal
decoding.
Or we could individually read ahead for every transaction. But doing so
for every transaction will get rather expensive (rougly O(amount_of_wal^2)).

[ Sorry it's taken me a bit of time to get back to this; other tasks
intervened, and I also just needed some time to let it settle in my
brain. ]

No worries. I've had enough things to work on ;)

If you read ahead looking for a set of ctid translations from
relfilenode A to relfilenode B, and along the way you happen to
encounter a set of translations from relfilenode C to relfilenode D,
you could stash that set of translations away somewhere, so that if
the next transaction you process needs that set of mappings, it's
already computed. With that approach, you'd never have to pre-read
the same set of WAL files more than once.

But, as I think about it more, that's not very different from your
idea of stashing the translations someplace other than WAL in the
first place. I mean, if the read-ahead thread generates a series of
files in pg_somethingorother that contain those maps, you could have
just written the maps to that directory in the first place. So on
further review I think we could adopt that approach.

Yea, that basically was my reasoning, only expressed much more nicely ;)

However, I'm leery about the idea of using a relation fork for this.
I'm not sure whether that's what you had it mind, but it gives me the
willies. First, it adds distributed overhead to the system, as
previously discussed; and second, I think the accounting may be kind
of tricky, especially in the face of multiple rewrites. I'd be more
inclined to find a separate place to store the mappings. Note that,
AFAICS, there's no real need for the mapping file to be
block-structured, and I believe they'll be written first (with no
readers) and subsequently only read (with no further writes) and
eventually deleted.

I was thinking of storing it along other data used during logical
decoding and let decoding's cleanup clean up that data as well. All the
information for that should be there.

There's one snag I currently can see, namely that we actually need to
prevent that a formerly dropped relfilenode is getting reused. Not
entirely sure what the best way for that is.

One possible objection to this is that it would preclude decoding on a
standby, which seems like a likely enough thing to want to do. So
maybe it's best to WAL-log the changes to the mapping file so that the
standby can reconstruct it if needed.

The mapping file probably can be one big wal record, so it should be
easy enough to do.

For a moment I thought there's a problem with decoding on the standby
having to read ahead of the current location to find the newer mapping,
but that's actually not required since we're protected by the AEL lock
during rewrites on the standby as well.

I think that'd be pretty similar to just disallowing VACUUM
FREEZE/CLUSTER on catalog relations since effectively it'd be to
expensive to use.

This seems unduly pessimistic to me; unless the catalogs are really
darn big, this is a mostly theoretical problem.

Well, it's not the size of the relation, but the amount of concurrent
WAL that's being generated that matters. But anyway, if we do it like
you described above that shouldn't be a problem.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#128Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#99)
Re: logical changeset generation v6.2

On 2013-10-21 16:15:58 +0200, Andres Freund wrote:

I don't think I understand exactly what you have in mind for (2); can
you elaborate? I have always thought that having a
WaitForDecodingToCatchUp() primitive was a good way of handling
changes that were otherwise too difficult to track our way through. I
am not sure you're doing that at all right now, which in some sense I
guess is fine, but I haven't really understood your aversion to this
solution. There are some locking issues to be worked out here, but
the problems don't seem altogether intractable.

So, what we need to do for rewriting catalog tables would be:
1) lock table against writes
2) wait for all in-progress xacts to finish, they could have modified
the table in question (we don't keep locks on system tables)
3) acquire xlog insert pointer
4) wait for all logical decoding actions to read past that pointer
5) upgrade the lock to an access exclusive one
6) perform vacuum full as usual

The lock upgrade hazards in here are the reason I am adverse to the
solution. And I don't see how we can avoid them, since in order for
decoding to catchup it has to be able to read from the
catalog... Otherwise it's easy enough to implement.

So, I thought about this for some more and I think I've a partial
solution to the problem.

The worst thing about deadlocks that occur in the above is that they
could be the VACUUM FULL waiting for the "restart LSN"[1]The "restart LSN" is the point from where we need to be able read WAL to replay all changes the receiving side hasn't acked yet. of a decoding
slot to progress, but the restart LSN cannot progress because the slot
is waiting for a xid/transaction to end which is being blocked by the
lock upgrade from VACUUM FULL. Such conflicts are not visible to the
deadlock detector, which obviously is bad.
I've prototyped this (~25 lines) and this happens pretty frequently. But
it turns out that we can actually fix this by exporting (to shared
memory) the oldest in-progress xid of a decoding slot. Then the waiting
code can do a XactLockTableWait() for that xid...

I wonder if this is isn't maybe sufficient. Yes, it can deadlock, but
that's already the case for VACUUM FULLs of system tables, although less
likely. And it will be detected/handled.
There's one more snag though, we currently allow CLUSTER system_table;
in an existing transaction. I think that'd have to be disallowed.

What do you think?

Greetings,

Andres Freund

[1]: The "restart LSN" is the point from where we need to be able read WAL to replay all changes the receiving side hasn't acked yet.
WAL to replay all changes the receiving side hasn't acked yet.

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#129Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#112)
Re: logical changeset generation v6.4

Hi,

On 2013-10-22 16:07:16 +0200, Andres Freund wrote:

On 2013-10-21 20:16:29 +0200, Andres Freund wrote:

Current draft is:
ALTER TABLE ... REPLICA IDENTITY NOTHING|FULL|DEFAULT
ALTER TABLE ... REPLICA IDENTITY USING INDEX ...;

which leaves the door open for

ALTER TABLE ... REPLICA IDENTITY USING '(' column_name_list ')';

Does anybody have a strong feeling about requiring support for CREATE
TABLE for this?

Attached is a patch ontop of master implementing this syntax. It's not
wired up to the changeset extraction patch yet as I am not sure whether
others agree about the storage.

So, I am currently wondering about how to store the "old" tuple, based
on this. Currently it is stored using the TupleDesc of the index the old
tuple is based on. But if we want to allow transporting the entire tuple
that obviously cannot be the only option.
One option would be to change the stored format based on what's
configured, using the relation's TupleDesc if FULL is used. But I think
always using the heap relation's desc is better.
The not-logged columns would then just be represented as NULLs. That
will make old primary keys bigger if the relation has a high number of
columns and the key small, but I don't think it matters enough.

Opinions?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#130Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#129)
Re: logical changeset generation v6.4

On Fri, Oct 25, 2013 at 10:58 AM, Andres Freund <andres@2ndquadrant.com> wrote:

So, I am currently wondering about how to store the "old" tuple, based
on this. Currently it is stored using the TupleDesc of the index the old
tuple is based on. But if we want to allow transporting the entire tuple
that obviously cannot be the only option.
One option would be to change the stored format based on what's
configured, using the relation's TupleDesc if FULL is used. But I think
always using the heap relation's desc is better.

I heartily agree.

The not-logged columns would then just be represented as NULLs. That
will make old primary keys bigger if the relation has a high number of
columns and the key small, but I don't think it matters enough.

Even if it does matter, the cure seems likely to be worse than the disease.

My only other comment is that if NONE is selected, we ought to omit
the old tuple altogether, not store one that is all-nulls. But I bet
you had that in mind anyway.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#131Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#127)
Re: logical changeset generation v6.2

On Fri, Oct 25, 2013 at 7:57 AM, Andres Freund <andres@2ndquadrant.com> wrote:

However, I'm leery about the idea of using a relation fork for this.
I'm not sure whether that's what you had it mind, but it gives me the
willies. First, it adds distributed overhead to the system, as
previously discussed; and second, I think the accounting may be kind
of tricky, especially in the face of multiple rewrites. I'd be more
inclined to find a separate place to store the mappings. Note that,
AFAICS, there's no real need for the mapping file to be
block-structured, and I believe they'll be written first (with no
readers) and subsequently only read (with no further writes) and
eventually deleted.

I was thinking of storing it along other data used during logical
decoding and let decoding's cleanup clean up that data as well. All the
information for that should be there.

That seems OK.

There's one snag I currently can see, namely that we actually need to
prevent that a formerly dropped relfilenode is getting reused. Not
entirely sure what the best way for that is.

I'm not sure in detail, but it seems to me that this all part of the
same picture. If you're tracking changed relfilenodes, you'd better
track dropped ones as well. Completely aside from this issue, what
keeps a relation from being dropped before we've decoded all of the
changes made to its data before the point at which it was dropped? (I
hope the answer isn't "nothing".)

One possible objection to this is that it would preclude decoding on a
standby, which seems like a likely enough thing to want to do. So
maybe it's best to WAL-log the changes to the mapping file so that the
standby can reconstruct it if needed.

The mapping file probably can be one big wal record, so it should be
easy enough to do.

It might be better to batch it, because if you rewrite a big relation,
and the record is really big, everyone else will be frozen out of
inserting WAL for as long as that colossal record is being written and
synced. If it's inserted in reasonably-sized chunks, the rest of the
system won't be starved as badly.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#132Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#128)
Re: logical changeset generation v6.2

On Fri, Oct 25, 2013 at 8:14 AM, Andres Freund <andres@2ndquadrant.com> wrote:

So, I thought about this for some more and I think I've a partial
solution to the problem.

The worst thing about deadlocks that occur in the above is that they
could be the VACUUM FULL waiting for the "restart LSN"[1] of a decoding
slot to progress, but the restart LSN cannot progress because the slot
is waiting for a xid/transaction to end which is being blocked by the
lock upgrade from VACUUM FULL. Such conflicts are not visible to the
deadlock detector, which obviously is bad.
I've prototyped this (~25 lines) and this happens pretty frequently. But
it turns out that we can actually fix this by exporting (to shared
memory) the oldest in-progress xid of a decoding slot. Then the waiting
code can do a XactLockTableWait() for that xid...

I wonder if this is isn't maybe sufficient. Yes, it can deadlock, but
that's already the case for VACUUM FULLs of system tables, although less
likely. And it will be detected/handled.
There's one more snag though, we currently allow CLUSTER system_table;
in an existing transaction. I think that'd have to be disallowed.

It wouldn't bother me too much to restrict CLUSTER system_table by
PreventTransactionChain() at wal_level = logical, but obviously it
would be nicer if we *didn't* have to do that.

In general, I don't think waiting on an XID is sufficient because a
process can acquire a heavyweight lock without having an XID. Perhaps
use the VXID instead?

One thought I had about waiting for decoding to catch up is that you
might do it before acquiring the lock. Of course, you then have a
problem if you get behind again before acquiring the lock. It's
tempting to adopt the solution we used for RangeVarGetRelidExtended,
namely: wait for catchup without the lock, acquire the lock, see
whether we're still caught up if so great else release lock and loop.
But there's probably too much starvation risk to get away with that.

On the whole, I'm leaning toward thinking that the other solution
(recording the old-to-new CTID mappings generated by CLUSTER to the
extent that they are needed) is probably more elegant.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#133Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#132)
Re: logical changeset generation v6.2

On 2013-10-28 12:04:01 -0400, Robert Haas wrote:

On Fri, Oct 25, 2013 at 8:14 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I wonder if this is isn't maybe sufficient. Yes, it can deadlock, but
that's already the case for VACUUM FULLs of system tables, although less
likely. And it will be detected/handled.
There's one more snag though, we currently allow CLUSTER system_table;
in an existing transaction. I think that'd have to be disallowed.

It wouldn't bother me too much to restrict CLUSTER system_table by
PreventTransactionChain() at wal_level = logical, but obviously it
would be nicer if we *didn't* have to do that.

In general, I don't think waiting on an XID is sufficient because a
process can acquire a heavyweight lock without having an XID. Perhaps
use the VXID instead?

But decoding doesn't care about transactions that haven't "used" an XID
yet (since that means they haven't modified the catalog), so that
shouldn't be problematic.

One thought I had about waiting for decoding to catch up is that you
might do it before acquiring the lock. Of course, you then have a
problem if you get behind again before acquiring the lock. It's
tempting to adopt the solution we used for RangeVarGetRelidExtended,
namely: wait for catchup without the lock, acquire the lock, see
whether we're still caught up if so great else release lock and loop.
But there's probably too much starvation risk to get away with that.

I think we'd pretty much always starve in that case. It'd be different
if we could detect that there weren't any writes to the table
inbetween. I can see doing that using a locking hack like autovac uses,
but brr, that'd be ugly.

On the whole, I'm leaning toward thinking that the other solution
(recording the old-to-new CTID mappings generated by CLUSTER to the
extent that they are needed) is probably more elegant.

I personally still think that the "wide cmin/cmax" solution is *much*
more elegant, simpler and actually can be used for other things than
logical decoding.
Since you don't seem to agree I am going to write a prototype using such
a mapping to see how it will look though.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#134Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#133)
Re: logical changeset generation v6.2

On Mon, Oct 28, 2013 at 12:17 PM, Andres Freund <andres@2ndquadrant.com> wrote:

In general, I don't think waiting on an XID is sufficient because a
process can acquire a heavyweight lock without having an XID. Perhaps
use the VXID instead?

But decoding doesn't care about transactions that haven't "used" an XID
yet (since that means they haven't modified the catalog), so that
shouldn't be problematic.

Hmm, maybe. But what if the deadlock has more members? e.g. A is
blocking decoding by holding AEL w/no XID, and B is blocking A by
doing VF on a rel A needs, and decoding is blocking B.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#135Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#131)
Re: logical changeset generation v6.2

On 2013-10-28 11:54:31 -0400, Robert Haas wrote:

There's one snag I currently can see, namely that we actually need to
prevent that a formerly dropped relfilenode is getting reused. Not
entirely sure what the best way for that is.

I'm not sure in detail, but it seems to me that this all part of the
same picture. If you're tracking changed relfilenodes, you'd better
track dropped ones as well.

What I am thinking about is the way GetNewRelFileNode() checks for
preexisting relfilenodes. It uses SnapshotDirty to scan for existing
relfilenodes for a newly created oid. Which means already dropped
relations could be reused.
I guess it could be as simple as using SatisfiesAny (or even better a
wrapper around SatisfiesVacuum that knows about recently dead tuples).

Completely aside from this issue, what
keeps a relation from being dropped before we've decoded all of the
changes made to its data before the point at which it was dropped? (I
hope the answer isn't "nothing".)

Nothing. But there's no need to prevent it, it'll still be in the
catalog and we don't ever access a non-catalog relation's data during
decoding.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#136Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#135)
Re: logical changeset generation v6.2

On Tue, Oct 29, 2013 at 10:47 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-28 11:54:31 -0400, Robert Haas wrote:

There's one snag I currently can see, namely that we actually need to
prevent that a formerly dropped relfilenode is getting reused. Not
entirely sure what the best way for that is.

I'm not sure in detail, but it seems to me that this all part of the
same picture. If you're tracking changed relfilenodes, you'd better
track dropped ones as well.

What I am thinking about is the way GetNewRelFileNode() checks for
preexisting relfilenodes. It uses SnapshotDirty to scan for existing
relfilenodes for a newly created oid. Which means already dropped
relations could be reused.
I guess it could be as simple as using SatisfiesAny (or even better a
wrapper around SatisfiesVacuum that knows about recently dead tuples).

I think modifying GetNewRelFileNode() is attacking the problem from
the wrong end. The point is that when a table is dropped, that fact
can be communicated to the same machine machinery that's been tracking
the CTID->CTID mappings. Instead of saying "hey, the tuples that were
in relfilenode 12345 are now in relfilenode 67890 in these new
positions", it can say "hey, the tuples that were in relfilenode 12345
are now GONE".

Completely aside from this issue, what
keeps a relation from being dropped before we've decoded all of the
changes made to its data before the point at which it was dropped? (I
hope the answer isn't "nothing".)

Nothing. But there's no need to prevent it, it'll still be in the
catalog and we don't ever access a non-catalog relation's data during
decoding.

Oh, right. But what about a drop of a user-catalog table?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#137Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#136)
Re: logical changeset generation v6.2

On 2013-10-29 11:28:44 -0400, Robert Haas wrote:

On Tue, Oct 29, 2013 at 10:47 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-10-28 11:54:31 -0400, Robert Haas wrote:

There's one snag I currently can see, namely that we actually need to
prevent that a formerly dropped relfilenode is getting reused. Not
entirely sure what the best way for that is.

I'm not sure in detail, but it seems to me that this all part of the
same picture. If you're tracking changed relfilenodes, you'd better
track dropped ones as well.

What I am thinking about is the way GetNewRelFileNode() checks for
preexisting relfilenodes. It uses SnapshotDirty to scan for existing
relfilenodes for a newly created oid. Which means already dropped
relations could be reused.
I guess it could be as simple as using SatisfiesAny (or even better a
wrapper around SatisfiesVacuum that knows about recently dead tuples).

I think modifying GetNewRelFileNode() is attacking the problem from
the wrong end. The point is that when a table is dropped, that fact
can be communicated to the same machine machinery that's been tracking
the CTID->CTID mappings. Instead of saying "hey, the tuples that were
in relfilenode 12345 are now in relfilenode 67890 in these new
positions", it can say "hey, the tuples that were in relfilenode 12345
are now GONE".

Unfortunately I don't understand what you're suggesting. What I am
worried about is something like:

<- decoding is here
VACUUM FULL pg_class; -- rewrites filenode 1 to 2
VACUUM FULL pg_class; -- rewrites filenode 2 to 3
VACUUM FULL pg_class; -- rewrites filenode 3 to 1
<- now decode up to here

In this case there are two possible (cmin,cmax) values for a specific
tuple. One from the original filenode 1 and one for the one generated
from 3.
Now that will only happen if there's an oid wraparound which hopefully
shouldn't happen very often, but I'd like to not rely on that.

Completely aside from this issue, what
keeps a relation from being dropped before we've decoded all of the
changes made to its data before the point at which it was dropped? (I
hope the answer isn't "nothing".)

Nothing. But there's no need to prevent it, it'll still be in the
catalog and we don't ever access a non-catalog relation's data during
decoding.

Oh, right. But what about a drop of a user-catalog table?

Currently nothing prevents that. I am not sure it's worth worrying about
it, do you think we should?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#138Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#137)
Re: logical changeset generation v6.2

On Tue, Oct 29, 2013 at 11:43 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I think modifying GetNewRelFileNode() is attacking the problem from
the wrong end. The point is that when a table is dropped, that fact
can be communicated to the same machine machinery that's been tracking
the CTID->CTID mappings. Instead of saying "hey, the tuples that were
in relfilenode 12345 are now in relfilenode 67890 in these new
positions", it can say "hey, the tuples that were in relfilenode 12345
are now GONE".

Unfortunately I don't understand what you're suggesting. What I am
worried about is something like:

<- decoding is here
VACUUM FULL pg_class; -- rewrites filenode 1 to 2
VACUUM FULL pg_class; -- rewrites filenode 2 to 3
VACUUM FULL pg_class; -- rewrites filenode 3 to 1
<- now decode up to here

In this case there are two possible (cmin,cmax) values for a specific
tuple. One from the original filenode 1 and one for the one generated
from 3.
Now that will only happen if there's an oid wraparound which hopefully
shouldn't happen very often, but I'd like to not rely on that.

Ah, OK. I didn't properly understand the scenario you were concerned
about. There's only a potential problem here if we get behind by more
than 4 billion relfilenodes, which seems remote, but maybe not:

http://www.pgcon.org/2013/schedule/events/595.en.html

This still seems to me to be basically an accounting problem. At any
given time, we should *know* where the catalog tuples are located. We
can't be decoding changes that require a given system catalog while
that system catalog is locked, so any given decoding operation happens
either before or after, not during, the rewrite of the corresponding
catalog. As long as that VACUUM FULL operation is responsible for
updating the logical decoding metadata, we should be fine. Any
relcache entries referencing the old relfilenode need to be
invalidated, and any CTID->[cmin,cmax] maps we're storing for those
old relfilenodes need to be invalidated, too.

Completely aside from this issue, what
keeps a relation from being dropped before we've decoded all of the
changes made to its data before the point at which it was dropped? (I
hope the answer isn't "nothing".)

Nothing. But there's no need to prevent it, it'll still be in the
catalog and we don't ever access a non-catalog relation's data during
decoding.

Oh, right. But what about a drop of a user-catalog table?

Currently nothing prevents that. I am not sure it's worth worrying about
it, do you think we should?

Maybe. Depends partly on how ugly things get if it happens, I suppose.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#139Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#1)
14 attachment(s)
Re: logical changeset generation v6.5

Hi,

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1]http://git.postgresql.org/gitweb/?p=users/andresfreund/postgres.git;a=summary Greetings, repository you can find the next version of the patchset that:
* Fixes full table rewrites of catalog tables using the method Robert
prefers (which is to log rewrite mappings to disk)
* Extract the REPLICA IDENTITY as configured with ALTER TABLE for the
old tuple for UPDATEs and DELETEs
* Much better support for synchronous replication
* Better resource cleanup (as in we need less local WAL available)
* Lots of smaller fixes

The change around REPLICA IDENTITY is *incompatible* to older output
plugins since we now log tuples using the table's TupleDesc, not the
indexes.

Robert, I'd be very grateful if you could have a look at patch 0007
implementing what we've discussed. I kept it separate to make it easier
to look at it in isolation, but I think in the end it partially should
be merged into the wal_level=logical patch.
I still think the "wide cmin/cmax" solution is more elegant and has
wider applicability, but this works as well although it's about 5 times
the code.

Comments?

[1]: http://git.postgresql.org/gitweb/?p=users/andresfreund/postgres.git;a=summary Greetings,
Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Add-command-to-set-the-REPLICA-IDENTITY-for-a-table.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Add-a-table-s-replication-identity-inde.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0005-wal_decoding-Add-option-to-use-user-defined-tables-a.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
�cyR0006-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch�[kw�F��l~E��7�6�`��=���6v}{�If�f�%5�XHD-3o���S��|���rf���.]U]]U]�cQ�������l:N��h6����9p��[G����z�����M�����zK�j'�O��j��G�9��*->�*�����"}����:������	�������#���>i�Np�S�R7��Pvt"����w�I�����#���;�������������xt��e$�`X�;VQ(�W*�F�n
��V�*d4�>)m�C��
�x0P��b����\{$B%�Vc�(P�Z���ZH�R�#�"
�	�+G�#��&���+v=�p��S��
�GA��%#�"�4h�������Q8@�!�|�J8@E<_i��A�4��s������Az$CL�s�P�3��$����AV���D�y�%�]���F�m)�#�
�c<d�Z�<0zJ�/��8$�SY�J"?������FJ���Oc����B�A<��m�����j��^�;�������n������B���y��
�:R~��_�PH3S
��1-�L����������)��T�����C6!�d�WO� ��[��v��#��������{O�m#������*hf��-��
��X���[*�X��0�X���BN'
C�u6Ta��tz,������V��������}� _X?eUV��HC6aKbVx����d�+�&���V0-/���r�*1[H���y��z��`o =��*Be+���#�����)R�L�cH��Lp=)_L
���Q�L�`
q�}��"z���j:��USa�NJ��d|5R�P
�_�O<��U�F�+��
#�IJ]����W\��/�a������Fj�N�����X���U~�^D*O��?c2��;�!v��P��"��J�����Y����o�|mz���������C���q�z�2�'������[:���
@�{�&-�����9������������N�{OP�(Ab�)M|���?-���_�g��>)PW����Z�_rj����f����Ut�MA+�\=�%!e������M�.)���n����Gk�]��'K����@���� [M@�����w��/Q�o�R�7�������}�:��������;ob&��)aB���"��x���.���C���s'6�w��m���<$�c�rV'��e����lj��5y���6�����<h�<�(BhsYHt�j{o��]
���Xr����'x�Anm�+��=^C��W���0\�;�M�A�?��B"���X��{�8V���s� ���;��������Z����`9�f7���.[:Y�J��[L
�_5�Z&���s��*��|ku��2��;���sDML�7a^� [����� �w���]��d���A�j����N�U-z�"�7���;Z�Q
9�G��(�������D��D�j{l-�2]��a��i�s4����R\u��{HD�^+l�ou���%���(�L�)����h6Q�����i��{��$Sq*�u|TC\��R�Y����F�����]D�62�1���Z�������7��7g����N��oBQ�>�	G.�X���|P�.�*��o-��7����`s��Tr��@���I��$��!%�m���4�V�:8��V��a��S��
�_~����q��sq(p��GA��J���o�Tn����������(�TG���"�x�0�����{��D����;��_�*�lll�m#/�bS�	������\��'���"����~�����o]�(�zTa�����J��R�m?���y�����`��������k������]�D���NELa21|�)$at2�
�����@'lt! =p����Q�n6���C����������1��B�J�q����Q��Tr���d����##C�4s��� ���"�^EP5`��
):���H1�m<O%F*f�X��1s��������ebi)*+�{qL����-��S�K�Q�/.�����0��S���p���J��������PTm.�D�0������U�~�C,�3�����]d�����2%���4�����)����8 �����`��f>_��������/���<�v�&�����ff�:`+���){�a���#�����d
��lN��*����J<�L�`�u>�����$)cDNRY���A�����/��VLB������W�1��$>�	��6�h�+�	\�u��*�|*j{J�\��e�x���cj��Q����5�b�uV����S����o= r*1L���F���������7n���y�M���b��!ts"�~>�uNo����� Y�����TT���3y��$����0�����U������@0EW���L�����QC}��-��^����k0�i>P=�	|E^9"c��o<q(V �����������L�33%�L��t�;eC�C"��#ZG�V"�L�Wq��d���}��s��Y��nb/r�H;*��1pO��Q|���7rRf��u�*��Ua�.����$0�	Ha���B�g��-��:#
�MQB�����;~����?7�c!q�]C�������c�Bo���TyQ0�d[�X����Q�BW�kDG���e��7���O��� �����W�}������bE�z�i�K	g�\��5�n�o�E����8���_o���I�q�MO��)���/g�+5���8j�N�����qH9�[l��'Iz��L�zz����������
h��'��s���
|cuhs ���H~�=���B�_�M��?�@�r�F_#���VL���
�z��XV+�8,��f�)j�ju_�r�<x�6��7��������$?��������}��&����O�O�%������s)�U:��i�;>W������B��[������#�U4�P$�� B;~y}y�3����J�}:����x�vz����k�o������������}3�c��&��P-��Oc;���5<���r��9���@/����!�x�(����$�x|t��j���!���gq�
\��w��N�*��-i���7g�0�����K�Z4���#y�XQ����kY:�*���������o�t��x2����n��Wd�|i��3������9?f���fB+���u��>O>�>X]i��������s.9�������v��@w���c��:��W��%���+�j�3��0�l��I~
[�L����3�����f�/'�+RB;��w]�8(���a��M�j�hV�f�\�c�U�Fn�	��c�WV��s��<P�����;���666|�MI$�s�'���'�HM���gO�l�����6i��� xIa�KEY�����E�C2���0_
�a�#��"X6�,r�j�����'�u������
e�����#�Hi�����yDiR\��O�;x"�p �J���G�?)������L�[��8F.���v��	QJ�*z#��2X0S�*F���#t�������!���	��$a0D9��,A��=��/�]�<������{�c��B�C��4������_���]��u��2dNt�y~wq��;�lb��m�s�r+�i&��~����}�����\?�X�7�m����h?��Q�t��f/������e�U�k����X�i�����.����7��e���^a�U!�gI~55z�*2����j��0n���C�W���UM��
G����q����A�����F��t����z���0<���1�DxL{���
���3=�N�s�.l���P���W��#�QQ�G  ��{�\;��"J?���������u��=J����M���/M}��0���������M���
K�=�
���SMG���������A8�^9���(��k$L�e��7�.���0��|H5+0f]�t�������BQw�2]�dx,K�Ah�@�qn�4
�x�(oU�W�:IZ����T����f�~Ii�P��	�>2��v�*������8)a�C�f����*��R���f���4�X�1�������g��C���8�VGh���
��Z��V"Y^|+��/�R��J������x�������UW��$v����������>����w!�y��{��r�9�\��_����x��C�Y�1~S�������g�H)���x��rL�	WKn�u�@���w�>��������K%+*�����Xx�:����z�������V�p�|��F�&�6+{�Q�{~�����%D��U�$��|
1��w���.�)-�A;e&�@��4�������?&=�	�WO�q���J;:�`Q���'���#3�Z�K��v�4e�4e�j����V�'�.iL��1�J����~ �^�y�bz����]_6��/�5+��hX���M+##�[��KKco����q)=�����{�o��|�"2�'T��t���4��46S/��
b���S�K?�U���DN��.t��&}�� �'�����������	_ml��D��$F�2� ����b
�`x.���0y���b�0Ec9n9�	)��XQ��)��P��-������<Q���%�nB��$$��T�i���?b����,N��!�r)���v
�(��t4��}`��9R���"Q��D���p�^��`��v�y-���<$�`2&WaO^�t@�%CN�L�">���j7���s�/�������k��_v�y�����.�����O�(���'�����\���=+d]��Ric� ��o���������zi�������Ph�z]�CY�h��n�����UG|��o*�;T)�b��BGG��w��r��m;z���D|&mR����etT�zgt������wD��~3F�vN��ub�%N�<$7��7z^wm�\}��|�����]P����d��g|�)�7�+0-|���*q=wLP�I�k����������q��[) ��*�����a�s�w����r���{����6�mX-�S+x�t�������j�k�]�W�L�*(��a.�1o�w^�������1s�x����}�Q�����2��=��^��%����xy��������_�w��]QF�����F��u\����,[:��S�TM�eh�������L3/�����>&���RcAt�z��v���R[�(i�R�S����@��)���x��+�8*i��S7�G�\��i��[�J�aD�����1U�vL-�����a\\�"|�*/��EqT������	�I���|&�j�p��M���M������������8	j:�zL�x|Ju�E`�����;��8���|�x�O�������J������{�(}���uo>	B�f�>^�2uHi;t-����J��g�o���)A��\�S�q�c_��."�t�������{g�������u���x���u��W��sy}3�jz����\������x��}�|��D�{���[������n/��#y&�<b������&�,m�l���u�	H�L'T��O�m����������R)$l�p~�Y��b�CV�[�<��c�{�5�kRh�M"�����������.2�HS����"1l���hU��C��z���de��|J��?;�Y{�rsK��fV���������!l��7���;g?;���rtr�����������������,��HS��n�����������{��~�K����z<�(k2uE��x$F��v;q|txp������?hTIN?�%Bw�w�5�Q�1���m��b�K%�J��a������li�i�wG��'����<v�===�xp��.x�zg��'8������������0��������������MK������GoN�C7��0��VG���K����t�c��u>QU?��X�6Luc��QS,H���)�����n�1�Z�Mgw���~���W���������Zo>������s2msP��Gk�fw���r���������%������������T�x����8�J�)L9��NqmZ����i��"�3���t#��j��AU�t�����*�n/�!L���KcJ��.m�T��a�.���O��L0�*��e;c�����\���)i&�n$Rh�qDz
��Iv����,���XN����Z
�g�fW�NN�����M�-u�I
�6�����Aw�7W�D���FA�iy�?/������+�^F��3��Ft���!Mw���y��o����9����,����n��T2d�O!G��w'���<�L�f�N���3����4>a)�Jf�_Rz����1��Sw;(���nGg�����������"�6�H�����;L�K���:��og������4�w_����K%��D�+��h8R��������Z������=z�Tsv�2�,����.���S�:<MK�X���#tsAD7��g��4���K����S���
ln'9��������W��L��V�P�w�0`MjK����]�^�����U
�KzgP9�?�8�$����IG~S����!t�����P�a��F�D%^�W'�u�n:���������P_e����a>��n2�'�J�#<���E���k
��E4o����=��#�o��)�x��}�	<�(E�.?�m3�������"�@�u�AWV�����k�/��D� �������v�<��.�%�8�/T���(j���4���.a�mj�s������������jp����}&�'���V�5$z�w�~���S��iX�����S��7:|_a���7���>���24,����"]��	a�8U��o�l��S���g��#D���T�@<���w�� �a���.���(�.��_k����l�T@��,��VQ:\�e��R�|W8���z7�L���u��kL�h�n,�8����_���m)�h�������v�%^�b�p�������JG~�{�����7N���������a�Z$)�'{;��{�\���;\���4��<���R��������[S���$�2�1T*��+p�"����)1^9�@[Mw���qTP��W	����B��v����������O%��d�
�1���Wj�����`��V9j������+�<���������o������>�';o+�)H8&�3�����������Tq�r��[����zr�N�F��%�K:���B�n��Cu�=�pv�jx�"�)RXF���#�Q%�g�}a���S��Lz��������]3������7����d409��H��+�I{�SD(��*�,�.�w��*0@C^����I�����Kj�>�U��|dn
����}���.���
��T�����)���q�LF��0t���g��[e�������fc�i��f�2j
,�����K��9{M�+��k��|��������-����ls��-��V��o�M|�Jd!��S*�}��
J{�:-&��Msi����������P���Jw>"��Z�����hD9�
m5�y��.hml�-E2�Pt�WJ������S!` :�p&F*t5�J�~acs1�:�0����m;5WA|{J8B&��H�H4�J�K��_��p(6?;v?d���q���^��{���6�*���{���(!��9�~��Y0T� Fy�9��h��c�(!$mE���_���(5��l�iv6��������c��G�E���i���
>{�S�g����f���p��A���cF���9�`��iY|EZ�O�w,���x�"�Id��5]��}��-�*�?�Z�)9r��B��������L!��<Mx=I����q����m6�Y�*��?IG�n>�t�����Y{
�#�mX��F^�4��[-r��(j�O������+i�E5�F!�@��>�?Zt�Bs�/�dE[�U����
Ya��C>��>�#�Q��ip�sq����T{���^����{J�J�8�H#�n�=���+�<�������i���7o��,:~�����m��~8��@�]m���8	������Po�^��Qtg�o�)]v���w�Y�'�89�7�D��v�~�\���q�B�u���G�(�K�]r�{��bd�4v�2gXE� ���f����k��z��P���5���'�$����G��\�����	��1uFS�b��h4C��3�")+�C���#r!����:���1�+�0Zg��>�����N!*wvZ�����}qE��^^u����[aD�DKQ*$H����%����U�?���v/����!R����2�7�b%h��^�T����(B�����xw��JF;��U�2���>zf�m��F9�`j�/O��)�O_VZ�W��7��n�-}�o���Z?���,��cf�W��Zx-��=�������q�37����>�����5�s�B.�Z���u����Cl��be�����,�n��87��H�>��h�t��;kNN�.�fAN��3��
��?�4�����Pq�8��@0��.B(v
4�[GQ�r�%�	�z(�y
���������*;~�!������Dl6����;�������x�7k����7$=w�9r�M�������n��2i�x���Xrg��6)W������J"���m4����5������{�&���+$���*4����1��
�LQ�V��^�����X�G6�R��J/t��ym�Kaq�\�����zU{�8p��6-w������d��@l6����$��/��`�l�k]�9�
(��`I)Ch�|-�E�x4D@7�����Er�d����({h�A;L�k�i
�$�-�5R��;�Db���~��1"��KaP�g���K�%v������"5�$��:]A!vDV��w���;�o�������
����s�=`�a�����*1}�����(��lG�����3N�Tc&`�cr��i9f��JI�N�#V�%y :����NA������[��FL��.1���L���G*Yu�F�`� ��y�bc�LE� ��<6w��{W��i�����ud,}�=��'�O����B�}�8�V�]�|�:F :zh��"u"g,N��U�R�&�N,-:T�#�0�$��|�� ��gx=��!�7���!,�C�J����H}��8��U�>c�]�ha��3�<M�(|�(��a��2�}%Eb�e�VFt��}�(�D:����o�����2y���\
P���ow�g�����������9)"���*S�$TuY �=&.�
'���Th-;_��%D�wE�4���I�7$���:e�OF�����u�\�$����F)�
��c1�V���������������wj�-��O^����Zm����0��b��6�`�lZ���������ds��b�wG��b��s����iR����~��}X����U����t_w%=�\x��
H�*�e>l��q��G�w�(�����|'y�oa���� hz�lnF�A�F�QF����G�+�m���$��|�xkpJG����W�|���$����@�U�&�k��������� q���'�����U�r:j�#-�_+3+�m;���^���z8Ln�~t99�g_�/xR�+�+����@jY?��� H�����<fT�����M�_`��"�oe������[����x��]�����"��m�W������|��C�����[�N���;�������w[�;[$����8�e�A-�����3 nK[K,!�$j�	�aJ��HW9w+�(��
U�������]X���-��[3M��}�����G������c��	�����<%c��A0Y�C��7&m��A��������PB
N��S���y�\tK��������*m�2�$AM���@gq9�G�}k�3�x���h�����D�x��O8���Eti����_����\_A3q�1L�WUdS�������)eG���&��b8Rd&�aEWP�����A��7���c����0�GeXd[�d?s�S����ui���L��H@�/�N��6�?j�Y�V���B�L�%��"�';=��bq���b���Q�N�W��lP����T6Kt�����r�(
��R�^�s��3��+��j���4�[������=�N;nq���*�=�Tr�&��w�{|ry�����e��{h*���=��{�j���k�n8W�����|�*kz����%���W��s��6���4�2�W)gXk�g��8�?sRF�8��fU�`��
�<�$�#�=�*-�IA?+�y��Cb|�_��'~�=x�D_�M�Kfv��R05��I��3�QW}w&�4�6���c�F���4��d��"^o�"`B��TJ����FD�g��cb.�h����{@juY���{ip����������5�i��c;�05��)q%!fe5�|,��Uu(����\�b�+��4�XA=�a���M��(k�W���9��7(�n�����}��3����=B��JK3��A��g'�����`��pD�T���AV� �a}��@��e*����q��0�%��3}mu{��j���tr��&�����LL������
��
X��BC9����)���59f�9=�K7�!����
��ir�������&��B�t�S����i�����db�f�Q��
\�n#k����l���rQ$�so����[�d�r0&1�j�?�L��?m������FcN���U�m�^%�w�>���?�qJ���%��SF�d������6FG��!�q�o-%p7��s���98�5�XR�r��	���z#R�A���������������D�r�j(1;jo�v.�@$������U��)��3���{����e)�Q�b6u��1z��ht^"��S����C�f��x��������u�<9e�>��������N�/h��P���t;L���LP*�)��6,2%)R6��	�Ii._@�����;����SY 
��m�v6�y��;�#���Ew���Jg���Kw7�G�)�:�����7����+����Y����~Zsr���_�J�i� ��`d���z�Zx�����U���f�/L�h���\����������|�����Ks��������M)�x`��3
�F$���)-?���� x��V
�����BaX|Zb�.�'��j]W�U��M����z��O����=��o�-J�o�22���9FORW�/��$��fc�RxN
D�A��\�v�����/@���k>��w$���wpt,J�X�Y��_+����%6z$_�:�g��(H�KQB!W>��V_�hKK��)Z��x��
YV��h��cH��
@�W��$d&1�zZ��I�4�U�jN�?j���>x�(#��2�#��N�r�Q�Fa����GNp��b���<�����$Q��1k�XAL����
�&�Q
�_Sjtd�?3<�T���k1�8���0��Z���/�mD�#9�/J��J���
�84��$��E#ryhk]�@}��s@)��]LC.i�`n�/�1j5<�Jeh]���>0WE�8Y�u]�Ew5��9�e��o�����n���^3]��"[V>�Q	��t���l�Tw(�/+'CHw�W���B����:����]v*��������J��J��SGs�G��q'aBbtSrH���}r
��p�|�`	��3~�=	������t�^M"e2�l�C�p����Av)���4�Q�&}�����7+�5�����������QBTx�2�������3��Z���h���PQ��\����-R�l�v�ibi�=��H�3SyB��~����g*t	��Y��E6�&��q��,G�QD��
��aG���*��:�f'<�jZ�B�kg���U������iF��tw�����ck�Y��_�;To��MW	��0��^z{����\�\S.\����4�/��]lI�����Q;W�!V�0�sv���b��<;x�=]k�%�Z�%�E�i��)@b&T��t�Q�P����O��Sl*�u<���3���wa�k^L8��;<��[l�)�\��!�V���*-N	����(�RD%�&�i�^�J��j���c[|	��Io\wU{.q���W�X=�3lzg<MaS��|�����0�����%w��"��� Z+A��WN�����^e��su�+�F��me[��7�\�8����9��Ji��I�W����U�k���5�Up���6T�" ��O	���yK�:���������gq���g�}1�<=�AB���_������)l�C"p�2^yO���G^�_�^�5����%t���.Z���q�;�'�M���_�����J3����W6��O����='�n�5<���nk���5)��h���������������y����up��sN��������>��8�7�|�a��l�(�r}�E�P&f/��������vOB�\��1�����O�0�w1X���P{��S<��m�s������z�lb����,KG�+�w��u�d��]�P���c�t
�TG���`��:`v���$VH�h(��8�R�x9	�XGQ�a
-���;KX�{Q>^�s���D��b!W��M��54���p �s�Y��g���s���L�=w�������9�b�.�=��a����{6a	r�^��(��L��0+��������Cr�*�01�������Z��;�a'���������rw����"���=�N��4���W�����"0[��\���UKp5������U���r��������1b�d���[j����X�i�A���7�������N)F�ux��H�
���7^DmUJwqK
v7�:?�L���[Y]{+���Z9}g�1F\����_����
���"� �&:(@4��=��fK9)8���&�e��	8W/n��m6����QZ�D��JI5�Yd@��L��o�#�5K��38v+7h��q��^j���ZZwhe�:���3���),�+}�d��AB��e��y�)�H�����U��x0v{RP��4�&\"P�#�
�����n&������{;�;��G4�h��;���d�������p��"�LEg�AE�������<������xN�@�E�����PR���M��_\s"'L�1�\���<+y�4d����94��`p���-���p��g�B��)3�����#���i\-	;��7Je��C&BX�+c;\�:mNF�n�	mG\�Q���4���I
�?�7C���n4�.���3i��qrF�T0��z�W�14EGmkz���
>�(<U�&{1��N)<n�x�'�H���1���zb��!�hi�\7~w���F��!�%��o��f):�b�Nm����Y��E
�L��/�k���G��u�<�@Q��E��5�^1�a�rr�:�������y$�{�k���j���-���2��i�&����	��-��.���*��f2���6���IdH[�o�Cu�5R���A�y����\�L�����������G{�o���|���������4�gg>���9�Y�\��q�Z���&>u"+r�_�y���6�XKI�~���J��Rjjr��yL[ac�\�������Q3� iR���6��=�����,�h5�n���c��4p���\5���}V����WKmb�Q�cfV��-#��������.���^z���A�2L�������;9j
���8�\&��&�ay	��Uc]���������:�h�g���EfS�� "�.a����n_��kgb4��%�j#o���#<������t��������ZI�T��C�?s��)w{�*t���[��j������b�%
���Y}6�������v.d{���1�S/��"c�j�E3�H��jQ�6gQU4W���N>�R)7W��?g���������N�F��s��
��i����J����c����eH��	�1K�Qj��wE�����P���������{
w��*��z���'M��,�2���rPd��09�1���nK�0�ty���G{�Q�Z;��"�zI\�y�����mCC���s���0&�x��Y���0{4&�3�>lbe]Xcqw&�LA����8��N��3�i���df�S���p�p)
;�hi-�(=��	�%�n.3��������P� ��B�:�-V���	i �e�b����W!]�����o:���33�pB'��n~5�}^5��*���w����������3��uu����W�.;��ak���JP�������23�[w�m�.|�o-A������y!�]�Yf�*��#��iF[J�����%��a��^�82��d��9R{����H7Y'�!�Q���l��2*� ��<�F^�L� lUa���8C���PC�EX��7����.����G���e~^�����i4�8	>�`Q��fb�[5��z�i2�j+���bve}�9V���ZV��"�iv�p�5Dz0�5����h6�rFC���ox�T�S�|���(2%���|])������
�O�F�����w>��=�9ga��p���
|m�p�~�b!�l��H��?W>%����r[�I�o��������R&��G9�#�:�^�@@4X$K]�[a�L���9Z���-�����z�/>
TG���g�=��8��!����X8��B�{F����r��G����%
BG�'=Af�T�e��-��i����!�[������Q���z�]w�T�ln:S�N����	���~���U��N?~��
�����n���;���4�36�t�}��Z]�������j�
)�7��.:�*zM��}�V����F�[��f�v��#k=e���T��He:�R}�m���8�
��w�w3MW���d�G�2M{�����)��dG{�~�B�s����H�~�E�����0~o�Z)v������j�~l�~��o]���:Wki�}�������q�
k)[b*��bh��0+���J�	��j.�$9���w����/���O�����m�>8�9���	��g����Q���C����g����f��:�l�;����&s�����'
(���4 ����/�-y�����_iFz>�<WL&Y�/kc���9�JZ��*�����'U�c*����[���T�
�l����6r8�a��7T;���H����$�95$Y?3j:Wk���������?�<S���&	��<�=�a�c�0
���L��
�mWYDt~+#����O��_/��4����L��	��
q��9��>�W�s�M���;�������CC5�����2M�u�S�������5����V�5��k�Y�I5?��t�3���e����0�W�Z\J���[������;�r��](!F���|�1�u�	C;~���5�3O.qu,������C��O�vvu�)���$�+��d��QQc�D��(PI�;K���/��v\��Bw���c��Z����8!V'��g�!�K��%8K�Q�:O�0�(��(zy����y����]'n��x��<��[�#��cM&W������A�q�3���m9���u"[%
	���+��KB��q�t�"�8�������./�[x��e�
�BA�j���w*�5�a�
�Y�-V��j�	��>e�le��Gr�v�vV<�>�,M�C���b ��?%�����v,�6�h��������t�K�P�_@v���w�����M\�����S4�3xE*s���p��Ix��y�wB��Gb4X6��H���x�;����G��\�����x��� ��6�F���������3����.}��d����#��W����$ �HRq�o������i[��y�0��@�V��H��M��c(�Hh�7>������F��P�f�����4�,�|r����,!�_��:J1W#����(L2<^~���_)oi��H�O�y�O�mX��O�j��G�.�������UwU�LB�D�s���;��9�B��XN�����.>���i��m��	-�Qy��2 p� �L��uzK�@��'�"w,��:?���"�� :-�u"y+��J�u�#�Z������V���U7��Q��ZU�TD�X�+u��j^kt��7s^��O��?������������k�s�t%8��jw���O��D�bBi�&��������3~����V>�V)B��>�!z�s�
�Z^M���5%A��������1u60n�
����p2>���p������?j���b�fr|����1��A��^~�����`�H�OS=�f>n�/��pf�}Ms��N��64������p=i�!G>^u[��!E|�@�=����X^�������o���������.�Y�'\����T/�n�������H�� O��0�~�xg�������w�;���d���Bu`{0�cj�&uVt��x�%<6q�c4���l���!/���HHx���L������H��H���g@���3�~%��eT�fvl�z���F
��(�s�������|���>j�o���
��_���u(f��
^2�u=�
x{���*z�;�.=��IU����"3���T|T|��2"�o�G=�z���0!����o;b�^��m/�Aw]���X�~�����Qu�<]�ch1�pB����M��RTr4�_w_���o�\!=to��hl���!�?�?Zd�t{Q�5���n�Is�s���+/�N�yYE�m	�Z�������D�������k��^�������]��������������������Gg��?���3����e�Aw��5�~�QX���!����N�0��n6�.B��pb����VQ	/�q)`x'c��T�n^�$KG����=�O�t��R�$p���<e�������
a���(�)�R���@}+���6wsU�b�((T���F.��v�)���y8�C����&PV�U��	�;���mX�ww����V����I��OppZP��^���[y�;�M�L�����]ai�u�f����3�*b��%T-�����NG`CCd�`o�!9^��,���	s�R�V��_�PC������U�-�W ]���n���S;~:v�@���6���n���h���TE"'�"�m���:,�C��9�O�I�
� ';z��w�����*2���2 [�!�T���G�+��,9���4%��5Mv9H��i�u�g$�s�G&	�/��]�;�V��O�gG;'�?�!`�������9
	��dm��z7�Q{C�
6�`�
�_>`���?XF�rRz��wks���g�h�I6���[�����H������N�e�����������%k�����V���O��7\��o�?��*�����A��������o�3#��G"�
oG����^M�ln=Y��<m&'�r|9����0y�h[{�4<�H�v4�M%{��okC:4c���8���h�&�Z+Cn�\�Z�^�4)`�w��^������u!��0�����v�>��rw�E�%j����v����#c!;�62��%xr�-J��� ��D�|�8y�z���_����gH=���~3�l!i]f�9�9���~M�J4��	��1&6rB��	;�>�A��^Jy��
mr��vU4W�F�2�n39���a^d{���{�,��1�hf�`R�7��!���o3��~��T\�c�2zS_�
�}D\xk�L-������h�`	��c��(��c�~&y����Md��p�:|�!V���H,��z���q+K,�
�T���K�������|EvP��}�ug+���O����t�F���6������y��`��	2����vaov�������D'.��^h��H=��� �A[jp���@��_wW���^�e�F���}r���E�K1�zKKqP�y��M�ac*���%�����d��T/������k��O�'r��?����eb1�P��,��jz�\!�u9��{r� 8�K��s�l��EZ�US����-�W+k:u�/��j�y�E82�:�8��]D~�?��]��fz���#?v�
����+��@�A)�����p�P���@Z�T2/�>�W��.���PA�R�m%_���m����@���k]�����M�<��n��n��^����!���� h\"�noP\�����`�e+S������+7�c�j��}��K9Y�+E5�W������"z���>�a�%$�$���?{� Ty�/�tp"z��r�C-���H�.����T�)���=tv{Y�m��|��+y,]�����Bc/'����f
��
��2��2�z���L9��p�z�1���)��]�Ts9:z�9{;/LK��������n�5�(��y&���V>��vX�;8�A��Q��7W
}0~H�0��������INE��W�e`�/�IJ�N�BKz���z�Q��� ��rN
�����w��?�������|89?��~zR��l��v�0p�g|�� (���1��K��>�r����Tf��Y^�e����GA�I-��C��Vk�w,�w�
Q70�
5�s/N3.h8�����=�����C@��F�Y���sB��{d�dM�aEs�u7��M�e�\�z�M��[�oLS�x�M7I`�0���
�NdG/t��
.�t�)k���H"DZ:���}@�<'�5�2o���m���K	�V�l���.�������
�;�?���s	�ee{i<v�_��]�����G"���T�����?��Is3����t��E�����2��Z�Hk|;��6Z����LH')���o'�8o-tH<F<���A�����?)i�R�o�Gjr��@�6v�[h�6/�c8�0��Z\�d�|�
���(��3�!	�~���u��+�U~?��v����p��������q&6����x��~m�n��
>��A����m*�d������q��B'��#"�����m�*�e�[w��9:w���H`8�l�����4,,kIa��{���`	����;F�F�vS�#e��8��h�����l�����y��O��5A7��m����F���������/��g����S��u��U�Z�UF������F�]4!��c��F����(�@�"���aUey��P��������Y5�������\M���Q~������&a55��&��Q��4l"c;�����pP�����E��Ug�)�I}}��~��"����SBM���
Ef���m�3��M+�7�I�
�P���;���;�e!1jx���s������Y�:��l����|_+��Dtw�|��CF��]�j^c��t�u����c���@�"���71�w����c�a��0YM��K�T�UWM(�)�*�H��*�@�FbM��+`VP����ICX��l�f�_��Xv�7?�N�J�3����t����Qko���{�%2|��
�d�����a<*����F���QII��,m_�3K�T��L�]�#4M�d]wq��g� ����������3`����]��?���Bt�D��A��	��t�M����p�(�L�y��3��������0��9������s��[�a��/�����@�������0���Kc�-0A,��hp8(w�q3�fau~��7�����`j��''����rp���,Z)���'�`�,�
�!9ec�CA����+t���:��7��������{��'�����o�f;�:>3���5�J�����H'���PW�@��UKpk��!,1d\�f�c���OT�v2�yeA2�u3n�b��+hwy���W�����_:[[����7�tGMB*�6���z�qh�Y��p��KT?���f�=R~+t <�����+�����Z
��9�&���}�rZ��j("��/`g	���%az\u7�D�^Z���O��C��H'~
��1�^%�������\P�v�*�8_�o���Rg�������X��6���3���dF�/����������Y�[��)��q�f6* ��F�����	�Q��deJ��O���X��)�x
��p�n�Y)�yg����sn���ze�L���d�_7��5�)l�����;P�X�_s��>��]yA�5q� �s.����<���lu�?l���-l�qa{����[��>w\�\�<�M�����}��c�0d�g�-��p�&�����W����_��z�!Qd����RR��e��(��B�3�FC�r�������+��O�����pe80n�D�:��|���nb�J��:�&�H
��e�<��-)�N��F��E��l��E�'L(���L�h�w��������JSIm�9��~�0���F�3�p�
z�� �_7� �V8����X���%p���<wW���\�+����W�EVGGZX`����~��9)*����EjPI�wX�,(*����=��\�Xf}#�@/��t�� ���,",^�r�v����3��}�������4����jW.��d��$���hH��fVm��oS
�gkf����a�"/0��kB���mH*�>������H�-���+�w����1l�>�g��K�MS^p~sBh�rW���� (/�N+�a�x��h.���#�!�f���R:<��Ap�$*����`�u�g������
����C3�O����D�9�`��{1�t���D���iW�Z���)��(
��
z:4S��V���:���������&�2�`�s,Uc��:7`6�V��5c�u��Z������g` �
���c���YS�������M��aC���m���Pq�%����N�'MO%��yP*	h���.(��
���=������wT�dlnz�~a�FW������`%O��v�Y,v.�)/B
�2�NG�[�0�*
L&m�����Y
&uU�'��s�eA�)~��#�t�1lY^���2I_�	�Na<i���\^"��x�K��
����b{z�Vy��/6�����'�sN������ZI6%����P& �����Y�f��zs���TC�����_"�N
&��0��vV�/�v���e�K�#�V�B��^�8Q�4��%�)�EeU����3X���
u�������
<�W��L���Y6L4 �3��������
vqjv���� dS�Q�����*���������������������}���}�� ���|PJ=�_C�Y��@�����<���s�z����I}�������Nz�W���S�C��> O�u���=���hoT�so�����E����� ��f&�~~;��%;���};#$6���Gi��A����L���!tO��Wa�IV�auC����{�������v����g�����/TsvA^�9
�b�Qc����������;G{���9
�89��=�[~�G��k��m����v:�B��1�=��"���9���5P���il�qV@rr���o:�(,��T\�����8
��]��KA�%7;A��t���x�����|2�\����*�m��_S{����Q�����y���=lp0����+���!�+�����?O�f��jRF�C����
u��7�����`n4"��:15�w�����������`	b��`@g�f�8�$��>���2HK����iL�M��	�Bw|B%���#3�K
�7��2!�#�/�)�]#�&?�^'h��0,B|nO���Y��7���^��vNOw��z������?j�A=�ec���C����l ������mY�7bt��0R��2��v���D�3,2	��K�K�%r����'v���K��M*l�u��_K�Q�9����$d���bi+��8+g�$�	VN�`�c,i��/�hS��E?�d�G�$bL�����i�7f?EEr���VVm�r��B|	P����!���:��c�lY��B��m��L�m��\7��L�K G3p
�����/N�*W;Ha���q;h���������l�%��h�0M���������I�/�����9[@j�4����/%���q��k>\T��yO�h��(.]�A�����a��wu�S��v�.�Oy'R�����G�d!�[$?�e�S�U���|�J���lEq���&���,��;��Zx��h�;tK�F�g���Q���(?�SG����0��L���T��2]>�Q����.}���
��{�[B^!��Wo8��iW�Z9B��Y���%�v����o�"/��w6��}E;�7Y{����\h�����f��
�xx�����w�%�Q�W��z���U��k�]7w�N�1�Sw�C��.���j�]����r%�X��G���!�foa�9 1a}SF��n���<8�4Z�*������>,}���5��9�T����e	�cw�{T3/��[=�w8����'V/��0������h�D�;E�Nr~�sv.,�����eJoZ'���h!\��e�������L���/����xl�D�1�01���vp�wp��{��i�w���p�������T���S�����������h/x9�+;C� �E��.��q�I�qdc�����O��K��[�(oA���n�v����.-����������A�U��l��d.�'�/O/Nj�dE�b�G��%�i�[��C���hZ�q������(o����e	�W�m�*����{�p��#��5d��W��F}5q�n[�	Q��z�S��x/{[Z���nI�bRzR!mS���~�M��)�������������.nQw�a3�/���$��4�Q�������S��]�>����8
��{[���j��i�n\��J���o��e�Q�ds;Z��bj
��M�-8a���F�B4�H\B*y
�$E0
:�3h<������yl�h��|�*+�B��)U������Bp���c�[(vij�����2��c��E���lm]l�DLP����:2�\�J�#�7�#��0�y��J������F�����>q�M�����F�L
Ty4�W
M����a:Ue�g���	�U���/���,!B�'�H�/��Fn�|J+�h5{���F7T^A9L��qb���p0$Uu_6��G��	c?����P�q6U����Kr��0�9����'F�V9�����U5�Q�uS�QL���XT�X��;��>9�^`�qu=��OF�m���I��2�d!W��pnZg����:-��'�N(�r����v�g^&4[��a7���Q��w9�\�E�+������B����6R~U�QXOms��\3�����Z���=��?�*p�R����S'�pO`>�t�l�f��������s��``�G�a����G�{|������m��a���3����?�v�1I�j�C�_�������9��J��k)o����F��9?]��k��2W��B����o� j��;�3�%�I�eW���zN�h4�;n%���]��U1�
�����m������?�[�%����0M�%� �x<\��a��o�Oc7�
������i��C�
�E��A��U�0�W����n,�������U�Q�C�{o�n��gA�
�����z��x���}-���"U��C�"�s��R�/�$�wo*��E����n��&�S�P��4���HP��c7����Pzl��LM
�����\��{����S�%i;�7E~�"4���d�%������������%�]����	�m7�	��n
�%]�����wc"��.��WIfn�r�T*���-I�pT��jv��/'AF�
��y����rG�e\�77a��3{�����E��;��������(�
J����n]�_2Up5��E�����a+]q�����H	���XA�9��W���&�\N�![;�5��=<;���]}�G\u
��
G	�\s�V���h�*)���&�\��������B�r�ZV+��
5��t�U�����G9
��1u 6���/(	7PC@;E
�������6���x�e���-�5�Q�T��kY��x��=��'V�jU/W������b��b7��b��-7��
6g�u@�x��MV�K��+��6�/�/�'E��b��BZ�u��*��`|$��_1��0y�H�]H;�
�2������V��n�90v]�QX�i����?��=`�P^�����U����m��0�.�<�k�[�

K�w�*�F��61�ej�i:j_�����K@�u&#�AI���8�������l*<��P��W�� {��(&��Z�m���������8qc2�v�]��dLF+���D�$�+,N��i�����r:qt�����dk�g�H[�t��K
����5eth�^#>�����C����sCb��a�1@����TU�-U�Cu������\��_�lX�?>�v���9ahz>
�*q���l\�Y �b	��*�vh��{�w�U}��$��JC;
���xx%���0O�`a
7G���N"wbp��kq5�Nd���;BM���M�l�����-.^������Gg��6���V�@����k>=G\u��������Q���?bqg�%���	���K��
Sm����K+����gC�K������_*4�A�����3���R�K���8L��������O/����`Zg���)�6���%��|.��rr�!�<E���V*"�����1������Z��e+�����a�M`�&��CCl\P+c��-m�'�S�X
��#$>B����[�f�e��q����~����@�u
����U��S:RuM�
'�zO,*%��2l tY���@����RSp�,q�Sk��9�#��D�U���m5����}�T2r'CQ�m��.5Q9�t���P� ��Q_����4���T�w�6d���d8t&mn����g8���)��e��T��z�Y���])��3��t?oc`���n&!�W/�I�Vo�z��e����kY���q���i�5�R�����tBZE!�dx�`B��&i�J����D����J���;��S��/�Q�?k���� XIV��9������)���+1�L����p�����6�V����3B�e�o(�;T��0`q�cBc�8Ma�a�O!��G�������c���.F���Xm�O]������h@�RJ�"T��HO:9����(����^�����|w$�,���*�^:�$o_�v�g�������a�����P�����n^�=e�L����������K���M�[����Lbd��A�~d�'\?,�j@8��B3�[���#A9T��|���
Ae�L����1�
��R����p����T�^9�
3w���U��L�qQ;������A��Sj�4����	T��'��9r�`u<����{���'�Q^"Z��C�J��������2FsM�f��`eb:����H�hE{��Y��]����l��s���g	��[��?}�HW��.�!��b���S!��O	}��S[�bS���Zn�[(�wKk��W��at�`�����,5����F�v�q�P���/�����Q��Y[�j�H�"bw+'m3�ppFR���M{�I��C&av����GJ\!�,�/�� ����7Qx�*i�� �Am�_�����"�^Km�
WV��C7�|���z���S'l�L����%3W�V��?O�d�T�REQT�7���B9#w�������!�;	>/�*^n%����Q���Q��
�40S�3AI\sKj��,����Z��f���N������a�����ZW��s'p�U�Pxf��b�g�`���������	�j 9�1�-�C�Nz��-h,���z�c�G�S����?*K� Dv8��eb!�ZZ��w.�� w�~�$��c��a� ��{�_�����4A.�����(���I!{N���lgC����U��JO8�@[eI�;p���/"������5��T��1>�	v��#u��zWy�g<pz�b�Eo��ht�x)��eL��-��;Kw5�v�`���j�����vk��y�/��������Nk8����b�a\�R����dvk�Go��o�U~�n�����N����O�n�o�`k������^z`&.��n�����M�o����j����`�����b�o�9��f�X*�H=�AN��Xd+������i?���T���b����&��#�X���=�������`��?��g�h7h��^O���RRw���\��NE4J�2h8�������C�.b����';��8��#�ms���9�v�l|>���P/Eppas
Hl���/[=�x�_n������5����	��::�k��<��&4M3��6��j��7&d�A��s��k3y�3�K����������V�����a��L
\�H�����������4�2�av'
�-�hMSMQYW^r��k�����IU>M��	��ZL�y���_>��3"��6gO�������Qk�.�Zl�R�������@�a�*���B����(gf:J���
6��&��
Bg���������MX�th��
!�	w�������h$Y���
7�G����b�aXv��fZ�c_��*���bS`N�J����n(�q���`��/�m|�(+��P�Wv%�X�x�Pz�#E�,8=d���7,���U�*�Y���~y?dY��VCY�g���)�-��]h�-;�)�����N7��k&��9���*�?bD��Q�pB�w��B���F��A�1Kk�l�Evg���c��E���.{�?b%Nfc�8Cl�G�\b�+�
H[=\�]�E]�*3��?�Fk��v����j"H�{!������R�L�I�G������;w�|�/������ROt'=�E�4 5:�!Y��\�$I����������>�������N6��W;mr��m�f������&!������^�h/}���
*��{�I
���qM5J��?�p�
T6�Q�tW��L��[B�J8T�K-��t��Pqy5��>��eF���C���|������-�43���3����l`��2�]w��������7�I9I����K%��x$�(��pYg}�!������3	��O(8�,f�8*/[@g��{���GS�~�>���[uW�S��z�X����R�����_�p�D��Rr��8����F�N}7�eu��n	&��Fj�x;r�)(�`Zpj-�S�
���x!�-Y�����0�5!���FB�23uI<������;�76\���?Q�SH��	�!�)���p���/|���N��|^��v�l��fq�g��],�9���_7��?����dN|��zT���[�!�E��F��qZ��1�0����%>X�I)4$4�Ms������j{F�v2j�$E�vC�7�~9�dS��&;@h�,�Q���%U-]+h���r�`]Sm�`����������(��(;�<��HWT�,�9����u�Z�4��#��c�Vj'��-R/*u'@D.����|Mk5���5E���&�
���L��B��g�2�]��_vw�&(C��x6[A��<G�63w�w�A�*[J��*!�vh�M~�=	9���U�Q�d��\��p�)������TQ��s���bRJ�"%�5��������.�5�������k"�cT���M��������W�Q��"���F�����"y�����%��%��R���@�'=���NG��0�05�P��������${C��>�Z�K8,����4VWf
��|�>p����.�b,G��Lu���������Nd����t��ae��Y�F��H�c3���L��&^���i1IF-���T_c���;Vm����VE���3Y�����y:�$�
Fb�K,"!}a�Id|9�=P������b&���\[�d�v�9�����3�S�$�~\g]��,�h�<��6�g8|�1~�0�,�a083��P5�_Bx���J�P�l IM���n@���x���U"�{������%EQW��jR�J<����Cx��%���<���������cD���
1��87F��a*L���l��Z��K���-u�,�Up�L�"�2 �m�}4k�F��4�U
����c�������L�e�����;�)!����S����Zn�\�0;���#)y�}��R(*�6�E�P:�JBU6JR�C�l�B����t�7�X�AJA����5�']���6j��ex���O(�D:�2����vP������H,.���M�Q�R�����c������~,6 �����%�15��B�����E�'j��6oVj���c-���eW�g�Y��g��\�y�3A�����A�6����C�g
P�gY�Hl��'FC�����\��
-��P�V���0B	J������m/���8`���N��)��[�3�Y��������8\���D�W
��*�>F�����j��[(�7<��pU6�8�0�0S^c��E��
����m��
�?Z�ny%X���H$����p��BE�-�%���L8c^TkB*�j�u,r4Q��Y��o�����/K�M�Z�.&�aNrY�
����-���3:&�WV�n����*���l���0��["��&�8�>^��kpA�1���u���j������i���e��u�C�G^�.�
j���-W��������3C�UQ���<j����9}2�3����l�=%I��>y�:?�[�FCsSw��
J��mU���bzoK�8�x����"~)��{w��_n�3�%7��m�I�
��Ii6�.vV���t�R�T%>gaK�F�Pr�?��b�*�I�.��!�1��L��Oc�KK)���B�g3tjkgyG,�x@^�r�x#��d�l%fR��0f�:C�������CJA�e�����?L�N�0?������m�����d����p}7�^���a�8���a:(�k�x��x���p|^g�y�h�g��pc�/����\$z��"��`�CKvw�������sqL��k�d��`}��	���R�)�v�LR\���,���u�t!g8��2��������WFL-+���Zr9��\�vq����0��Z_g������9M6���=(���C�G����K���`�/��0�����������Lg�f����Sk�qq�E��^�G�x��HA���q-�9Le�+�Ci#]=�G"O`����Q�u�<���IZ�c�	,�@�=�i
x�`�����p�9�����%;k�|�a��hAC5��~�$%���0�F��b4��;G�7��?}x��/��~=���&�?�����*q���af3z&��l��!����'������F�\f��U��o��eb��
%�7��#�H�^�:-������6t����g���a�hw�L�i�;1
(#�m��w�~jn�WKjs��^e��@q�!p-b����X�h|��7��G�o�.%Z�����Q�c��;h/�|��r��iad��Xr�o&�r���3"�����.�_*N�e6fmt���dl�A�,N@Hf�X����,��.>�D�u�P���;��5���/lf���2�e&��u��]~��F� ���{U���=M-�=����y��`�7�A�*��R�L����P������a,����/��W��byZ��KxY���4�G��3���9��
4��T���`[�V��u���%����|��BJ7�!P�h��O� �{7��e���
�r����#s�V�NK��K'��qz/*mxkfw3	�$�oFF3#_!b{�v�c&������
58eN���j
����{���r)�r�=o ,I�<��E����B�+Z�����.�������`F��\dn�k�����/q��%� ��5C�i�h�(�E~K���������9n_o�:S���'���U�U8	;���v?U�����}��5����'ZVb7���:%z�+�E/�����@��
�\�`�{��u� �S�OT-���2�jZ���yu��*�����������7U<a�������]h�������0'at�;d �5Z��30:�����i���2"�$0�)\����`LzA�*�S��9����,�|�c�j����&����xk���U�itT�(n���5�R�?1�3��t�����e���4jb3���StN�{�l�ts|���VCS+_����ko��y��������>y���9�}���a�����m����t[�LVw�
h����KK�C���,���mw����:�s����&5kO��i��/�%��,Dj!=k;��a�p�:�c~��:wz�����}����aMI+@{6�R �����"LP������������������(�a�{u�t�(�]��Nt����{7k�A���hx�	��s}�Qd������p�����WO�q����0G�^��4�mK��!��p�w0B�Dcf��+0��A�g�Nj2��y�y8mw:J������Z��3D�*������(>Y�������:��vuS��e��YM��:$M0�r
��a���/s	7+)q���s��2#G�'jzI��	�,=����'���N������r��Q9�k�������>��&��i��(y!47��_��8��&8T.�
�x�����Y�!a��PHX��d�N��0%�<:���E��[>��������	:P�J������4��+���6'�Wn��������|����/�^?(kZL�TV��D=K��]`.5�|!��x�~��co	�����"��
J���/��$������R}?��6�7� �{Q�7����Cj�4X[e6����;]u4��q��aUL(A��'��6�#K�5��B�(���n���c^�x2��7���b����z��^�//K���U:����iOq�c�A�<������Sx6O�D���#2+�3���e�>�4r���	����B�;��W���)3U_���P��5�>�F���j&���U�]�h�e�{o�����������dD��^�������CF��d}0Y�f��Fu��J��������$O~J"�^0����Nj�2�Bz�����#�����s��^j�!}�R�'��,��R���)kN�s��u�v"���_m������4Kx~D�3�	V�f'N��J�nK������ar	g�����e�O�={��������������+,�;T;����j����0��Y3�����`��������e�KT�����d;��5�*�n8�/�|����:�9���L��.�����>�������W�����z��e~��De��4�O�i�fi|;�0g-\P����
{y������E�1����CxN�������U�������o���D������1�u�p~(�w;A9_���(;����@��m;Q��@B��V#�jew���W)��TB��~�&v�����K*��C�w-@��M�ti�.����w4z5"_6,��{�Z\g^q�h#B80�p���|����B�o��"��������x�����h@��Q\��x�����q��V���q�g|v��l�,����8��E>���aw�wW[6�{w��o�]8x�_&<���#��)�Y��3?�e������G��%;��������3I�v��;�6�f��@za08q�,L���<�������	�l��L�q��1���������%��$b��6�T���q^�9;��)���q�7�:n�h���t�	�bw�!B�.��GX�G=��Z���>��n��O<k��NZt_��Z<w���DI������|�����f�T�b��y�[lXAp��i�w����� �D�xeX
��'r��>����s�_�+��������9��H\�9�@��l���{��f`Y���2���m�u���l4B�+�q����b����A���e��b����\�X���'M{��Ma4V]�a�c|�|=�U�T��t����_PS���(z�����uZ����@�o�O�Nk2W�z��k����_����`"5'��#���|���NzB/���0+�G�m$p���]�|��.>}�����m�I�O���^��&	���������|������Y�tV�UP��������b����S��C����k��U��M"�e��(,/Lf_�R*1��#(H����r���DIj�Oz��+�&t^,?��X�������i������v�lm7�.Y���@���j��&��oz��Z�������kR��e�Z(-����)��T�O4�vAp��
���K4���)
N/$b��[���;�������������;''�G{���������������N?�!f"���Z
�'��\�I�z�J(+�&�t��/���X�O7��
�����E �F��
0�j�KM��i���X��������m�(����\����M��i�	vR^��Lx��t4���#�}��:|����y��IHXo����EM���8��������Zh�'W����E��������
��t���C��d!���N�����F^�o&����N���h:+�A�b
N�Z�����n��}L�g��l�)Q��l��<�	x�i���nC��B�����:��q@��Gz���5��sR����D
C�5����l��:��������9�����G�!�`�=�U%sU�/�G��W�=O��"����X�u��M���O�)K��Mt��e�[f)��`���>���0n��%<���v�uq\���b��O>f
��]n��Zxl��1��o(�0�5�����XG,4
�D��J%�\S
8��T�d��496��0T���i��(T[�5~:�P�a���_z��#��#Uq�'�=��1�I����@��MT7��:��8&������H�y�����kj�����r�����k�%Mss�����3��e���Y]I�*����k�����*QO�6�|��g���i���=�d�Mt�90��	%���J��	��"�����#��*����L�����v�N��c�7�6�t"4�j;D�-0��{�	��5���������R�������(�y�	j��a(��lWG�K	!���F6���L��\G��������k��	�K�V^l��Qq�����,�{�l<�[Z��w&xm����+��� K3�"��cYua����x!�X�7�E�$"]��QmY9�����������g�1A�#����`c�W�d�+���e��ww[��1m��2�N������%1��`���e��`�������	bJ.n��o��aJ+��c#V�c�1@����,�R�HF���[?^y�VV�����u�?u\*��
ufw�B�K�;�um���KU�:����4?����[��w<\KX��`-G1V	���L�x��
��q88H���O�&6������9c�3�o60t0���X��aF�G����t���!��cC�!e_l�����+iZ�����c���[���g4�����|�4�dG�y��QCuJ,�(��m�p���N%V���ZI99MG�)�G���id�U��:)�v}e��{M�4��^
%y���q�����7��tj��`��7��<��C'+E��/�2����i�4%�wF2k�U�"�"��-	���Q���j��+��:�:���"Z�����Y}�q�������V3e}����7�vm�������=]��|���5h�����lR���3O��Qt����m�#3m��W5�����iS��6��k%�m~�9=�~��8�Jk��q(@��P�~��y)�My4�hDY�8�+8h���"�J���~��A�d���/;�;����Or���?o�����o���L��x������;����]��Yu��hm]���������A_o�g��H�g�u*�RfP���?��B�'�{���\|�n���?�7R��._4���v�6����?���{�~Baxp���������]�N5���_��h�y9J�E���\u6�'��CD� ��������;m����p����m�I��=y����o�N�:%���@B��a{���U�*CK��<�b8�@;��d9%2�H�S����6����el�����3L�=# #���m�.�3����7�
0��
[=��.�����J���2�!&���;���g��0^��d�C�
���m
9SLz���KCJ����������;���x�CEs����K�'e�O��Qw��C���};9�������A�i�������A���/�1R�� Q5l5	��vp�V]�{�l���u�;�����'s���c�^�����5�n��&m�����/]}��ML���%���jf�e��M_�~�w��%��������N����9�$(�Lb�� lw�N�j��e�<#�h-	�����o��b�������m��i��O�KS��!��&Y���E/�w���^6k�w�����|������������,�7��I~g0U,��m����i�����0?4��
	#O^;l���v���w���ned�@~8`t=�L���*K�(�[|u@@���7���?�\{U��"�A�*Jn��K7�b��9^����E?�.U�3��*
 �F�?.kp����0*3��GC"�y�9�(�.��Z��2J�Oo%?J�me7�_��`>'{��'�����}���eg�������#m>���f���v����B.p�~B���CXL
�u��9���q����+��+���/"��BPV�Xn�<B�A|`�K��l��LK|gA/}��k7<�2*�-���h������15l��j���gd3<�����]	�pu�!2���-�M������T��-N��s0i���\NK�S�\C��0+k����4m����	$D(6�`w�?���m���6"��B{6��nc61	[�7Y�<y��[%*�������7]kr�RO��.4#������m��1�Uo���e��T1�_uAQ#�o��g�]�@5�_�Q/+����a���8�x����_�{N�L�����izJ�(Q���4�@�P{��^��`�Z��������������#���D�?������nHz[d;�P�oa��t���2�C���V��+A���u
�^����� t�t�x��f(�?��#m����9�U��Ex��%o�Ze;'�a$�LS���j"FK���������(:[v�����A�n�Y�w��v�n�c�Q�IV�c[@���������������4lt���s�p��p��us����	d%����y��-�F����-�5�����b+i�FN�w������9D2��OO�tZYe`B����<|=�cr�q��Rn�����#���x��L�������������"��*,���)>��}�_�#5O���`E2{���U�w����'�P]9O`�6D.����|�C7�Y%�D��R��<.�I�,�<C�c+�h��eR���"wH�����OG�n������Ds`
�[r�����|F��t�������8Cf�d�r*��k�Nu�������w����?^�kN�t�|���i�$��_)��&�]�n�{���I�v�8W�Y�2�X���u��\���[�5}�d?���J:��\i�Ux1�r=�����L���8�&������pl!Y��@fgW�^���<���)%�����W���h���w����>���W{4��T��)r�����J��#6��3����Y~<��/���I�o�]�SuW�9���G���!	��`2jg��zM!����n/���a���a+�����Rf��fO��]K?O�'�-
��w����eR����W;�w4�T.J��������t���XN�Gv�-�N��jn5N����C�uv�s��,��FXs2E�[P�A���1���������������B[�AD�#��?EO���s�e�?i?����:#�J���|��_n��x��R-���A�SX�)��;s��4��$;o�7s:|G�LwBbpn���Y�]`Rsj~���"��c�t�����!��7�����y�)������~��I*5M���G)%D"���L{��m)��=�S�H�l�6�$Q;r=H�6A�|P�h�������V�zGO��0�JL��O]��c'��%�D�$��b��cB��!;-�����%i+��3������;�%���L���Q	����M�@��m���;H�	�lr�� �������Fu�$�=>�&�p�b}�����z�|Gr�S�MY�p6�T�X��0���2/d��'aQ�Y�!O���L`�R�J�P''�����uI�?
���KGm2���v
���^�
6
�N�svM]�	:���C�L�����+��f�$=i
 �����fM���U�>���P�i[j|i?I�hIS�Cc��qCkK)0xv��SZ8�����2z�w�n�+1^Rt*��v��z���
���oJX�X��)QkZ�]�.x���Fd7����&�s��Pg�wM��L3�[�C���6Y�#^U�-p�%DR������2�����>M�����-�S�SN[H��/p�J��
�N�\�*B��?UrR�w��$��O��Q�����`�+�|1!*�d
�rK(O|�1R���8)g����AI/Z#�|�������i��
5(A���H�"�~�q�&��������|���+�HX�It�s�x�J��/�8�+�7���cE�.w�T]Ib��� q��W��8����U,�\�#�~$�k����5\����5R��(���n���6F�Q��(�fEg�y�.�
q��@_�������"��1�9��q�nmn>�l�2�$�������v��:�e 1�F'��������l���-Y�ln&���[�_<O����9_���iS�aJa�����H��=��C���U�$�jO��s�7��5F�=I�0����s?��$����Pr��`�A;������z�����,{�1�.;�����O�#�+�&��[S�EB� ����1�2PI��0�Uy��ki?���Q�������T� ��W@i�/������O�����b;$���*�8�@$���
�Y�xl��M�G�5��H���(e�U�X�����}6�)����ZW
�:n9�dF*$��������M��(�i�ZN��c,���v��+�v���F�.�v0<r
%-]��-��;�[���M���6�O�\��oD�66��$�%7I_8����a8`;��51���/�!��]���)������@S����#��R�#2s���s��M����eK3���J�;��eH!�"'����x+���x��O�u'�ul���_�"\�����1JD�����Z>�]g#����	+q{�d�t���A�u���������Y.�A'1X�|��hX#��GDi�
���g�,�T;I��T-��9l��
qw7�l�q��Z����Y5)� �%�|fSi�Q��-bar4��3J<�Lz=��(���z�B�#�K��F�A9Ts��d���PY��l|��6����5��8;#;�MW���x�)�X��'���I�V�I9_��t2$H��$�����)_�,��1�{Q�]x���"Uf��/�C�o�.t.�dh���y?��#�����c7�b��f�B�J.��g����c�H~���{�{�u[�vT�5)���2��J�g��_P����u�-������p�{�@R�pG�����$�NJ��F�)7�\�w�����w%��%u�x�:��u�y;��+p��6Z��i�}EA�h�w��������!���]0�����_RX�����w���K����x��GF
��lIX-�v<W/�������������a�s�i������C���W	a����49v�f���g��	���M��KK��Gc3mU��D��{A�43��!p+�E��{-�$��om� #YW�	�1e*�����cDk'�@lqhaB��(��.��	9(���7H;�1�/x�}kP��p�3�+7�C�D/����������95�r�����-9�FC(�������Y�z�\X'��\�6����3�!�9�s4~7s�}wR8��$oY�h�&O6�����61��h���&o{���f�i����04�����9����!��k���M%�J�����de��(��V�?������W�ct�*���.�=t���e;����_�=���������"�Li���7@�+5G���k��OD���u	����+7HN�&��_��?�/
�R��v����9��������A�xK���bry���L��Z���	��j��V�)���(��y_�$�Cg+<��=`���z<""Jn������H�A�0���t��3"���k�[�Q���IP�g���;����A�����zf���r ?����.��!�W�duPM��^:��$M
��Ek#�����0�"�[M(��T{xv�>	T;��'�,�1W�^��$�pt"���'E�u�K
�E�� '3�P[0^�V��#O��ka��AVu��"`��XC]e*�J9&�,+7��H-$!�J�;Il^�~��)_�*�� w$6$�j'��,�������.��%m]�� p�8s5%���`?�b��"c�����,�%3�fN�r��3��r�c�LBG��;�bU�Z�(�����GR y�X��@�0�?��T�v�]�#$}��~DM�u&��OOE�.�?1��"��Yw���hH"`k������������-G�����N���G����(����[lT<_`��v1~����E�
�[�{|tvpv�	�E�HL����x��Q�<������t����<��l�}�<Jo�"�����<#�����DD�|�l������XIaC��KK���]�����H=b	8�z�;��H��N���K�����^=����Hf�)3�.�`R�$k�C��XZZ�O����_V�
j%5j� �2K}�LpU��0k$+_{g�j�Mu����9IXw0�����/�myR�����M~Iz`��X�$5������&��S����k=Q�2��+���e��(9j �����(e�E}�5��=-^�������N'�)0�a� ��D�%e�N��Z��5�#��B}�~M��#�#V~��Sj�vsBdzH3+����4�z���I4��T����|*���0`H���&���BI.���?�L�����F
���Y�v�PZ������u�����sc�B������lF��3�kb�l?������`l��P���`2��9�F$����L!���@^��I�3��a�*�F_���z�H�H���,h'�v�����OA��f�T����ywW��(1�OI��u.3�{8����I��e��^%������kaBpQ���T���`r%:�#V���y3�Zx�~xP��
���5Hs�)2��@��h����s~S4����m��~>��[���J�	�y�Q�iZ<(O��h����D��1�'�#w���`M�f/�	�p����nv
�ka�Ms�/0��h�����I3�1�#���h�f]�k������fG��.���#����UT��U�C�!;{x����6��X�;�
�7�x�@d����V~��c������@dz��4TO�����]7)�q�]~��������f�i<��,�J~���UI0��x?�i���[�Gb����M�9��R�.�$��^�.4�J8I��mi���xcY����|8<o�?8����e��gz��I���	U�������h3U�~��W|#�L������=tGg@
S�v��}�Vv���|�1	;���g�a�*���|q����lI��N2[�B�Y����UC��'�����.r�G���'�\�`u�7�(�bu��s�����/�TLI�t6�|U'�Xgz������\��V�4ji�`�7����=��@���m���1�q��l���H}������b�b��cJ=^A��+��a�^��T�}�F�P��9>�~���IU�.Fd������e����UMhr�8:��	/�5�������I��K��{�O�#���Z�Q��y1�8B$)��l?��ZoP�o��z�������;�D>�H�Qv�i?��y���G�/z���A`'�$�yY�U�T��gf-�jK�""�*��at�(+����������P���S�*�T���xW+��n�M@�7�g��;����rCdOq~��R��S�������+�����q�y8���H;�Ovb7���[���|8�U�la^w1��OI�Z �i�X��P,0HdJ�]�\<DH&������SFHaa��a ~��Qb������N�1�E����� �O�X'�i��� �c'j�Z����GHi��pU_DZq:�����+�����������7/0��������!�^w<�7y���q��pmM����P������a�J���<f����q��x���tP���*�%�1S���a�&������-G�m6��y�C�)�
O{����f����l;(�1M�rbC�uHp�O�t���y%����@5M� �����\��
0�&;�n�)�Np[<�K#h��K1�?�B3�x����.�h#��vz�������~��u��8������Y����D/S����;I�t!O������u��K���#!E��D)�2�;v�w����jz������P��0���T����~��T�;��%�����S�����5z����U�w����w����������&>� P����Pd��Y�8��JJq�����u��D�!/�N�3�k{���~�b��=�������,��]�]_
�n��Ou���0�Ck]^�FCs[���V�� .����Q6���k��'�o�%bET_�������g���Eq~�9���������w��)���W\�n������bZ�z�E2�:?���*G�s���`�)���p�f{=@�|��
f��X?y���	�[����[��hG\����R�n�r����*N�����H�AyD>��T���}g�OoV+��#��Z�����c�":E���p9��-}}~�iNuj�,S�M/e>��U���5�5��@�
3��L{��M����������h>h/�I�5�-.A���:)5�R�����$_:C�_��(��<UJ������|d\%TZ����."&u��"���n� {�k&jf��&l��	�fX�HDn�4��b�D�	�����W�a%��)��Gty�����$����Q���r�g����������z�'�DZ��
P&�J��u���K��V���D%p�J_
���Q��(�k�oE�4.l�M��h�����1N�&��p��HM��BQ���<����fB��)a_R!e�bd�
���y�$CG5$�F��:R��1"&3����a��`�%|X�8��U\4l~���G����[�M+����0�L�������
�qo%	#}k��j�m��"��{��Y��
9����`d�JV\�n�/HG0������90�h�{�t�m��=�D�����O1�����4�^w�J��#��"4i��������t/�hk"�p{[f�yD���Y��>:�.���!���X�T�Sr�F�{#�� ��'�,s�G�����ciR�@�vN��z,j
�W����!I&/�O@�[��'�;���mK1�:w�S���-�q=�QN >�`�m�F��Q\9d��L���y��&��#�V�.3)������g1/���q��e�;	t�n��)Q�V�����g��
���t�
b`�_�4��B,NIv���F�.��}z�S�	�1�����}3�o:y��v%��)���������*7'^��eBU�D�������Lrc�������@V���:(�jZRQ7��>?l�
��qV�`
o�ur�+a	����3��X�i
�`�m0�PyN��8+�;�rY�[}�h
� �~VI$5�t.[�Gl
���s;���0����h����hI��_���~��G\F�����!�Kr�.5?~�@��Jhm��;�s5nl>���B������V�9��p�Y���d�C�{I��Gx3Ce`��������BrK�z�z�f����Q���&?&�E�}BZ�^n��i�O��p����IZ�H];}�������-��l�+n���*C�W��[��a��������#C�0�7�Fw��7f����Q��i��6�*���3���#�K��m������jR)��a�p��������~r=��0���]<7�����H�+h-��p:�D�+��{*7�=�����#�;���
�y["@�������S�������� o��o9\<+jTC?���y�G�((��Q��n���.^��9�9����{��=��5�uZ��
G��:A������*��T
��No�1i/;;(Z���#"�O��{$\�/���fz��
�?���*�=G!�A��������<���l���YA�af�n�e	��W�h���������H
`�����4�rb�{��f�P�G�uP���3�e��F&>��]�)������{^5P���|
�0d���D��E��tec���8$��6�}����~D���:�G���E�������sb�������V���9D�r�=7�h+~�l<{����~�U���
������t�P����B
�:�r<�����#��MWM]/W�_��^��DNAd��&����>��-k8!���w�$��i��x�yt��-4�L�R;��B�����}H4;�S	�MmVD�QZ���.e`�(�+�lhFx���?�����3&���a���	U����8/�.=lE��y�B�x��9�gc'����At���&VF��
��/�{��IS��)���qN��T<`����W��p���$���F]������������{�6�I���l��[��/����yy��F���
�0�����w��I�������m�`2J�����($�G���6�7p�l�4��@��8\��-�'F �D� �����:��R�*+��,=��%d*��4����3�'���4�3��Z]�����(�����xJ=��y������`Z������b��Z���wH�|Qf�/��c�5��X1�����7[�����S�a�/����_�|( T�O^�s����;
2���;���)�o��W}�g��k?���x�*8�"E�VoD��L�O��J���)�����b�����E5����!������*l�����K�)�Y7�B������0�gO_���N���0N�9��e��/����4��kBV�UK2�h���,�"E�;�[��m,�����FM"p�(���������0)���6�M�T]/CP�����\���Ax�c�Y"��bz0���~�_��d��-9�)Z�vH�5�K��0&$^���U�����+-�X�#�_1��d
�x1����fd��p��C�����G�L�G���6���.�m�:h��?'���ry���h8=�������.n%��O�`�L�fD�9�t(T��e���%$�����I@�f�����<��X���D��#-Pi����J�9�R,|�"��N�b=�s��^�; g;�,����b��2��"78��@�����.[c�W�U "��$�po����O�74�b���	j�����IS�k���1����'�I6V��U;O�h�/�����1+��V�v/��]�a��
TK�~�c�,��J��3f�J�#����(�6�	��j�����Y��O����N�p��II/��{�v�nV ��%�`A��e�W
>vv�V�����;e���_'���\�Z���ul��!�4k����2M��X����@scb�l�s#�/l���*�L���&�%\O�%�	7W��(/�����:�L��gO v|�1�-*�H�r�O��kf�5F��3����H��T����%[���o#�9��
��$������8W�Q�|h-�����B����������`+��u8�BD�Ja���^�n7�v=}V�+)XNF_�/������9Lrh�>/\k���q�j�M}���p'�����`��%}���2�8�j�R�����8"8��t8�����W�t�:4�s�F���Z�c|$l�&�����"y)2�K��fCa���H���A��_t>lSv�O���{)3�!�tF�x]CAm5�n����g���pM�U��b Y	Xb����������_|gXV�f9f��+��J�w����	��m'���RE;u� _,����@&�
k-��&>�d)Y�vSUk!�6}
��A^N�X_^%��jT|9��4������#���>��MW�qom-Z�Q}�t��"��T��8�(��X�N�Z\"�G����U'f�N5�C������z� gO����Ba=���&E���esr�����O:� =�pD���P����u�C�.�bz�7Ip�����\/%��kLUzP���2�Z�U��FR`��X�����.��w�(9�'9�&����~�A^uR���?d�������?t�����l���S/�cO�����~QX��"%Qw�6�����G<���.�-�M���"PQ�=������0j�u�4Y�BV�N@����b��6��0��������P�������j#&e��r���k*iFxX7~�\ROJeNP"Y�u��q?!;�u:����m��B����8��lR���[���������S���n+��e���%� ��v"P� >�����'��Aw�It�.���@�'�������Gbv�^�.�8��"�O��N�}��Ms��q��	�3���\(�[P4v��I�W����|!1P���
o�Z�o:����K�K{��K�u�����h:L��.�u*DUm.y�������-z6	����rgfo^_U��}'����	���V��	)�}�^��������������L��K�VLR;���W��,D�_�nA�����h?�*����;�3���N����X������(yg:��D8�Ws��X��q���������Pjm8vk���BK�M/��}��d�����
����4��C��)hc')��[���+
o�t�����y�_�=),�0�HR�	���mvO���kX#s
,6?f���^����L�
�v���%; ��t�n���gt��|jwcJ�������;Mo����1�O����b"���,�8>���TJt�j��wN,$����(p���k'dc����O��}+j5Z~�Un�2��L����n���"��,���w�7��v�o��Zgys�t���8�%��^���(
���#6��io��A�f��;���G��#	$��H/�Vr���vt�iB��q���DU��P��et��6�*��mW��0��������w�=��Dg�/���@�m�	6�Y�+�A�l��+9J�����-�T����y��>O��	�ee���a0�c����&�y����A�:�I-dL�rA��5���"���$����<�Y�����O0�u���*�,���6%��V9;�n_G�%pX�s�\0V�n���F)���/"i����a������u]���}�O>R�A�����B�i����@�
������Z��X����.��
��;�P�?��yK1���T�z��k���i��R��1�M��Lx]d��4��]� >[�R����XI�[�K��L���h�!K-�]s�eV�j�u�"�3��q�(ipm7�"�r��'�������=�3{����q�fI�c���.<�^�:��s�E��,h\b'���9��nl�Ci~��M���-8���������n.�]zB��z�	��w2g.�+�V��7 j`sm+������{G���03��7HG�Lr�!@jD��Pc�R[�t?��'{G(�~4��dk���[y�Y�PH�8k0�dP�\Dr������0��K��������2O;�����U+Th��%��wR�z�<;&z��l|L7q��v������x/l4��UU��|�C�7���L1U84�*�p~JVTF�,��Wc����<x��g�$-=t�����X^��FI�)�h�9��6���������x��1���F���+
gtP8��F��E4�^���o�����'�ZW]�������W�k�/|�iv��9�6��������r�P��;��������2����g�M�#��PT��(��B��#T��,P�����:F��6�xJT�7L�c�HTX2����n'����)	8_�����.�>�3��L�5bI�B��G:�HYV�0K9�\�r�=��Y���V��(3FM�/�^A�N�N��g����3�-��nN:_�\h�2|7��:�
���*1�mt���g5��
3]�F�b�
#EF����f�O�.��x����V��Y*b�;g�y��U��^%����fI�(M��N�|S����r�z�pK�?�L"�������;��]��#�����a"<�iQM�V�-H�QU/ER�{B��.&���r�R�I����)2gl��0�wgb�$�!#��`�F�s�0�|���F=��%(���}p���_��R�bs9��*[�$K*�	�h�^ -@V����C�]�+�j���M]V�
tl@�*���
���o�b*���RJ�h�6wg-v\�jK&4O�Y���������*�E��|8����Ie>�#�����K�q����W���	��w��3��J\��'*4��w����w��]-��a`�����/2�3�G�/��X�r����v'=������mefAC6=c�T��'��o������j�0�{�j�Z��`�����g,?����k�`3�[����n������( ���<m�\^o�>����`�5�KD9����z	����g�jJEF����Y�MR\FW^�9��),�����Y�?YU���f�����SNf{:����t0�qU�.\x4��h�k�q�6b���5;���u�,	p<����"�$�h��H�l!+I���,���x���S''��H�<�"���n89�s��,W�>`c���m�u��C"�|E��A��
^��V�9
��8�����{/�9JI��nx2>���rK^����1�$|c�kD�E�����KU�\�k�����'�}�����6���bC���E���^�F�#Q>�2��E��3�6(�����U	�ZpJ.���yi�{�
"_G�����4��x��W���Bijz8-�����
��j���3o+fU=L��^�U���k����E���-�}Pw������?����o|&m-����$&��.n#F�����H�����Ak3�8bE�'�r-{"������I��0c��;�	����2@@���-j�������J�N���i����@U��_�0�y8���z���@A ���T/���y;3h�#�"u`�b���\uk���q��������sdP�=�gz�(��������\�t��\�����3��P{a�4�*l9�<�;�.)����B���CV��
0��a�8��a���,�[%"�w7�?�z9�����FW�QS]y����3Q[]�r���t�A
����4�����b�����~<GR�'�������K�W��������������~��\��e��fF�
�!a-3tD~)$��m+����K�8�g��Jo���3Nkj��������'u����T&�����������!�4sh��������w[;[���[�F8�.lRv�����M�bCI���`U�>�	k�8��Y%�d���.Y.($c������b)4R��v���Dq��E�~�ZV���=L'hx��f1��tp{�x��	�e��R��<�e� !3G���()�c����� �f��ZU��Bgd�5�{2�� G�^x���=B�;��6�
?j��x�n�X���;���;�N�������}y���B>p�����D�F��~&G���	]�3�9F$��>���Z6�#�V���*�[��v���j���eR�{�^v�����O�"�h�k8c]�^�x����
{*��y>��y?�!)U���"����� $LV>H&��p6_�0����B8J������e���0�0�kd����Z��d����u�+��N�c������d�+L�Fv+�#'������������-`.7�$Hk�?��o����K��'	�%�?��t`!���t
���!2��(C^Hq6P�_^6V��{f�:�������������9J=s5�F��U���T�p��]���X���o�	��&��Sw$C��$#�A��� _0�-{�)HH�bS����>� �]�U\�:J,v���Q'�r�4�(�����Q�R=�������V'��(`�#�dp!�*�Mx)YB$&v���f��4"&|��~�sF1�H�Bag��
{�����A?��d����@/�u�����������5�t�?�M����x�\���b�@�z�R����F%���-�C�=:v�n��K�=�1s0��Iv��MJSMuk�i<��?d7B�tO��#��D���d��dJ�MT��h�������g��`�
(^\�����j��A�6y�8�iB��D$���&��x��Q��rq��������o���`_�����$8;����`�5�F�����~%E��L�,�
�2��H�i��"��,��2�G����z�����k�H���^$�E���9�e��cD��������@}��)�����:��V��2/
Cl�E�5L���k�u��"V������D���+"�^�N��cqq�tj�j�C���/���w���c_�k�l�����"��s����h+�97���`�K��:�w1���R�b�������p�vO�w������O������������'o[��vN��~r�:8�pv
�����
�(;�eo�I>h$5���_��L��CB��5h��fS�O-�������
����������!c�Q-�(�-�^��l�jvuh6�Blx��r����N���� {�v���dA��������vr�-<ga1��p=!�;���I�r�wL������*����C�F����e��
��R��X��.��
_![�wh�R)Pl�j4(8IV��(q=z_Zy��Gp�`=���/\s^��Z��^.{��~z�1%A�������'8$nO�a�p%��P�G�[v����L��d��`�+�P8��$��x&�dM�SO�3 x�m������W���Q��d����wU>�*�+z�k7����Q7�6b�d:6�y�B������NU$0n�`��d��Wb������aL@�w����'*m���*%aS9����d��8��6�h|;�|���\�x���bH�d�,\�e6�	^5��+-��UOV5�m�X~5����
4-����*�8���@�\$�?Q�S��pK����
�a��,CaE����������;i������c3�j���oY��I����T%��(]&�����L���A��S#�������#2���+T���]g��^El��kF����2^b��'. �q_^�m�Yi|�]6&{���E������^$���|o��
��<�CI��������������O�35�A�����`�b7��_���.KL��&������8���\�;��������<�U��a�sC������9����@X��s>��� p�
�V���zE��J�xx���v ds�~[\$=��b�WRv������\�"�������I��L����-X��!���i5�3�T�'R���W��b,&\
����y��)a3&�%V�P�����#����2�F'���>��US�7m^��l�y[I�.���}#=,���&��P�s��>Ae�8Fdn��b��X)��P7�����QUU����
�\M�p7��gln��������P�����`�I�V���;t
���#�2vw\��{��d��9!��+�Qz�8�q�X=n���������#�����kt����M�-z
�b80��vOW�����1�6_Q-eR��3�:)�h�,AFkV�t6Y,�g����Hk1����)�����P�i�������f<��w��������
����]\_�0aSO����cJ�<A�!���f�\�t�8>
�Q�	�j�����b2�$��|)
��C��E���Z�&;�!3�^w��|������-�(����4�5�Z�wT�Fa�4���sB��P>.��R?[�7�pV������������������oR��N�*z�����cq������)	�p�Z�|����/�y�MS�������4�s��'}��#(��6F�"	C�j1�����r����`�)��Z@�I."�!2/���XJD�"nS�/��H<:���|�+g�'��W���x$�����-v8�a��4&cYuxv����;���h�����&�Q�@�J�N����*�������*��QL�K���=
YZ��I;��VS������F���R&��=�]��S��q	�c�9��x���_k�:-�WCg�WV��I�}�j�h�����f�u���
Zpsq�BO/t��^>x��;k����m��C[}PC���Ll�(�����x�����������)�{�K#>���}wBN1���p�H"�:������z��3�;��B�fNg���,k��=dU6��`l�Q�d%�=��u`�A�*4�N�R��=�c���%T��lW��<�����m�'��J���L0�`uds,�N'�<���&W�j��O��'��Io`�0��$/�m��.w�B%�������7;�Ek(o���6� h�
[�T]����$L0XHW�[l,�GT\��7�f���M/(�&kk����Q���>��\�h7hg��L����-�E'�I�m=�n^����l�����������g������mq����s��o���mm=}�|�<��.�G��_���wgo`���Zr�Kxu9	�q�$�����Tl��k�%��sq��J���k�oNb��=C��2��}pf)[8a/��|�O���2���m��ye%�d	7�m�_�����i�C���_�H����E{���<y��l9������}
����Dcu6j)�U�~�R_��Q}	�'�yl���K������7�T	��������I/i?�gc��s����zF����)�����+��J��h�>�_�2'6{4�m�$>ii��Y��f���g�%L��U?����E{���]��88�';@8!\M���u'��ns6g�&d��H�+����_�v��-��O�~�
V���u�2��U0����Rl�g�LyuF���(	z�Rb����)%4�����s`%��v�w���GS�z��$��w�\������b
���7���.�,x�����`"�Ei�v�8��>��r�|�=�jn!��z����=���`���|-9��14�}V�0qN)9�dv(�(��$+��~G���%��u��E���5�v:i��w�y����,���F�g���H����n���@�������f���P����g���7����
w*��/a
l�_^��5�FN���e������C���3I9y5�(�n��]����N��Aq"��L���8�����&O��>���u�n�t�(��sTy�x`�4}�TW����@G�����Z��*�f�f
���^�����������J��M��������Z~<H�2~���fo|�:��z���7-]e�>��)X�����{��D��9����;��8�|�o~G|���'���4v�3����J7.����������M${�4�!�������;3>�3��{IpvI�����PS�*� ���S�,0qO��6qX�7�X�5I�)b�k�e�`���x�)���s���1���6Lb9Hd�t�����i��q�[��}��h���L�S9��`R��i.����{�Q�h�m�����5�8��NO&�Y*}�rY:D�rX�|������a���{Jb�*�c�
��:�����D`F����;��a�!�s9���:��I����3�Aa���7j:��Z�d�a55���4�:��.D(���6#m����P����
.��G�b�����
A�vF���=�	%P
n?
�d7w�@YZ�p�?�����������D�S�����j�������1���mI��������*M	qNG�q�c�U28�������G�R����.'}����t�M�L5:���R�f#y�S�����B;���s:�O�l����`����Q�ar8-a
&&E����d>u�]�D3_�	�S1�n�F��S �������%H��W�2����b������@������A���{���g_�z<���C���z:�$��Zg�@�[��k�k�-�@X-=�X��bo�avY!W�����a������A��u7Bd������\�[\]]*�Y���w��?��?�����+n��AQ"����d0�X�W�1st��+��~����`+I�:����a��Y� ���A����O;��������=h�U�o
bEl$�k����M����`��c\�H�(1{V�%�vM<A��NU�e�����^�E����f�����Hj�s��&����,�%���IN���0B�a1���:� �f�N0rt�l�g	;��9^��~��W�8>���r�'�Rr��^��x����9Y�q_f6���	��n�m�\��Q������viJ�����M.m85$ vJ~w��r��x������-MXMg�u��~������xd�l���\zI��M��`,�#g�T>��.�/G�~$��	7�]�dw�!�z��dL4�5A�/��-'��n���.[.��m�0���(�]Ws��'s�;-e&��ZA}��S\���}2�zE��������Z�mD��2�waQ�],�CB%Y���_�t�H��d�����e���Nt0x"��5��~]�|�R�\�sN)��G�O�t���9�^�"����{�T��)#��^t��ts}����g���|�x[�te�-����? ;��<!����0��z:,x"d�=�i<�VvV�����@���t*S{�J�/��� v`JA�|+�-f�`W
�B����CS�i.0���P2p3��=l��{�V��H�'�l`Q��8c�M����)E�I
�����:�������]��i�Ky�$W�����L������\����?� [���������:��Z��p<bq*rF4��=�R�\���������X_��s'7��5.�lqE	�7��t��x�r���n6(�p�DJ��f/:/�__����fZObuT�@�����������%���8	���aq��=��t �1�p)Da/is�Thu{��
�����j����3�E����K�1�?��k���SH�E��������~���l��I��K1P����\{#zE�s4-?<�i�A��_����q��Q�8�_p�Jq����k�D:1�{��>���9����7n���h-�t|�LO��[X��.��R����m{��
{�����m!���u����J��;�;�������T3}�;qM_<E��������g}�#���K�����m��rp]d���}
K4B�]K*��%�4��3��qx �����5��E����n��AZ	:O[�������h��$���J��bQ)q@�T]%�t�� ��h	jkx�����;y��l�������9�pM��:;�<�M#d����2p�����*�_��D�[B;�nI�Z���{8�86W��"N��)M�?A7I�q<$a�a��yn:��L�u5��*��.T*R���h��������:nz;A��&#SZ]�(����L��p����uu/�eym��]~����t����K��nn�.�����l�>�$�.���u{��G\�7/�E��q����JJy��z���Ye���/6�C/t���9#0��#u�nd�����C*Z��6��5�]�F������D��<H��D��LfK����<���X�����0��Xh����W(�6��
������S���x4(��[5�M�����,]j���4?���3����M��n�(��:_�����H�cNQ�H��eXV��2G�����z1|Wf/X�?����W���3X�b�)2�"�hI�	��lK�����Op'��N�nK�q;��(B�\}��j�i�N���8P����=��a�7��8������Pb��r��o1���e�.��E%^O��@������!���w2��Fs{����b�	���UR����<@QN7��%�^���4�����t���8�+*�&uP�r@.U�a)���'��V�Ns��������R�)��R1��������9�'HN���S���?&�����]s>I�x�!a�e�uE��0�=�t�#}K��\���a�Zt���A
#N�D�0Y
�R��K&�8��"0V�]��D:��oi�C�>|��/�*��p�L�+�:=�����_��hN��-��3tH�r�pU�h���a�P"��v�.��!��*fF��2���M�j�v���������)��(��
#��Rq�b��ks��o�1��nA�8�K�\���S���h�c����]�9�����N�������M��<�N�-��o��y�D~�����R9�������{f�Q+�^-��4#\�v��:#�`��wp���>�*7M��
� �O����*$���&Qy�:4�9\;����A����G����5c_}���z �x�=y �����r���T�[��/M��7W=H-�	�D�(s�L!�C���l�������7����x��^���p�����h�����
:�o\N�5��Z�*O��l>y���mv�<���U��N��!��%~75B���������S�\�==t�� ����6v��M�*����[�,.���0u�K�B���F��)������Dq�Xea1	�����"�9s��T��V��������������}����'��;��G��������|���U@����Y6fG��"�m��~�Td��JI�:d������}���~>�t�o6�M
;n����zv�p"��>��$+&��x�4�����
�x�[�`0������pR3�d��)���z��?r��@�sX���]���|w���O��b��Hw����v.���K�~�}ws�#]S��3^�
m����;�1��=|8&�%�����9��w�DK�[�2�	��X+�x����/�w�:������\)g��sh}��8��S�n�h_K�
�C7i,1��4��*��$
O����
����,B�.��0�#��K���U3�z��u����l*
F)��'�y�Yr=}����0�F�(���HF��~�:7������R�J�-�@�O�`�a
8�	=pR-��r�TS;��q��F��,a��xw����[��G�7���%}�>�d��E~�:�����������������/^�0���������? #H���jj=�?1�z�^&�^�>)�yV��ew���x%g��������E��R���<���a_��s����5��J�'2g�|�.2,����w���hoYH(����G��z��1*����2[�MI?��R#&�����H��b���<c�������
~g:s!���7�Ai�S�V�)gB
���:��������}�~r�lk��UL=Z�N���s�|���0�Ya�K�J��2��=�_�yg���%��������������w=��]�t����^�~x>j[�)����8<H��m<J�d=}�MYT��h&��:���W@�����I�Iw�l�f�M%.������#�`�[����I��2_��0@A	�$�u`7
%�(�O(�"=1���L�g}�\���
������5^�("54����c��N3��j�=�7G��(>���%{�g�w0{�s��h��f�,���l>yN���1�.��]C8���K�us��W�G�!���
�=N�v?�	��������<���
F��}�����b���%�J"@�I��d�������P�2����+��F�l�9C3�dF�$J-�;� z�Y?-`��)E�
.�[��z&%g�(x�_I����g>�&������������_��Z�������YU�]�Bx-��n2d�$�9ipH�0�Wy����
X(��H�6����"An3��ia'1��&W�C}�s�@�����/���9�$��N�p}�1
�p�� �
�A'�%5�6�8��������:���0)<�������a"������Adm"��-3�N��������,�������}9-h�����"����]c����LS����d��S�\���X{5&��:�j<
>~�I�Q}�_�n�������Z%��8������n�U7Y���S�*m�����q�z��&�3�!I���0^$���>g����~�9<�3�K����Gd����m/Gr1���g������vD��1b�"���=61��G����&�C��r�
�"/�a��r�!q($:^��dd����#v�Yj��XMS�����di��}N�*����/�� 5�����*:B
�t���j8�nnP��i�IS��`���������X��c6J��C��R���&��.4�-�����|�-s �d��6���= �����M��B��v27�bE+��Kkz��2n�T��Kg��'�v.]���=k�#P�;y��NAO����NQ����=�A+_��Z�R�/y�_��||��f}�0�����C��8���������I�#���Coa�O�92���5}�y����q��/
�\����z��Fe��EZ�8���r��Mo*�y3@���x���Vl�aO�����CUWw�K`�����J�^�GJ���?��|U�� I1&JfD3�z�@�g��ps�x��&����)�II�N�dr�G.��
F���9 �r �H�h���FQ7f�e����������%��>�1C��w�uzk�l����������b��jV�
.+004��I�%����B�N����u��DI
kRU�����S������P�6>������>����{��9f����������!W7�����s}��6a
�����CH�];�G�`�g�y���\��6�����+|E��z��Xf^�Pf��K�0����r��D�k�T.����9����W�K�������wG��p�;N�Bw4�C�;����kx���������y���.:�"�<��lS�����MOkJ;������!�����^�Z$�;�*g��\"L���[�ru��^�H���.�fc��F�+��H�*����M�YtH�
���[�G�=H�A��AcS��1�)$pFp,�i�dF$�CZ��<�����H���Z3|�^~M��� \5����:�RT
���V�����d=���V��-=���j�T�	R��jVN���k���c�e�l�����O�jh/���5��Nq��Y�f��d�TA��:p&g��P{��X��&�����}N��Z���I��:=;���W���*
�
fgq-SL
t0��$�����^�A���)"4�����Y�c+��V��g���(�3�	�k�-<�#�5�&��}eZ�`\���U`�>O��c�Hn���q���5����5���9��l�=����O��N��_�@(�+����b�wUF�a��e�)�[����"�!q�NzLw���Q���t�*w��;0N�@�� ������Z�_�#K#����v��-`l%nad�r��@������$�A�
X�fU7:��MO9����$1!����h '�Ll?�o���s{�G����[������ti�Y~�)x��^[�n���.F�iJ��)����~h
�4��d�up'���K����K(�M6�������������S0F�}_kp��������^W�6"����+����$�"�;��F����X������Q�W]d�j(��������r\�O�5N��#�k��a as5��F��S�g��e���
���\�?�-��F�a�nfO_ln��gO3���?L�[�&��l�?�C�A��7��\�6(�
��V0(nc�|�1%���W��'� ��f~����
L���)0���������Vu��fC!�I���_�(�^�$>y�>��v��������]uk+�3Y[�����K8}01
���]��(�l��;��7o������X�fc�O�~�)h�����p�\�Mm�,!�Q�i����)��=�������/���DPE��n4�t��7����21 �5�?vt;��/�X��^�8>�[M~�`H0:kEe��Y{�����)9����-����'?��'���N��v������Q���R|\)&�Y�����������;{���w�TV����9��s)���E�	��N��};#MY�[��6�g����O69�!�5�n����I���E����1Z���� �5+]zd(�C���>�}MN#8���"���Jx$h$�p�����$ �|a��d0�/�cJ�<����8R'�������;f�B�^tVh0�nTf���q�"����o��������"��n�9����w���#�e�5TOF�<J���/������4���=�+����'I#I�6���-�A_@��e?y{tv�������c7��.��r���{�X!>�D��=���yY��-��3S��\�OO7�!��������p_����g?$++��oO�6�|��?|��4���g�����o��OK����u�?	���$�����*��`��E�B�b����������nS�D&��g/�g]CT��1w[�������'�G��~c��o��\���E�<�&iy%��v�W�)��~v�5��p���{N��5;N�s�k9-�pK��������S���@�<8�/��A*HN�j���������4���dDO��S����!|��!����K����d�m~vn�.����$Eo�<()�����Oq�%t�����V/K��X"(\1�g��	<�z3�����3}"f�9{�z�^�xAT%'s��1c��_h�L$�~���49�D/�&����.y0��7J�����s�)0Vd��;�ud�����'�����E����mP��n0��mE���
�C������w���Q���ouF�!p��I e�l����������^���4��=M�? ��_����	�$y�7��������Q��~�k�F7@�.���:��,|��L�$�����w:O�O�)�����
�O�k�6�s���	�&(A�}���VR��\& W�p��������5y[��\�F~��"Lv��9�6��@=~x��ds�i���3${��I�v4�g���\R����}((
1�d�g�Y����o:I�j1vo�xo�U�|�Oj�W��k��GL�HP��	x�J�����\�xp�U���S�
� O��7�)'�Hm�#��M��_�J�����)���������'������m�;;[��������[��d�C�g�u+v7��h��������d�bf"(g�Ub^��w����!��OIT-BY����^$a�2�%��J��M2�Q(����l�e���"Y�����������R2���\��X���gI�2!/��� U�qb.�d��������Q���~�z%�
���h�������^qq��	���}l�D`
}/%�Kktjb���D>�����Hi��H���\�|���V�]y5w��1J��&��E���KQ�b���l��B��Y��2pN*�wVb���K]�j���������p�kC��HHL%fz�����BI��?��&��:.S��[�t)�2�s0��L��($����������0KG���h�Xn�t�:c4������[�����G���.�p���	}��3����=�����/��3J���������V��C��@�����=�L�q���L���^Y_@�G�lf�p_�t���c����R<&U����CT���n.Z�%��A��%d�MQ&���7�����ZJ��d4�������9H�����Qj�k�453(�Y�l	c��c0���^����F�l�mQCs������~k��t�o����_���#6a2
w�\���?�Q��+JU�o�}C=���nk&���m�(A\kC�>���v�G�����&
�g���5�<��'s��c���_}_	�^~��uuz�_�O�A?����zZ��ufe|�������*CL����Yi_l��_#D2�'��X�]���_��"���E��hN��e$-�#>����*��<�k�#f(�m���-y�jOB�:��S�)�>���E)����4m���rYIw�z!����y���r����v���
s������[R�Z�P�/9���!"�h/����y3��������k�?Z����?����4�~�yP�im�l��.����?9as'�"C&���#�Tx�6�)��"�T7��-J�@����j��u@�P�kk��N����!�h���[����������'���-R���0�E��E2|��v�!Z+�D��Z\r��AO��	������=�8T�>����g|\�o�;rp�,�����OKIY3@�iN)��P~07W�HI�-r@0Z����2;yxv�,�|�,#���H�=��7������!	"-�$7���tS��t���i�����s���y���ei�TD�C�M0��Z``��\��8eoG?�9g������7$������3�*�c�v	i$���^���>T}dA����b&�y�Z�mT�x����Rv��~������]Z�1����a'o��\Cb���f	f�r'�,���hg��|���������h�WO���i��u��(�`Z����o>��ET���|���ya%X:�3�b{/f�{mE������	h��(��a�Sh����K��
d�)yF�@��!�'��I�w���-�q����DH�;�o�?x�B{��J�u�������BG/l�9{/��b�b��	�[V�dntX�n���W���'�['������b~#�<j'���<�2�����%Q$2���^V"��d���?��S�����U�|~CGF��O+r��0��f�vW8�@�[V8�����I�+0d�OTMI�'�s'��@8��in��MVl����1����/y�r`~1J��s����>+�������WO�*��Nj�J���V9%U6�(��"`F���}�8b���1
���X��O+��7�<� ��`�fMC8�����Q�:�;	@��/y�*X4�}��'���g6Tm�����X�0������
���n�6�*�9�L��sn3U6��&�����F��g�EX�������"��3���5M�p��������|#���>�YS����\�����'�],����8���g?�#��~I��d�	�/�Ee�O�*���r&��C��������������xT^D�20�6�8���^��y����o�Y����}��Qi�a��e	<��"��ih\<��97E�7B�nj_M��xL��1���+�?-��x�R�By$H���r��(�O�9�$	��S�Ce��	ZS
��E%{�I{�7��+c��$2���}zc#F�_3�F���5�����t�����&��Ck6 M[S�z9��x�d��d����9z��:8:�?=o��g����{�����0(���kS�h;�7l�"�q������'&iz�&�ha�PF����3���%���'����_��j�Y���1��p2�
�����q���
r8��*P�e��Z�/��>��{���Zb_�~�9aR
+�[nR���=M[���}�qtB�=p�|6O�{�	���:"t��\�	��*��� z9�b�9����%Q�+��aa�:�a��F�P{A������]�~PD=��/�G�����;oJ!/���e�x!�����c;�p"�������6�����y��~�=x<�u��'�`$N�D�&��5�t���H��m��3���@�%FK�s-��P�T/�(�
dMy��H��-���;AP���c��Bl����bW�Hf"�^8!Ro�YzSy&@u<��JQg"�#9�q�����\�4���e^|f:���!i��, I����='������Tn�?b�!f�Ym�������{�.����B+b����2j�lr���(]8��O,���B�%�cK^���������#0#��W�q~,[���J�H��wv�4��8E�2���lJ�.���kQ�2��6J�B5�L�'��#���������3����A8�
�)�K�`��������_RqQM�Q�>j��^<�&@�FD����!DG�[D8�N����]7
�C�3�d����)��,��!�"i�v2����G��*�j�Wa��������q�R�]K��U�n.wn������5���sG�<N�����z�����I���v��5��2����2i\0CZW��wAw�c�S�mchqg�������h�cQ�!WA��rC�S30����YjX�O�is�����.�.#�~�����,3��8>-zA��e���x$���"D����L��8a7����+�.G���Gqo�I�UfPG)A	����r]�$�^��--�U�Y���fasV���#�mE����r�������������B�!�����M�G����}O�n���:]�����R����M&��J�7ans]�HM�-X�<wYf>�������%���+��:�U�EWVd��GhJ�IS��[VcCu���
�����X�d��Q�(n����m����"�%�2>�>�iX��l�
�����(�_�0V�V2�u:}h�(�F�=��m���`Rt��!Q�F\�%V�b1(���l���/{�1�c� ��,��
S�pU"YF��y5Z.l��0���	,�}����$gslr��3��x�2�4
'��e&���^^%xM��9���v��������LQH
����>q�1,C�����z��-���
�]�H�����*Pl�ieT�r
1k'�(��j���m�L������W:��T�cn\���&6����H����cL�2S���
<����U:�
z���j�{�u��P"M�.*���h�mj�:��s��C#�]Yc-�_�����)�r�>��V*��h���=%��|Q�&��YiJS� 0e�._��u��v���������xCl�<B�M�Y��/��q�:���Y+oA�*<)��5��n��Uj�7b=��q�+�xVu���,�\�5+M���A��
��F9������L+��������1���B>/�sg���J?<���K��J� 8!���j������'�x0��7���_k�M"���.y>����*8����U9�R�����^��p���Y�W�F��*��a�-�/��i�V���a%�.�b�W���H�L
�3`�rr���h�u��c������'v{����8�@��:P{����/UWn�*kN[��#���V�}}#�������*MBB|�b-]��7,S������y�X�L$7(h����n[�3(�2Hw���0��/�����e���=����v��L��?���T�WF�z�{�6��X%��8oq�G������UFY��O�|��?iF�[��H]�>�&Y]U����W�C|mK�v$}��&�A���F��C�����Q�$�b���;$P�^����Y��$������4�pR�ON�`Q�\p�k��Y6F������r��Lf
���|������MeNn��3Kf�Gh���Q���S����R�LB+��b�=#���.��-�����W�_r�����LQ`�[���Oy�g��gt���m5����
r��Q[��Dz��K��k�Z��P�
oPd������:
��Wp�"H���j_�)�G��pt����w=k
���y����������/�.�u���x�}�����gg1�E�����Ip��p:�^��x���3����vN^88�s]���5�DP���E�_�'��U���WY�w8f��8��=����%��r�@�Gg�;����1��X >�DV&�W��
m$'!�lT;��h���H�1>
Z�\���O�
�R0���3��l�
���KHR�D�B���P��b�9�0fOY�L�j����D(��&Ea�A�uN����&������<{wL�R�KL�L�U����T�B�1�	i���I���r��t,��f��+�R�p���]r5���x������(nCe^����������9*�
:� ��R������#���9;���d7��&���%��c8��#+6|���j��#�����2��i9 ��#l�22��F�:�*��u��q���-K�\n�L�������a�N���H,�Yz�c/k�f�6���tq������s�9����.e=�4������r��3�A��s�R�����X[S�L8��+�>����q���i�4lvb���b�:w��P#3l��b�,�B(@�,B�K�b��M�vTPD���1~IL!&�rJ�GZ�v�H,\�S��iv��UED%��E]�W�u+)�z��I�Ai/�|};F��
�*�D��S�C�����b����[1y$?k?�F�����|�����"S55ND�r�M�,Vx��{a����b��g��/�k�<��>C��*�^-C)?�����Bz��r�#��6�0cv������"��+�Gn�h�&�E��xA�|A���?#?q��RG�a�!��9+���]�l�1.�,:=M7���%iJv%�~Z��������l�t,��(L�o4%���K'i�f.~��.k�^_���x��~��b}��]7kw��^zSA���"���o���_��N~�������,��p��7�[y
���������l���n����Y�a�<�A�KL$�cw���7Pv0�+���!��fO�������A���c�������I�%������Sq���5g/�-����k���$*�u���N��w�'[[�����Q��n)\��61�~
����}1�y�1leX���.� ��Aq"����ne���{0���|��3j��*A&��(�5|YF�/i{2�;����#����%sUS1��{Y��_��S��!/0��^�K�z���p�`$�/7��x
J{wV�_��R���f�������=�����goKSA��4E�:y��������%��U�F=�#�N��+A9�_��x����4����j�
S%M���B�
����!��������q�����xL������k�M	���W�4
36��V"@&hB`���0(N`m3����oWUw��~@6�`��]�������Kt�	��,��T�8!�m��v�lu�v
����HE5A�:@A���	8��]�;p���&��z�=wcr�.C++�����#���A@#R��
�%���T��M�
���2\N)�*�)�)���V��-��Cwc�����h����
�p��t��g�/�9����=5B�>����a���:[� �s��t/�)c���.�����I�N��o�����u��%<����o7d����~�;G���2"!�Z`���~%��7�����p<���`���X���70)8�5������!�bR���|R�
��vN,�����/"��%��
d��odVC��P-5H@do���z�GpY9`�85�����:��3^�a�������i~0�
����A����Lm��>}'�����#Oc�j��
��
����2�b���<���mq��]��z�K�����h�O���Xy��������>sN�j����"�a
J����;���*BVQ,�P��
x)����
�����=�c�<h�UhL-v��������X�y[�����kn���J����Jg�n.�$����<H�F���
4U<4��_Z��?`&��w8d|����G9��A���faT����i��ez
�uE��he��}V?~V�}R��4�y���ho�'�����\�J�J[[��;����n����s�d;����l�]��_�
���e'|����m����_s9~Wf`�(,�$�8?�>�������?2�5 Ff1_��"��_l��F�$���/fS��#�
@�.�����o������)�8�|���2�CW�wLoe��'�x�k`���s����!��z����,j����O,�C�H�S&�Ir��C�`��Y$���,`39iw0���&��%���M"�>���6c�]'6e�����]2�}v�b~l��F�k����|�B��������k����2�fZ��<����%=���k��6N
D�G��� �@)�������%����[�������wT_��l������#f;(G�o]b��1��<���a�L����!�`���c�3�s�����us/��KY^�|0�D��c\���v�9����l`�/��������"0F�����yk�S��b����AU��������)�������Ki�����gJ��\SNc�*|!����"�"��!w�,�����^��Im��^��v�
&�$O�����f�-b�@�w8���q��ZNF���t����m���t1��fYve�=�7�,�l�}0-�i�b����#���[���}��(>�
��'cC���7y.yU�����
��V�A����������{����yZ����}T{v�g�1�/y�e��t���S�����A7��l0
��Y�e[�~���{��c���*�!/�	
0007-wal_decoding-Implement-VACUUM-FULL-CLUSTER-support-v.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
�cyR0010-wal_decoding-logical-changeset-extraction-walsender-.patch�[�w�F����mb0`�/�S?%6N8�/��[�I.GH�c!)z�MS��wfv%���qO�����6������3��]�=g�Z���������?9|3m�����5���7��V�9����d����e��l����l������um��>;�xh��F�l�������O�gZ���O��:�J`|k�>�=8�?jM�W���r=8b��������i�a�56���=;b�33u�b��f����O��R�������M5����F��|O��h�=t�x���	���3O�o/X��/�:�V{n��k���m+���zX,����v��aE������i3�c����9��y�"���6��A��[��wl�����}�������A(�@����G��w�z�7����*p��qg&��N�p�����e�AV�:dS�s�o���~6��N�Wj�:�4��-.�r�0�S�h���i;�X�d�2
����������o��M&`���N�n�����k���/���W?`5�	/ag��B?v5��c8����S������{�xp<������r������w���W��Q�����g��Q�E]!�o�u�>�1q��u���N�G709�|�[�g5��f-�%�_�����8�����J����zJ�p���oz���iw������.�����=�
H���4�7�^.I"����/{������w�k�}�(��o�����C��a��j���^|�����{�
{�G���;�9xd�g�u����e~�y�X���x��b�/���Y`��e�||g��Y�2�f&Z��NOc��S����	��GJ����\������2Q��w	GF�+�V����a�G1U-3���i�Q���y3��n����@�GL�$C��M������l��j5����GeV*�2:�����j���s�����.�^�_��r����c�4Z����7{�}�u�V��pm��8-��O?��Y�=��V+Z��Y�����IK���R8������`[�]��
�Y��=��\)TU4Nlm�a�O�T�0l���i��&�,���~��r�p����<�v��Y�����/VF��u�A�r8�)�P_t ���U��(��Y���k�Y�L��bO���{����!�$�����LC��<K�A�/qA�1*�U��UMI�������E,5����������AH*�"���y��g|��q�{%`"��Q*�
��
��:��Y��4_����P���?�;��&_8���xAe��Xz�W���'Y�3�4&�@x�x����������
���Vk`���������G���
�����i�F��#L�9&�i�8`wM����9���c������\�*U� E ��0�y�����&��pf-@����-c"I����2��)�Dv0�Ut�w�P�-0uFSj�a��;^%�}sfs��-�gQ��X��zv�����|�yC���.����=~�����!W�{0U3���	8Y��W�	'g����H�g|>�FLVg�����wPoA��wXo��"����3C��#~q��[X�T����_j�A�31��u-�yPd�]��}F�(����]���<�E�:v"�hB�6r�)�b�}x4ba
)�9n�)^�lS��%l�`�	�
|j6������l��n���'-�E�l�?���z���R�Ob��v����{p0������}����D�^a��=a�����d`�X=�^&5CKY^h��M��A�r�������o�A��#�c����H�r=�r���@�SG��B��<w�� �r(�I�&r�y
��h�F}�����h?��S{m��i���j��*�������?}�_"1i�������z�R��N��]�}�����5��Z��e�����'�����ZP/��J+.��F����� �����V0	�F��S������6h���=��x��m� �+�����,|��K��,��b���L��m����7g��HS��:7m-��5�KO:}�N�)A5R�	Q5A\�q�]���x,Bq.�h]��h=&+�������G%!��/�!��q���:JTg@��r	��,J����HF~O�^R�*<2�]^��<�: +�_���%��Z��Dv��`�_lO�����Z�.�o
�����u�������N������*���Q�$��W?��?-��P�J�;Q����r��P�cgc��r�1]<������dg�`u�������1�2�e���1&��HA���1�l53�xnz5�2�����^U�XEE���T"���+dB�<�F��d������"�[��+(�b<�s���J*iWR�-�����DNB���X���5r,ULq �o���`#fg�����\ErC�	"��]���C�����U��J�i��6N�pv|tA(ud��6���M���w`F���1��k�}3uHl�i���8.e4q�t& v`1�gn�����`<�����zy��#�k#X���cM2\$~o����`��i������=�5��+�o\N;q{{��?(K��>w��)@kp�,����cT�( u,c�O���3I����+g���`M����U����6(�H�"{fL$�9HZ������ �*`�g�7����<r����N��F����8�3�!�{H��DT	�,�7�v?c�������%W���[��d\���pl�����:��d�C�	"�9K����
Bt�u��9�<�%��D:�������nV����n���.)"��b����Y�b~�y0`'�s+��Dk���FcH�$TZV*l�lCT����_�/���2)(l�(x���/�#&)M��#�����?���Y�Y������s��cHl���]{���9w��|��v=�5������kp������W��"x��jw��zD���A�`�EfM����a�b�fT���)P�I����v��p���4B�uprh�&(��8�\�n6���8��-��`����b�	/������,E��0%Q.b`�La0B`��@1�Ao��P
yM��(
Y%<|�((C6e���_�)�r�?�����^�f���
���4����:�U�������_���������.�PvmAR�]� �[t.x�@����m�����"�[��T�bz��\`��� ����-l
�U�f���b������9m�D�'���.O�f��*)1YM�(�_��9�d�A�`�����M3-
K��h��lnW������`o�����I2�E�9��N��@�c�$R�I8���I�N�s��������h�x�P��5 {U�Xe��F�z9��^���z�h}X�����M� �J�xAs$�#����(�; �m&
v����8�����
=��3�)M����;�)B2)�e��������G��cS��G�x��k/�Eq;����Mn��4��S��xXlE�R��u�O�����Eo���r��y�'�%��"��P����O��~���p�'nY*r��>>l��p��L��09K�uh.F�k��S��w*���"+����iZ��9��:S���Y%N&��}���Spu�G��Jt����F�m
�7������t���z��������G�*,=�A��Tr����������J��$��Q&pZ����Rl��
���$=OQ@�����-f��Xx����2�B��B��v�����X_�U%2�o��l4��,��zXI���8��X�Ws��8��x
�88��o��K��������G~����yu+��U�e�n�Z��=9a���DR>����1F�
K�;�-�~�����) &��J�	q�S��Zsbe3��BN�Y$�6�+6aj%1�Ga�b�
�u�������0�a0V'�d����*������i�I�������+��$#�
gb���)<����b���,�|L.�?W������\��m8M31TYe�D�^0�p�r{v�.���yI����uHk)aH$+��8�P~��pOl{�R���PQ�c�kG_h�!�&����7%ij��]������t\��������C$�H
g����|B F!# �4����-�!�D�!|~�U���nae11�iyt�3u������I���������cBR+�+�xr���|/'	���UFa�P�#>T��G�xV�4+�t���
�����C<�r���,n�6Ix�8+��:kW�iw;ljr��z�O��T4�����u�d�z@b���P2r��:.��	S�y���c����Z����n��]����x��o�%J��{^��;w��\�\��H���K�����[_w)�����W����j�Hc@�:��cm	�)����I�x�<�{������
��V]o���E>��X�t����XVY�E��hh4&�K�d9<��LM�*�#ca"I{�$2�*c��(�+l4_���wW+BW�-H��_)���rtV��������"�
������tbn�L�f+~����@�H����r���O��j�04p���f:��TT~�I�1Q�D���(�J?��+�L��%��jn�����JP~�G)�d�UnJHSeB�[J�>��z�&���'d��L�R��aGCQ
�zt��*�'�?
�)���s���%��5�t}Hc�������I03Oj��_�0����E���|�
���0�B�P���L#����=RB�/_i@��c����&�����5��=�!{������*�����>"�C�k��
�`����Qo|�_S�x�BNw��	�n8�c6Vy��$���F:V��R�I����~�~���+����Q-�m��=)��2�!�"��n"a��4��K_&�cIe��o�����!���������_<p"3R�TuFe���>�MW_�����g�����5�_��B���@;����p�/1� �b�R}��L8'���K�3]���sF���(W�feA�]P%��A]�S�~V5�LI�U�He���G���T��37�`���8���wP�m��!�����%�@G�X�tXC
�0��so�}�{1�DE:R�i�����no'���zI�5���'����k��/���o�F#�����&
p�X42}��3\^���V.T� ���3�V��[)���[��0\�-R4����5�����Yy-'��(	5���4� �Y�O	z�P
�s�\`��f�E��/kG�K���%���x������A�q�f����'��R�E`'��$�c�����,c�����+��wO����~����w6�����g����W��J{��=�
�nn��=��;��y��c.�,���1PCG�K�������A�����=Y�}]�����K�R�Q�u���)���������f|�B������"�D|�LcIF���9�&�����
;�@�
����y�g����%�1.��;ev�c�/�=��yKL�r���+%�O@���<+I}�j:Z����������)�)�O%�O����M*���H�G]�������
%'�ey���`����� ����`��I�u=��B/���8�4��"� �������
��b�q7��/�VN�d;[�/NC�����-�qQ8�����7O����m�����:_h�]=�q!�iJ���b�cY6"�1�y��x��z��k����8�����s��a�"�]��S�
89o��v�T^]���I��~�����jWv�C����s�����=4���~�I�A%T�h�3�/1���X���u��Y�_���d��Vl�������
�dQ�U�d+�0sK%��*����V;��v=04�xsg�������D�Y
���~-b�T�:�E��{��
Q8�����cb^�Iq���T���}0���;���r%�.u�����!iC��Xn��jY�$S��g�-���ls�ZcE��d�%��d����0qR�F>�� �)��$Vq�����4���(P8�&�@��H�/X@�-�W����l(�[��w���yv�8�]u�C�hz�a���10�,���Q��+��V����k�gx����
<��D{E�����g�s)R.�iTZ�v��}�K���U���
���/o�w����7�����	�V$�+����l ����!V��b�i5W8Z�D��`��jS<�j{ �oD����m����O�{6@|?`��]����S�m.��#}���<���R�M�7��1b�(_��=��uu�����Ef(~(��l���v,f�������Eo�{�b�"�rjE�dw�������M�
Ln���M�^^\��=�&J��l���>�h�����/���oW�7��o�R^�^�O_��<�x��s>%1��8Bg��t~��g��`���BN�k��%��n@�:�����c��Q�%5Wt�n�/���r;dyn�������L���x��Wm_F�X�O�a�������a��R9,2�3Q?I���`Nbw�������v��������KJ�xzu&�@����Fe
�p�L-��l/zIp���R����^�(f�f��3|YL%R�kQ�jX�s�����p�11�d���;o�*;���&����5���b2u0hY�<�>����V�pq�e�X?���:cV�{1G�b�F����������1���bS�GD�GDX!"�+%v�DXB��)�_����n7��������GOQ��z�`��������9��9��y���(�0�,�U2O`q��1�r!��D��h���v����^�0E����X>i�9u-1�D�9cNF��d�v�[r)��5���i������5������r�N6a��)�l��9n��YP"0�[�����_5X������1G2�������.'Q������S��&q���0�� J�j�-�f�F��Hv1<���y[�pD�N0#Q?r]�-
>�r6x=-"�w
���(K��""�HkRqh�	`�f��,�FyI����aOu����I��y<\,���c��6�&���|c��gfZ�t�+��_���@�11��j�6��r0b�O�7/?{�������A�w�r�������Qj�]$�z�s�Lx>�$\�cu�	gD7I�A���9�r~3�c<�Tq�������������?U�Y���$��)�-����&���Y������cD�J����1�������sHv��?r�7�;��D�h>��ix��K/��^zrve�Z����@u����8�����z���v7�����`j
x���W���v��p�����%8�$�H>�qG�l�x�s�Q�V�\���fKB{�6���"+r���l}��h�Z�WU��I7;���+����`ij���t*#5ojy\����\�EI[���i>�
�J�&
W:���
W�*_`u��m��x�)��%^Bb����N�H����N������^�5�u����m���c4��~�m|��7�
�@�Ns�s�hh33��J��G��NQ
�����g���n�7
��1�f���'� z�N�<�w$��g���N�5��9��fI#4�A�S�"q���-:l<�R��� ���4[�b~c�Z��e+�s�Q�\J&��;w48\j�����t6�jW�M�e�M�z
�z�=��c{I5:s�R-�L���ct�HE��)�}�66I�x��49!�S�B�H����l��dl4�m�SB�5D�u�k��lB��G�� �%�������p���a��*-|]%v�tP���P�QF1'U7C�Am��=�8[��RG��'s���b"�+��45�|���w����f�d�_,S�S,�sw��=L�6��1���v��0]���h9�`��$��
1Q0ob~�]IZ�cc��J3E��x��5��)Dl��7�<h��u"����)��3�E�*��6'�6Hg��|�HK�V����������7��c��a�	���WC$�Z������Tk��YZ�f���F�g�����8=�Oj+����Ia�(��6�Nx�af
@��z�����
�,��j����:_��A�K:%�R}�g�I?2d�v�['���a��g1f���n��,���=�"u;a����[2z��$al����3�w�s'8��WT����1�t�-�pj~��$����@t�x���q[�g���b�}��NB��N����*������������J�R�XX��8%*�u�_n��`~sxxPo��K�oHw���`/nb�Ld�Y�!rC��!ni0U��
&K,�UX����{������\���� �E��IX\��m���~_����,7������G��6�L��VY!v���Y�����[
��vw�'f��Cr����]�:i��R��7��I�����2��6��U��B�\�m����,��U��+���o�SA;A�����3�J���43B[G���w�����*�T�/������pbm`����uD��������u�U�z�u��!!0�C,�9�T�Aq��\�3�*l��}/�h.P�`�	J�?��W]��+����:��)�.b���bu��VK���p�<j��9�zx��3�����������)|��y��/�;�,61	9
/�Z�$�����=��t��#��f�,1]�5��r �7��_���hv�0�k<v���t5�Q=m���������[�����	���k�a�	�����	+��kS����k6��Fs��4��������q�e������������Hs��
�u8�Wp���9���$��E|{"����6Le�i.�5�����=7Q?�S��5p��$#o�^=�8!�~`b����:dhQR"�hj"0���)��
)�v�A�4C�0��@�-��[�uC*�I(V5�J6X��7�m���9Q�r%�����I��&I�4���x��f�.��f[AY7[�ui�Az���
T^�v
����V�|uB��_���K(�-���$h*N>c����y�s�(����3��St���D�w�x���U�v����&��q���V1�b�-E��]����g�A�P0�TpM>�O����~.�q�`>r	'��1y�	�������.0xKj(g����
�Xu�6�6�6�6����b�x%L����$~WM���<�0F��
a�1�p60��D���8�Z�R��(�`����*��|kr��������j��lE��\�"T|�Un/o�5�x����Wi�s��p3��&6���R���x�/[{e@�*�!m1L`�2
�f[�D���&	Gj�#.�&`w����3M�\��::2%3&O�CH"<��c���p"�������&�����a�i��q39���T��SU���8U%9�7;D��V��s���dJ+R����.������Te]w�|�E38l�+s�{���jia�
�rC�����[�N��;�6��a%'G4���X��[���|�Qs+�|����"A�.�z��HC����Q\��9o�<*A@@$X�p����y_���f�:/���<������**�/���H��{xtx�O����[���U�����a�_�'��|)�<
�f�o���V�U�,�'XU�cw��v�rQ��S����Q����I��?����j>J���UC��f��c���v���#��q��� <
�����,T^J���P�
7��/���]��Z���&�L�5�f�9#
;vMi�O�(:hv�������j.���e����F����j�%f^�&�p9����-����*[��8���o9���l����U�d�L��p��-��������{QX�B�V�o��}"&j������\^����KWy>�����-�e<���|�ywm]���$��H�JZ�^t���++q����E�N��+G�1�����C�����{H����[�3��SF�b�)�*��H�:�}J�K���I��tF�������k5O�����t�|����*�9u�
����T����J%���3�/rFy�
�q=��Z����#{�l���`1����[�8��.�_���G����G-?\L/#�1���Iq��������'�z
Q�U��Kv�����eKi��<��\f/�P�����$M�&w�2xvf�5M�Noo���f�
��fx{rr�{�:��l~���?=�tw��
0011-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0012-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0013-Fix-pg_isolation_regress-to-work-outside-its-build-d.patch.gzapplication/x-patch-gzipDownload
0014-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
#140Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#139)
Re: logical changeset generation v6.5

On Tue, Nov 5, 2013 at 10:21 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:

I have pushed patches #1 and #2 from this series as a single commit,
after some editing.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#141Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#140)
Re: logical changeset generation v6.5

On Fri, Nov 8, 2013 at 12:38 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Nov 5, 2013 at 10:21 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:

I have pushed patches #1 and #2 from this series as a single commit,
after some editing.

And I've also pushed patch #13, which is an almost-totally-unrelated
improvement that has nothing to do with logical replication, but is
useful all the same.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#142Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#141)
Re: logical changeset generation v6.5

On 11/8/13, 3:03 PM, Robert Haas wrote:

On Fri, Nov 8, 2013 at 12:38 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Nov 5, 2013 at 10:21 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:

I have pushed patches #1 and #2 from this series as a single commit,
after some editing.

And I've also pushed patch #13, which is an almost-totally-unrelated
improvement that has nothing to do with logical replication, but is
useful all the same.

Please fix this new compiler warning:

pg_regress_ecpg.c: In function �main�:
pg_regress_ecpg.c:170:2: warning: passing argument 3 of �regression_main� from incompatible pointer type [enabled by default]
In file included from pg_regress_ecpg.c:19:0:
../../../../src/test/regress/pg_regress.h:55:5: note: expected �init_function� but argument is of type �void (*)(void)�

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#143Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#142)
2 attachment(s)
Re: logical changeset generation v6.5

On 2013-11-08 17:11:58 -0500, Peter Eisentraut wrote:

On 11/8/13, 3:03 PM, Robert Haas wrote:

On Fri, Nov 8, 2013 at 12:38 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Nov 5, 2013 at 10:21 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:

I have pushed patches #1 and #2 from this series as a single commit,
after some editing.

And I've also pushed patch #13, which is an almost-totally-unrelated
improvement that has nothing to do with logical replication, but is
useful all the same.

Please fix this new compiler warning:

pg_regress_ecpg.c: In function ‘main’:
pg_regress_ecpg.c:170:2: warning: passing argument 3 of ‘regression_main’ from incompatible pointer type [enabled by default]
In file included from pg_regress_ecpg.c:19:0:
../../../../src/test/regress/pg_regress.h:55:5: note: expected ‘init_function’ but argument is of type ‘void (*)(void)’

Hrmpf...

I usually run something akin to
# make -j3 -s && (cd contrib && make -j3 -s)
and then in a separate step
# make -s check-world
this is so I see compiler warnings before drowning them in check-world's
output. But ecpg/test isn't built during make in src/interfaces/ecpg,
but just during make check there.

ISTM ecpg's regression tests should be built (not run!) during
$(recurse) not just during make check. Patch towards that end attached.

Also attached is the fix for the compilation warning itself.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Recurse-into-ecpg-test-during-normal-builds-instead-.patchtext/x-patch; charset=us-asciiDownload
>From 99aaf866b315af21fddd858c3d2922ca21918f8c Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 9 Nov 2013 11:49:27 +0100
Subject: [PATCH 1/2] Recurse into ecpg/test during normal builds, instead just
 during make check.

ecpg's make check, which currently compiles the contents of ecpg/test, is only
executed during "make check-world" and not "make all" making warnings during
the compilation of the tests hard to spot manually.
Instead build the test framework during a normal toplevel "make all".
---
 src/interfaces/ecpg/Makefile | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/interfaces/ecpg/Makefile b/src/interfaces/ecpg/Makefile
index e397210..4f05ae4 100644
--- a/src/interfaces/ecpg/Makefile
+++ b/src/interfaces/ecpg/Makefile
@@ -2,7 +2,7 @@ subdir = src/interfaces/ecpg
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS = include pgtypeslib ecpglib compatlib preproc
+SUBDIRS = include pgtypeslib ecpglib compatlib preproc test
 
 # Suppress parallel build of subdirectories to avoid a bug in gmake 3.82, cf
 # http://savannah.gnu.org/bugs/?30653
@@ -16,15 +16,13 @@ endif
 
 $(recurse)
 
-all-pgtypeslib-recurse all-ecpglib-recurse all-compatlib-recurse all-preproc-recurse: all-include-recurse
+all-pgtypeslib-recurse all-ecpglib-recurse all-compatlib-recurse all-preproc-recurse all-test-recurse: all-include-recurse
 all-compatlib-recurse: all-ecpglib-recurse
 all-ecpglib-recurse: all-pgtypeslib-recurse
-install-pgtypeslib-recurse install-ecpglib-recurse install-compatlib-recurse install-preproc-recurse: install-include-recurse
+install-pgtypeslib-recurse install-ecpglib-recurse install-compatlib-recurse install-preproc-recurse insta--test-recurse: install-include-recurse
 install-compatlib-recurse: install-ecpglib-recurse
 install-ecpglib-recurse: install-pgtypeslib-recurse
-
-clean distclean maintainer-clean:
-	$(MAKE) -C test clean
+all-test-recurse: all-preproc-recurse all-ecpglib-recurse all-compatlib-recurse
 
 check checktcp installcheck: all
 	$(MAKE) -C test $@
-- 
1.8.3.251.g1462b67

0002-ecpg-Adapt-to-recent-pg_regress-init_function-API-ch.patchtext/x-patch; charset=us-asciiDownload
>From a0aa8180a849751df6fdcf49a83ff87d906c8aed Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 9 Nov 2013 11:53:33 +0100
Subject: [PATCH 2/2] ecpg: Adapt to recent pg_regress init_function API
 changes

This fixes the warning during ecpg's test compilation introduced in
9b4d52f2095be96ca238ce41f6963ec56376491f.
---
 src/interfaces/ecpg/test/pg_regress_ecpg.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/interfaces/ecpg/test/pg_regress_ecpg.c b/src/interfaces/ecpg/test/pg_regress_ecpg.c
index d01703e..740b566 100644
--- a/src/interfaces/ecpg/test/pg_regress_ecpg.c
+++ b/src/interfaces/ecpg/test/pg_regress_ecpg.c
@@ -159,7 +159,7 @@ ecpg_start_test(const char *testname,
 }
 
 static void
-ecpg_init(void)
+ecpg_init(int argc, char *argv[])
 {
 	/* nothing to do here at the moment */
 }
-- 
1.8.3.251.g1462b67

#144Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#139)
Re: logical changeset generation v6.5

On 11/05/2013 10:21 AM, Andres Freund wrote:

Hi,

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:
* Fixes full table rewrites of catalog tables using the method Robert
prefers (which is to log rewrite mappings to disk)
* Extract the REPLICA IDENTITY as configured with ALTER TABLE for the
old tuple for UPDATEs and DELETEs
* Much better support for synchronous replication
* Better resource cleanup (as in we need less local WAL available)
* Lots of smaller fixes
The change around REPLICA IDENTITY is *incompatible* to older output
plugins since we now log tuples using the table's TupleDesc, not the
indexes.

My updated plugin is getting rows with
change->tp.oldtuple as NULL on updates either with the default PRIMARY
KEY identify or with a FULL identity.

When I try the test_decoding plugin on UPDATE I get rows like:

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:1
ii_reserved[int8]:144 ii_total_sold[int8]:911

which I think is only data from the new tuple. The lack of "old-key"
in the output makes me think the test decoding plugin also isn't getting
the old tuple.

(This is with your patch-set rebased ontop of
ac4ab97ec05ea900db0f14d428cae2e79832e02d which includes the patches
Robert committed the other day, I can't rule out that I didn't break
something in the rebase).

Robert, I'd be very grateful if you could have a look at patch 0007
implementing what we've discussed. I kept it separate to make it easier
to look at it in isolation, but I think in the end it partially should
be merged into the wal_level=logical patch.
I still think the "wide cmin/cmax" solution is more elegant and has
wider applicability, but this works as well although it's about 5 times
the code.

Comments?

[1]: http://git.postgresql.org/gitweb/?p=users/andresfreund/postgres.git;a=summary
Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#145Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#144)
Re: logical changeset generation v6.5

On 2013-11-09 17:36:49 -0500, Steve Singer wrote:

On 11/05/2013 10:21 AM, Andres Freund wrote:

Hi,

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:
* Fixes full table rewrites of catalog tables using the method Robert
prefers (which is to log rewrite mappings to disk)
* Extract the REPLICA IDENTITY as configured with ALTER TABLE for the
old tuple for UPDATEs and DELETEs
* Much better support for synchronous replication
* Better resource cleanup (as in we need less local WAL available)
* Lots of smaller fixes
The change around REPLICA IDENTITY is *incompatible* to older output
plugins since we now log tuples using the table's TupleDesc, not the
indexes.

My updated plugin is getting rows with
change->tp.oldtuple as NULL on updates either with the default PRIMARY KEY
identify or with a FULL identity.

When I try the test_decoding plugin on UPDATE I get rows like:

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:1
ii_reserved[int8]:144 ii_total_sold[int8]:911

which I think is only data from the new tuple. The lack of "old-key" in
the output makes me think the test decoding plugin also isn't getting the
old tuple.

(This is with your patch-set rebased ontop of
ac4ab97ec05ea900db0f14d428cae2e79832e02d which includes the patches Robert
committed the other day, I can't rule out that I didn't break something in
the rebase).

I've pushed an updated tree to git, that contains that
http://git.postgresql.org/gitweb/?p=users/andresfreund/postgres.git;a=shortlog;h=refs/heads/xlog-decoding-rebasing-remapping
git://git.postgresql.org/git/users/andresfreund/postgres.git

and some more fixes. I'll send out an email with details sometime soon.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#146Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#145)
Re: logical changeset generation v6.5

On 11/09/2013 05:42 PM, Andres Freund wrote:

On 2013-11-09 17:36:49 -0500, Steve Singer wrote:

On 11/05/2013 10:21 AM, Andres Freund wrote:

Hi,

Attached to this mail and in the xlog-decoding-rebasing-remapping branch
in my git[1] repository you can find the next version of the patchset that:
* Fixes full table rewrites of catalog tables using the method Robert
prefers (which is to log rewrite mappings to disk)
* Extract the REPLICA IDENTITY as configured with ALTER TABLE for the
old tuple for UPDATEs and DELETEs
* Much better support for synchronous replication
* Better resource cleanup (as in we need less local WAL available)
* Lots of smaller fixes
The change around REPLICA IDENTITY is *incompatible* to older output
plugins since we now log tuples using the table's TupleDesc, not the
indexes.

My updated plugin is getting rows with
change->tp.oldtuple as NULL on updates either with the default PRIMARY KEY
identify or with a FULL identity.

When I try the test_decoding plugin on UPDATE I get rows like:

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:1
ii_reserved[int8]:144 ii_total_sold[int8]:911

which I think is only data from the new tuple. The lack of "old-key" in
the output makes me think the test decoding plugin also isn't getting the
old tuple.

(This is with your patch-set rebased ontop of
ac4ab97ec05ea900db0f14d428cae2e79832e02d which includes the patches Robert
committed the other day, I can't rule out that I didn't break something in
the rebase).

I've pushed an updated tree to git, that contains that
http://git.postgresql.org/gitweb/?p=users/andresfreund/postgres.git;a=shortlog;h=refs/heads/xlog-decoding-rebasing-remapping
git://git.postgresql.org/git/users/andresfreund/postgres.git

and some more fixes. I'll send out an email with details sometime soon.

93c5c2a171455763995cef0afa907bcfaa405db4

Still give me the following:
update disorder.do_inventory set ii_in_stock=2 where ii_id=251;
UPDATE 1
test1=# LOG: tuple in table with oid: 35122 without primary key

\d disorder.do_inventory
Table "disorder.do_inventory"
Column | Type | Modifiers
---------------+--------+-----------
ii_id | bigint | not null
ii_in_stock | bigint |
ii_reserved | bigint |
ii_total_sold | bigint |
Indexes:
"do_inventory_pkey" PRIMARY KEY, btree (ii_id)
Foreign-key constraints:
"do_inventory_item_ref" FOREIGN KEY (ii_id) REFERENCES
disorder.do_item(i_id) ON DELETE CASCADE
Referenced by:
TABLE "disorder.do_item" CONSTRAINT "do_item_inventory_ref" FOREIGN
KEY (i_id) REFERENCES disorder.do_inventory(ii_id) DEFERRABLE INITIALLY
DEFERRED
TABLE "disorder.do_restock" CONSTRAINT "do_restock_inventory_ref"
FOREIGN KEY (r_i_id) REFERENCES disorder.do_inventory(ii_id) ON DELETE
CASCADE
Triggers:
_disorder_replica_truncatetrigger BEFORE TRUNCATE ON
disorder.do_inventory FOR EACH STATEMENT EXECUTE PROCEDURE
_disorder_replica.log_truncate('3')
Disabled triggers:
_disorder_replica_denyaccess BEFORE INSERT OR DELETE OR UPDATE ON
disorder.do_inventory FOR EACH ROW EXECUTE PROCEDURE
_disorder_replica.denyaccess('_disorder_replica')
_disorder_replica_truncatedeny BEFORE TRUNCATE ON
disorder.do_inventory FOR EACH STATEMENT EXECUTE PROCEDURE
_disorder_replica.deny_truncate()
Replica Identity: FULL

The test decoder plugin gives me:

table "do_inventory": UPDATE: old-pkey:

a) The table does have a primary key
b) I don't get anything in the old key when I was expecting all the rows
c) If I change the table to use the pkey index with
alter table disorder.do_inventory replica identity using index
do_inventory_pkey;

The LOG message on the update goes away but the output of the test
decoder plugin goes back to

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:5
ii_reserved[int8]:144 ii_total_sold[int8]:911

Which I suspect means oldtuple is back to null

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#147Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#146)
Re: logical changeset generation v6.5

On 2013-11-09 20:16:20 -0500, Steve Singer wrote:

When I try the test_decoding plugin on UPDATE I get rows like:

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:1
ii_reserved[int8]:144 ii_total_sold[int8]:911

which I think is only data from the new tuple. The lack of "old-key" in
the output makes me think the test decoding plugin also isn't getting the
old tuple.

(This is with your patch-set rebased ontop of
ac4ab97ec05ea900db0f14d428cae2e79832e02d which includes the patches Robert
committed the other day, I can't rule out that I didn't break something in
the rebase).

I've pushed an updated tree to git, that contains that
http://git.postgresql.org/gitweb/?p=users/andresfreund/postgres.git;a=shortlog;h=refs/heads/xlog-decoding-rebasing-remapping
git://git.postgresql.org/git/users/andresfreund/postgres.git

and some more fixes. I'll send out an email with details sometime soon.

93c5c2a171455763995cef0afa907bcfaa405db4

Still give me the following:
update disorder.do_inventory set ii_in_stock=2 where ii_id=251;
UPDATE 1
test1=# LOG: tuple in table with oid: 35122 without primary key

Hm. Could it be that you still have an older "test_decoding" plugin
lying around? The current one doesn't contain that string
anymore. That'd explain the problems.
In v6.4 the output plugin API was changed that plain heaptuples are
passed for the "old" key, although with non-key columns set to
NULL. Earlier it was a "index tuple" as defined by the indexes
TupleDesc.

a) The table does have a primary key
b) I don't get anything in the old key when I was expecting all the rows
c) If I change the table to use the pkey index with
alter table disorder.do_inventory replica identity using index
do_inventory_pkey;

The LOG message on the update goes away but the output of the test decoder
plugin goes back to

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:5
ii_reserved[int8]:144 ii_total_sold[int8]:911

Which I suspect means oldtuple is back to null

Which is legitimate though, if you don't update the primary (or
explicitly chosen candidate) key. Those only get logged if there's
actual changes in those columns.
Makes sense?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#148Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#147)
Re: logical changeset generation v6.5

On 11/10/2013 09:41 AM, Andres Freund wrote:

Still give me the following:
update disorder.do_inventory set ii_in_stock=2 where ii_id=251;
UPDATE 1
test1=# LOG: tuple in table with oid: 35122 without primary key
Hm. Could it be that you still have an older "test_decoding" plugin
lying around? The current one doesn't contain that string
anymore. That'd explain the problems.
In v6.4 the output plugin API was changed that plain heaptuples are
passed for the "old" key, although with non-key columns set to
NULL. Earlier it was a "index tuple" as defined by the indexes
TupleDesc.

Grrr, yah that was the problem I had compiled but not installed the
newer plugin. Sorry.

a) The table does have a primary key
b) I don't get anything in the old key when I was expecting all the rows
c) If I change the table to use the pkey index with
alter table disorder.do_inventory replica identity using index
do_inventory_pkey;

The LOG message on the update goes away but the output of the test decoder
plugin goes back to

table "do_inventory": UPDATE: ii_id[int8]:251 ii_in_stock[int8]:5
ii_reserved[int8]:144 ii_total_sold[int8]:911

Which I suspect means oldtuple is back to null

Which is legitimate though, if you don't update the primary (or
explicitly chosen candidate) key. Those only get logged if there's
actual changes in those columns.
Makes sense?

Is the expectation that plugin writters will call
RelationGetIndexAttrBitmap(relation,INDEX_ATTR_BITMAP_IDENTITY_KEY);
to figure out what the identity key is.

How do we feel about having the decoder logic populate change.oldtuple
with the identity
on UPDATE statements when it is null? The logic I have now is to use
oldtuple if it is not null, otherwise go figure out which columns from
the identiy key we should be using. I think most plugins that do
anything useful with an update will need to duplicate that

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#149Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#1)
13 attachment(s)
Re: logical changeset generation v6.6

Hi,

Changes since last version:
* fixes around the logging of toasted columns for the REPLICA IDENTITY
in UPDATE/DELETE. Found due to a question of Robert's.
* Initial documentation for the additional wal_level, but that will
require additional links once further patches of the series are committed.
* Comment, elog/ereport, indentation impovements in many of the patches
* Add the isolationtester tests to "make check"
contrib/test_logical_decoding and introduce "installcheck-force" that
forces an installcheck run even though it requires special
configuration parameters.
* the heap rewrite checkpoint code now skips over files not named
"map-*" instead of complaining if it cannot sscanf() the filename.
* pg_stat_logical_decoding system view: 'renamed' the numeric 'database'
column in 'dboid' and added a join to pg_database
* Remove several FIXMEs by implementing support for dropping data of
transactions that were running before a crash.
* Add CRC32 to snapbuild state files

Questions:
* Should we rename (INIT|START|FREE)_LOGICAL_REPLICATION into
*_LOGICAL_DECODING?
* Should we rename FREE_LOGICAL_REPLICATION into
STOP_LOGICAL_REPLICATION? stop_logical_replication() currently is the
SQL level function...

Todo:
* Implement timeline handling. We need to switch timelines when extracting
changes on a standby. I think we need to readTimeLineHistory() and
then liOfPointInHistory() for every segment.
* Once guc and recovery.conf are merged, we might want to support using
recovery_command to gather older wal files.

I am starting to be rather happy with the state of the patch.

01 wal_decoding: Add wal_level = logical and log data required for logical decoding
02 wal_decoding: Log xl_running_xact's at a higher frequency than checkpoints are done
03 wal_decoding: Add option to use user defined tables as catalog tables
04 wal_decoding: Introduce wal decoding via catalog timetravel
05 wal_decoding: Implement VACUUM FULL/CLUSTER support via rewrite maps
* should probably be merged with 04, kept separate for review
06 wal_decoding: Only peg the xmin horizon for catalog tables during logical decoding
07 wal_decoding: Allow walsender's to connect to a specific database
08 wal_decoding: logical changeset extraction walsender interface
09 wal_decoding: test_decoding: Add a simple decoding module in contrib
10 wal_decoding: pg_recvlogical: Introduce pg_receivexlog equivalent for logical changes
11 wal_decoding: test_logical_decoding: Add extension for easier testing of logical decoding
12 wal_decoding: design document v2.4 and snapshot building design doc v0.5
13 wal_decoding: Temporarily add logical decoding regression tests to everything
* shouldn't be committed, but it's useful for testing

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Add-option-to-use-user-defined-tables-a.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
�@�R0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch�[�w����l���Nn�m��`c{69�k<�������{8-��B"j�����}U-	!���?,g����U��U���a0����#��5��5�j��s,��a]Yr_I�R��fS�������D�v���~�V/}�q�;���c�b��!��/��������~.]�H����7�������F�_vj�W������N�����?�Zs���1�^�Qv����D��(��Vt_����+�-#�������B���R�7r�pS -�zT! ���Ii[�� tTh���
c���~��#*��[@��������b@"�BG1��h�PO�_9�I�4a���X��9�� ��:�W�=
"0�/�(�A�A���/,% _����b@i�� T�*� �����zn��@���
��c0X��1��B�DG�8/Y-�n4���,i?���&7���0��1��`<�C&������b;�C�9�%�$�[=yP0�h���,��4v}�k*���h��{�������u�sy�/������p��]�8r=z��CWG��2�
i&b��W>�%����{��57B5� T2�=�u������p�&����i�Qy����Rr�t�w�Y�5|�i�m���b!�XC<�CC��0�s���ak!p�xKE+ R�&+[��Da�����*,������C���u0���<�����s�[��c�������i�&lI��"�����,}��`��0�
��%��T�@S�!f�8�a?o_XO�L�
���PE�l��]Zu�X��a84E*�	w���	�OG��@GC�o�nG<'�B�m2��^2=���zt�TX����"_�T0TC����|q�Q��J:���H|�R�>)�������h�>������Z�Sk���F����v�_��������L���a�w8��n��.8���y�(qv�t�����M����b��|��y(�!�3�S�Q�������vwwKB����rO�������#�U[>��C�����x�	jZ%H�!;��/�s���ezs��kA��`�'����[��_��������y��j�uDSP�J������2�Gi��
��D����Z{7�A
\��5���L��%���k y��I��VK������5C���7�k)���b�f������x��^	�?��a�x��71�	��0�Vc	�z<�z�ob��!��P�����k
�QTg���C9+������Au����
��7y���v���1�<h�<�R�&��e!�����al��)��B2�>���?���Mx�0��k�R�61�k����2h�G�RH��������7��\���d]��bG�vu�4Sk��<B,��,����gcO'+\Iuq�I!��&�CK��_{2�@E�?��y���Q�h�9�&���8/J��g�]tW��v��a��iq�D��Y��-rl��h�F����M��~x���d�B���j;�9�"�6ne=��"Q���[���LW�j��h���M2�d��C����2��
[\]�x~�h��#����"*?��f����zH�Vm0�����@'��SG��kD�a�w�*0:���27v��pHU���k��f����7��7g��_�N��oBQ�>�	G.�X���|P�.�*��o-��7����`s��Tr��@���J��3$��!%Nn�c5�::�V�[My`�({���kh���������:���P�&
�������Q�������8���e�Q�)�����8���@TJbcCl��������;��_�.�lll�m#1�bS�	������\��'���"*��\��0�X����C����
����V�����h��������S<
?o}(���[{ ��G-]�Z�&���Vw*b
���*M!	���np�D��:a����������v�9�������|��������U��s�����.��L���%���D���#�Q�$],#�U�����S1/IS���Th�d���-3���@2�>�Q&��S�h����$�I���;��j���4^\N,k�Sa-��q)�a)��w���!2��:�\���a,5D�g
�!B�p���X.4`g>`�^;"�����4�e�p�1i�e!Ns�S��a��$��zXA��{]3��������������y���l?L��\�r�#��MK��������� +Gc �t6�?T�5�3�B��B�*��J|�L�f��>��\��$�L���CrDT
BU���Z��E���9�������+A����~�����^wo���������J*NV�wJ;��
�d0q�/�8Rcph4�Bg!=o�=�Z���b!��t�z;j�����9j�7p��s�+�����_<9�bnN���g���m����D�|>L����<��%�
��������J��������w��i�6v�r�����q������k[la�Tv_�G��J��O
�X4�3��9$��&���L�L�4!]�N���������Q��JD�	�*�t�,�����%w`.� �9�M�E�o����u���+'i������Yn�G]��3�E����a�	�Rk��P��l�z������(�\x|Su�9���fz,$.�k��\�����8�,���=�I��J��e�����7��b���\#:>��-c���v/}��?Q����������=�'���sO�}XJ8���j���Y��3am~y.� ���hA�$W�j�S�e
�����Y�J�L���%W����D7�z��8����-vw��$=�u&~==���F]��
���4�j���9t���X��"�2��y<������E�A�O&��C\����d��&���^������
+Kl��u ��A��<�R!#y�6��7�������R?b_��<�]�_��_�$��|?
|�-q)�������V�|v�9��\E�"No/JE�nu�����C�U4�$�� B;~y}y�3���u���t,\)>1���m,~pW�}A�	�e�K�t���|���n���f��{�7c>v�n������4'l���u������]���|�qw+��� �R���V�8Lu��vA[F�,��z�_>�^�X��Q���Y����������e+�{y'���:����[����o�&�a0�sG��������F��E[��|��Wi�v7�4Z�
�|/���|�q����qP&�"��M�2��t�K��� �9I3M
��o�-J��y�Ih���H�>���,
�s����$�g���'�kT��XU�y�(�^�Z�U?x�w����a-�#�j����V!���0������y��i��D�&3�]����nv��y���j4��A�\dy�U�"��	��c��V��
s��<P���"G�i��666|nzI$�s�'���'xWM���g'���}�zS�4��u<��`F����u#�k_T8����	����Q�8��*"q'#���v\���}��P[�X���0QF�l�q*?B��D��������&q�p�p�5(��	R����I*���2T�<K�������Q`��Zj��������7R�/��3e�bx��B7�`_��P��r |5MH�$	�!��������� ����.os�������X�8�^�~���]v:w�
W�6��9U������e������������V2*�L���k���_����Y��I+T��m3���0z�csT$]���Ku$��0PYoU�;�I�������F���Tc������!]4��
[�
Q>K�������M��K�LV��q�%�(B�����j��W8��g��L�'Ny�%�l5���~l�W8��39c������n��}���u��;vao���J}U}��>�_���2F2��,.)r�����(���$g�ik�
�5%�QBe�Ho*g��)h�����^�4_����o�fO�P(���o����j:�,��L����
����9��-�&�_#a��f�d�>����!��T�����M��ZE�v��h���,���O��U�4��-��UM�f\cL��$��bRA|�c���%�qw.[p&\���l�*��B&�W�-�F	�0,�7��}�T�����( �7S.������4��`-�~���b�^��<�Q�:��}Uo���������[9����6���?R�2tc����>������n��2p!@����'�8�.?/�scwZx7'�����1O��_�q��r��Ts��j@yf���E�C���2�4���~�����r?����]�O�4�����U�����g,�N�K���E��@��ar��il>�j�^��������=?�V��%D��U�T��
q��w���.�)-�A�g&�:��4�������?&
�	�WO�q���J;:�`Q���'��c(3�Z�K��v�4e�4e�j� �oZu�����!H���[���1?:�[��VF�:���f=.�Y-kp�r�ied�r+��yii a~G+B(�CQ\J?668��������B�����k�����y��1MG$%�������X#������������	������A1�T�����o�z�'f4���Mb)�_hP|lBo�p������`T�)�"S4�c����R@�Ep!���(���}�,}c$�$A�� Y��&��G�Nd�qm��{��9����%���6�P.��|�����ep�����L��8G��Q$������d�����?���y7���L�D�*������2����*��O;�v��9G�y����*�V��e��7Z����b/_I���j��P�Yx���&��������r@���!�3�<�~�N��!�*.0��N�����j_�]����uS,0�u�&]��9��\u�'^����C�1�FJ+�qtt�{7Y�!@�O������H���g�s�
n)�_FG��wF����([xGt�!�7cd-a�t]'�^�%�C�3[p��u��6�w��gN@�
����:���y���S�y�i!�OTl��c:�Oz_��������$>X.�V
(���**�0�{��c=D�����7�k�oc����Z�+%G� ��
�U�[����2�6���P�K��H�����1��ER#�g��)���O�,�M���b8S��-��zI{�LO:F��-nI��;$�%�e�o�fM&Q������<v�uYA)�tfICWRR\zez'������vK3/�����,&���RcA��z�v��UPaK%mfT�����P��^P��>C��%G%-�z�F�H��<m;z��G���U��~8�Z��i���;�o:���M�������(�*U�r�]?A@?�%����Q5O�O���k��O�}���	�aagc�N<��B?�"<>���"��(^����^��W~�3��������t�{%�
���
rCj�y���N�f8�� �M�/l���������D����Z|����S�b�e/h7��V�]�3�<�{�9���.�N���������y�����^;���A���f.���Y�s����i�S��U�v�Vw�J���K���3��{��������H�?KU�(�b�2�z �k4 K==���TeA�j��*m����Yc��Z�����������������~�mp���<�d��En
j���p���@�s�wh�[yg�������9���Dj��i��/��l�TW>�0regw1�g��%�����l�T��%����p
}����Ch0)��~��}<R:z}�z�s�	ep���c��c�!0M�j����_�����W'��p��r����O'�G��������?���z��4�6!�x�8�P����a@�h2GW�h�A�� q��t�$������;<���.(��%�w�����M�N�d	a��	s��9����k�4{�����������W;�?�?�U������?�?������������hY�g�O>�_z}r�E������!�f���Sw8�P2��Z����Pp(����������������~���&��c\>������6���0�����3d��[B�� [�L7�����3���VO~L��vN^�?8�k�~x��?����'��sf�KF������0�I��
L��+��
�d��^)�$i�
�P����T��~�?��O{���w�O��Q���5�6e�����_�0���(�b�R�1`�K?e���n��������H0�+�}d_f���������T4j��f���U�T��$�z#1�*7b-��hGi�
bh�������A-��I�G�:�@9w%��
� }&�x�9�����f���e�7gRQ�sI��������L�%����&����E�h�G��!��L�6��l�%�[~�,�V��R����yu|_�
2��)YV�WQ�������wi���+k��(�Z�P���,�l^Hs��
����g�]��U�����k�eki���x�0�B�HB+$���U�1iG�R���m��~���6����������CS����q��rXk>s��c�R���8#�D��a���'qSu����m��t��{-�[�Ire�b����un+����;4��+L������pd����zM��!3B�qE����)g������ �Z��3�0��n�X%��{�fY)\X����e�)k��J�����d��]}i�����9����bl]�^<���f�'�x����Z�
p�p�`�f�%���<x8��	�8�p�.������c��<H�k����;�&!$�����q��-���k��0)�A;C���i4�#^��X'��#�]���Q�����/�~�6}��q3;��V0VIz��Hsy+a
3�2�\�c`.�~n���,u�gD�<z��w��	�tn�=�jk�J���|��V������Dl�/W~���%���Lk	�Y��o���H��A��.�.,� ������|���+�%���h���R�td��6����+�����G��n��1/����:������G�7d��3�s�r��FW��`V���:{0���W8^�$K.g�^����ghI�G���;�=�y���.�h���^��E&�z��s�_r�,�l^qV�u9��e4���f6s)����
����I$e�;���!	l9>@�Tw���qV��$n	>��yE����B����7��QN��(�yt�KP>�~��}:��A�r�"G�}�5VW�������������%�i����q����#-���:���@�,�@P�M�HiI�]��E39����5���s:����r�n�����=|v�/)����R�@"��pd9}&����}U\>����������J�rb|5�cb�~v8'��x4$Tw�]��W���Nx?Ab��h�����FzGqyr�VE�.�|�o��'mB���/���s�[�������A�>�&������Q\�<h�	����3�)}~�������
:�u.��.}�ML���&Rf( �|����M!�9G�s��n��|���k�C��\*8p���Bdk-�[X�*�8�d��'}��������e�e�Q_}�x��K���*�'E#�.}�����a+R���jQY�$d[�'%�N��pL��0K3���B�����s����
�������W���/lZ2����`�1������`�P��%]�3(w��dZ��/h�I������@_�d�����q���V�� �W��yb�������Bk�~g���"�U/�$�M�3��>�u�������n��t��fL��W'![J�k��<\p���a�	�9����U_0�q���$���t\�{{�����M���������\1�&�a�����x�ae�		�@T�Ze�����a�y�wu����`�R���[�9�N(q�j������9N��(�t��gQfq�8��l9����%�W6��Y���M���=`- ���<D����|����M����������|���@��������}�|�{����.��Q
�����X�������]s����VS�bGJ�pw�$G�h���p-w��f�)y��`c���:_v�~ER"�l>�Nr���yn5�1�c��Os�u����%�vQ�rD@�>|�� ��H7�8������f�����
�7�l��7K��+^�77iQ�\" ��6^
���O��O[���~mB.e���~�o�nk�����	j%�a�����$����]{��2����83&xM�v�{|t��%+�����}sJ���a=�_�l_9��y�IX���-�	�gW��-)FVL�QJk�]��-��Rg���q���v���"#�W��L�i�]/h����y'�-<��cF��X��E(�
�F&�ON;����[H�BP�#\�-A�5���������g4��(�|�ng0����-�T��g��>�":����W�rHm�� )�)��r�����%��;�zR����������1�~eVF�K�l�>-��K^�����E��a6�=9e�)���Nhx����kT��q�S�l��h�=���R����/�;���uw���.�Q��{f��1�t�lk�}�q���%���%�9{1{�����}�@��Z����q��=���C���/c����b,S�+r\�&��.���d6�g�ZP���YR�����p�FU����+���"QT�����q�"L��	�^�Z(�x�et�h��L������4��������
"������;�pD�f�0;o��x��*�����v��o���)	;����EERc�(����8NpZ������r'����t O��
_�����{�'u��v����Ebb�[��63l�%u����3����(E���@ z������7l,t!��	��,�{���9��i���&��B@���n��}R����fG�f���t@�TqLa8@#���-|�}e�w���0���������&�����}V9�	��!��<r4	�0U����
���+h2+�V�/���G/�I�$G@(9V@N|���)vF�M)$�}�JB���������;�o�H������
�
sr�������������h���v�K$$����e��8��������:��1E��Rs�Y��LV�Q3��<9���4+8�b�%�=����.�����_`�?�[
L
��������jF^7< �<S��`:t4l�Y&���C3}��u�Fx5�S�`G���g��8����;WX��i��w�����TH,y��d�� $���##��7�:������&��,t�G
b�J�~y��V.���V*D�x���l�c�I��]|���k`|� 
�GSs���R�����+
�8��n�K�LI�������/8���:6/Qd��K1h���|��Kol��K���C���2��,��4C���%9� B^�U.�(yd�T�G�5���F��Hp�C"��<�W�mg �,	����Q���Tg���X�����3T������3��l�p�Y<�iX�o��*.�"����B���}������	��~�E�~�|K��*�)�L�E���0,����	�p�o:��lQ}��z
����������4�M �
���� /&���K������2:/���..���+En��#0R����]���v:����e���$��@���=~KI�\���9K^���,�v y:�����d]�N�;X��x����n>.��h�W�]��,��:��������r�!\G�J�<i6�� �B��q~�v��2�#���p������416�������g\G�}�N#�Dud�UV/��`V�$&>$����v��U�'��|�UeK��	�T��5�S�+
�t�48�2�������� ��m3&�s+��Ar[h�tj���o������W�nm}�hw�i��{9�#������y��8�����u�Z��B�x�2�&�������t��#�_�w
�w�4��Iu���AD�G����6����)�h���9����C�Y���7��!�Z��������pu9��=7��s�XeT8���^�j�����=��K�:�������A|-#��r!i�c����_�����5�S�z�~�*/�xu����(�\��xQI\x4���T�K�iC��a�	rtf�aGW����A�5�'Rg�Q��:������-BV/8�_�������]���x5�?������i�?k�?���"�?��b4��b�`�(����0��uy������A�iQ�������ZwwQ���N�%V��FyP�������1���_[S�V�'�~�%���1���A;aw��5�{����B��SX���
���K���w�������H���Z��/n����]��z��V��(�A�^y<���~l^��
���f��4�@��v��/������������ �f7u5�9F/���SP?��4��H���dZ�02(�������w���I��^>g��f�����a��s����3�X��%����4����GS�����a�}����B9=V:{=��~�u��������N���������!O��������ih��Qr� ��T;���2�sN1�[�v>��L��.�/��B���c���&)�xKr2��p����La�5�]E8�~J7�9�8`���#����0s�;4�~�q��xk0{��=�T����������N<��)ggy3���1mBa��������k���W���C�EJ?����_���h�-������	��Ri8�JH�X�o;��c���Q����%�l\��p��aaa�:
��y�S��$v�O(:���}Z�������'��������,�8�-�c�Gn�d����z��Q#Y�NH
����b��77����RG�H���:S���	��a�����q
�+l�����=e�H�F��=	�1���1�{|k9�KDu����sp�{�9��r��c\��F��� '�%5-2���gY�-T����IYh�\�����N�Pt/5�{����`�~�
���}N�8��TT���!���c�&:����Dn#�/I�]�����x��������q�<9m�1V�������N�/���Pw��|[[���L�0S�
,
%)r6��	�Ky._@�����;�����Y�����������l�Y�������.(]!�/\j\�7�K��wvo��c��{%;T3=�pW\������('�3�)�*z ����Q��c!O[v���:�cH ~a:G�oL��/M���.O�a�E��^t���.�?�nJ�q'CK�~� �!�2��!-��k�A��k�������^\���������H���p���c������_����`(���t����=�GU��6/��+s�X�=�; 
q�V�����C��W6zB�������G'����������)Oh\a�F�y��{6��n�^cJ��1�r�����b^�a���|�$/%��\x����~��%tW��bB��K��9�2:C�����b����1F^��E���M�^�&'�x��Q�[�X�,>%��;��%j�}0^�:%H�p�I��=Na���k�C1Qkm'�Qb�n\��I��g>SH�

�~��R���p�Q
1������<����u�1�(!�-���nq�wtm@��9j7<�Rg���&ve��(NYl_����E�#���Rjo���+*�-���!Yy��@$�.;�S����,���UoAbS�oFD�y�n���Q����l�ke�lv�7�;��>�M�	3c��C����x��k8��q�3�=����$�����%aG�5� P$�O_>�����D�+��-C�I!����g���Q����$\�,�=���V�%� ��8).9x�X���v>�������9k5����<Y����f��.�#;����������w��T7.�����lrMj>�~���y���"0�
5xE:���Mw�t|�N�]��4�YW��g�+�5ec?��� �;��?6�����+z��~~Q����%N^��:��%�J�r=��p�3�nR[5��XQ,J�Y���QV�Cl�`R��z��2�E(x��\{�-�^�-�M�y�I<@f&\��t�j�Q����OW�4�;fwD���'����)��`��:�&���&�hqJ�EDf����0����SBEd�/*���I����7Gj�>�^���bV3��4�r�����]�������W�r��&U����6�1����S:��$dN��[��t��?�X�+������Dbi3����^_U|\&��p��:�M{�P��'V��~�Hu��?s�	���?��
�.�i�@�c���k1!�,zV�n�}-�"�j�A��?&�V]3Q���CO:�����������Bg���#�+L
�����+;��9"��3���p�T��up!��#>e�?Qx�3��{���������PVZ0��'��|e���M�����~������{�::>oa�����?8;8��z(��&^�����Ca9�i�Vw1����I���k�?����h���o�o��d��#�L�uG]�?l��/��e������,����D������p�,�J���2#�����5{5���8I��O��
/y
�F�A�����H�%:��{h1K,]C"Z������s�XT�s+�2��)6��� �������zXa�MS�#��F���������.�/���.Hvd`�
i�G5��#����[Pv���`��>0�Rq ���?�{�O1J�f�op�� `<��N{�I�k�����@��I� 9�l�H?�y�.�o:M���%�~r<")^`��������������m[
*���CZ�(��	[��(iR?����B�D�����
'�S������7�O)��ux��k�
����xRR[m���M,���&����c�o-m����BP���!���`Pov|It�W���F ��=X��`���O44�ld��O�v�-~�HR���xw+��o�	v�x���+V��TK�E&��h�.c���g��x&8�n�
� I:0�K�8���kM���3'x�u���<���}����l��fe%�m�w����9T���o�b@�Nl���R�`�����1��	�����������#�{����V�F���0����`��;�?����5��^������`@a���&��� 6���[�@y�'Bo0*����?a��9��"$�!��[�MI��L�08ry��]KN8��+J�$��$�U�����(����u��MR���������
c�����{$�
��I����������&Ds�:�����H:�6���$N����^rG�*3��kM�Q�\���l��`�T�&E�-���i�bX#�"ob2��
X��x��3�6���R#��c6G$���7~�4��i-UVs`'�d�"�-r�Fb��ys�qO�~��Xe�S��[��X��a�V�&�[=�[��?�ZG��_�����hI�U�H�LneN���L���2�3���Lf����<�lQuk�����B������l<Gz[TJ�+���z��������ho�����^���������{����[s�=/ak9��Q��Y���GNtE��������j������Y��THO
N�a������0�(�\�����*��
J���H��|Q�������T������w2�����jIm����T=�$j����Z�s��@-t���9u)	5�A�;�^��a��t�����N�Y�s'71j7	���`�p�]B���^C�,��������X��8��B��G�<��.W���w�T]E���\����Gm�+{����@Fp"��-��{��y���;��k����V���-��E��_�_pe�Y��dZ��:o��'���J��U��`x1��^,�K��(���:[m=����<�hn��������+hg�?�@m���T�;���2�J)�p�(!�i���/������Lim�o�e��&o@t��@����e��8��Q��[�`aY�\���y�x���=�[�vnJ=�eL�!b��\��79d�r\0*� ��7O�Z��F�e�����������BE�n�y�����[*�HS���s���1�����3�!ml`
o,@g�{��4�����LvX�A������&9e�t{�s0�J�p�p+J+j�ZzQ{��K��\1���o�
�P�`��@/:�)�����!�e�b�P��G!C�?_�%��t./�`�����2N��Cd�u2�X�!�;��
�GMT��<��miU�������rfgxsc5!����1�������f�g�.~-%��>u����'�N�E�j��e(wcb��:J.��H�*U�:DuI����i�?�����g�[0M/�EH��E������S��\W���cq; F!Z��4�S�y9$������Zt���L���-��6��b$����@�K�2�9����R{���6�l���kD/|�j5;O��O;��������&^y�-��#8K-{��k,�j����������K�yx�y��q�����FK�s>��XR��R�@r��QB��(} 8����
e�]/�4 ����r��0o{��������[��4��x���c����k��~�D�W�' ���,��xP��D�������/��[p�[(��������b����.��D�C�M���������j���f3S��)�z@d=��������	��}�bU��Zc0��J�IC/�V�������|d���C�N�F�>
�g��z3e������[W^L�t�A>�����gt@��!�#����0_� -2C�s0���Ga���(��H����S��4�����Hv��2l�g�����A����#4�E�O��q���a�k���V�l�������@R�APz��g~W��j+��k��?e�N:W����g����u�����"���*g��G��#��%�;���M��BBkRT�^>�5�EEJh��-���?`)v��������(_��$�
N�#�\��������1�~��:T&�=rkT�Y��)�0�y���`��
vX[�3YW3-�G��|�������W�u�$�����y�+�������G����i�:8�9���
���g�����E�s����W�<�D�7k�����].�3���/���O���"����o�u~�<�_
���;� X/�%����D�aE>d,ReH'rJ{���*Q�����Fp3pb�|��;�r�#�
~w_�Ld�ex��,�9aV�x��E3�����!����kLt����w�����@���N���rW%������E�#.�({�I#!�*����������a�������_��8�����zrI�����S�nqz�#���?`v<����X��LAr��������/�R��C��_(>��hhU1Axe	��������Y�C��3	g�����E���u#�o�������?����8q���zG�b���|��,wLC?S������������a��������]�r��:/�Q�f�tY�rT�X��Ce(���*	�S(8�L������0�������V���>N�u�'Y���sx��$���C�)�bE8�^>�T�C�;-�V����?:�?:�
� ��?�B�7K�I�sf��`5D��w�j�2
��5��VkI�����^Ir��W5��3��00!��N/3����>S��O��g�k��R��l�Z��z�	<�>>���wQ0�9`���=�)��D����RL��C�g�z�'�R�����3
)�~�����WW�
E����|
�$>S����_U4�U�#wR�o:�p��PfY�)^�J�-Rp�I
+����}/���]�g�H���!�'��7S�S
A������Q�tg��9s��O�����b:�n���������Y���(�X��a��*���O�N����nSl5HF��u��:8>)l���+���`����b|E�b4Z`3�����a�����gq�$���4C�L���%d�����q������*C���������_�Q$�q����L%B�j������N��� O|3m`zn\��������&	�b+��&C�+�@��X���&��.��aXm�_o��	�@cP��b�<�����������D��&���^h.�w����:y�%[mi@���Q��v��m����,������p����i1fVYb���v�
��lI�_0t%��wWrq��X�����c\V�8�2��4�U���&i��o'�[���7���8K?^K�-AW��t�^S�*���A]�*h6S�~<���������n��]P}�����C���2��O�QR��/��"�x��/@S"�����Df����'��EX�.�jf��hm{0�Vv����Ek���z*�����5��5�X�4	s8����
�>Xz���f������1c��'K��;\zT�&:z��0�����
����c�G���+�I��������3%���8*���g�B��'��ak�3��xVU�y��$�M�:b5�!��'7d)slm���N��r��%�0$^��UkCK����d�V��������X�:=�b�v:$���H"�H���J"'K�����b3�L�j��K��W���i�p���f��+�o�������x
�9|�u=�q�R�����m� yC����l�
?���	�A���������� -���6G�����&��%��S����(��m�?i�6�Em�k@+HOL���������3/�����9H��g�c+�����d
�	��~�����
��p��\f5�C�>�������/6�(�)�\���M	KY��a�e/C$�����Eh$B�F�xT�`I;X8��7P�����2���&Fz���]D=JF�2���9�����?%��N���R?v}�F}���a%�����J��oq��8^k���P�WaQ����� ZG+�?�j��e�����G��o��|U�=�zZ������+tF��?��Y���$��`�0���p����l�(&HW	���mW����h9?;��_��b�����l�(�NN����v_���5��mP���5�}�p�����2����G�"!�QT���u����w��k'��]z0�uZ��rq��No�>�BI���B�e=��q�������Y����7�le���x�l�����z���y��������l�n�����|�6d�w��k�q�>���&�9r���(��n���	��n����s��Q0,��M�d,
�����{d�X�"������<;���r@efYf2zc��$k��N^���y����yxv�V�0u�RY),_q\�F�	ef����0�Od�y�</�C3vk��&�n�����l#�\J�uh�tn������4��+89m�`���a���������zy ���t[�r(�)����L�����c�I�9|���m��RiO�4qx�hRn��6,����aN�B�����p����*\�o�J�*7��/����.��I�i`2��A�����
����$?���v@�X���S�������ET�S��������3b��3J��J;7`Rtj�b*�b7z�����Ep����Q7V���"�9G��L���m�	$��yt	
��S��A@��-�$�����r���vN^�?8�C�������s��'���K�L�,��MA�����\�tS<�_t{�
&K�����(���e[mm}���* �$[�����Y��������d��}�L�L��������H6�[��G�'������m|��3(:�W�gJ���1�O��)���k�
�/��it;�"��v=y����������r����0y��{X�e8"g���p:2�����{V4g���?�O\�*5��^4���?N@����K�}�9���Og>��_�y�N;����>���b�x��@�!z����v����3c�+x72��-xq�[{�d����x��������G�to���9C�!��^�P ���.3�9���M�����w�?$�))�����"v�}J�����j+#�5�����U�"	���v�y���Av��Lz�g�"�c4�����MuA��������F�
���2��8}��a,��c��5��6m�g��Yvy4L���q������c�~�i�b���Ml��5t�#���#�-�����_YaUq��n5�0��9�8��<�[,Ic�Fgf�Y9K��G�-I�}<���4���
��E�J�v�ovh�D�\���Z��0^�M�Ef�@��0>@�jx�����4�������Wi��1�`����"z�*�P�>���8��"q�n��-/��
uY�)H�Plb�����z8+F"�JC�*+�q�f��.S7��%���O"��+��n�}O �n�E_7��Hj��5����p��j�_/��L�_��*�y~�od�u5P*p����]D��f�/{�C3�S������vx��g��0-/�*KNCp��������
g��Ke��v�l$Zu��|��C��[�����s��on���L��ut�����oJ����aw�a"�{��Kl�l/b���q�eLz��e�.������TZ��@�q���
���������Gz)'���RS�����C+����3z$��M�f
.n��'O
���S�W����f�H;�C�����n�e9?<(S�bA��0��U�t��rw\�C����D?�h�`S�C�$�(�����c���>�z���*W^�/�L`6�13��%����}��N�l��(4������w{�q����9|�R��Gc��c
�Z���3�N������AhA�����:Oo�	�gr�,	3l�o��4�����n�#��+0%�������"�&(����v6�	S9���
���|bR�+�Om�9��;*g���\	�)A�AfUw�����z}|�:8:F����LVd�i���Q
���� ��qq���]p����1��4������������R����m���YZ������>X�$�%>
pux�S����.������K�fhb�?~��EK�[F)��Y���s*t��(���TZ���
��F�z��	LW�����i�Kn��q~CS�[
�gi�RF%G�I���L��U�f[�kJH�B�)�}\v���?��-,�U#[Z����������
��_����#���A{�����2��:�_��m�����#�����0����{LY�R�t���/r<P�F�`�_oa$n��Fs67���y�?8���>�H3���2E������U���)���������]�>�L�B[������&dH��p;
���j��k���h=��Y��E4}8����x����j0Uk���c�^^�]����r����L-��@j�t�q �����j�7;h�E	��nF�8v}d-3]1�7�6B{n���^"���.��zI����q�����?8[������	6�����+Q�5�-0u�U�b*�H������������Q�4��I�R3���AF&?��<��n�Kx���V�i���-B�����H�.�'�@�fr4�u���8$��5���pm�!��/�jp8AHKz,�V�c��b:�H�B�����Kg?�*���+jj��-�}��H��{X����t^���d�9T��tz�;�NP��23pP�t�8lFC��5�_�~Y]����>���t:�����\��=wU��������s�u�g��BXW�����u__�m�P3�eXL�	��_ae��O���1?��9�}[����EL:���~��������&�)s�_Db�B��]v!��H�0�a�B�$Q��QUG�O�MA�$������~%Y������R��!t�H�-�e�<����|�����,/������������w5�8� �p,(A�U��]J�e���Z�� �j,�C��}U)�9��R�8�de����5�S'��������p�L4�������<9<>?a��,�nq�q�g�����"��	��t}N���%L���M?R�+���������Q6n�c��o[Y�(s��:�@�-�Q��Kk�?S���vb�������h:F����x����,`�zH8�;{rr|z��x����9P^�2=����h8Q0T6�*�de�o������FS$������Z����od_}}�7��59�
9�����%��2��2Q;�Lu��=����Ik��`���a!aX�5�����g%�9Z6$����n&-���p-�W�
z�[&����!m��@�_sj�6(��������SCb�NBZqh&��=��s��T�@x��x?��k�U,R�h�2M~�5,�"�/(@{h�X�Rq)��������r�m�K��b����<�~8�;p 8	�q��y�J����)��V;�"
���6�i�tf���EHfa3�CKd��qg:��}�,��*��C�X���YH������i�?����j�j����<�,�r�+K��}�m���O��+�W#��B���Sl����J��m���Y2���LG�	�]S��Hf���Y����� nhz���5C���K��IH����o,i����>n?�S�]�='���wO��n�v�w�w'4�������:�
_=�n�x��Y�@�&�R9.��)l"�>aY�d��?��-c�r���b`W���}�>�q>� 2'���	�qs������$bq+��O]e}A����p
`-����(�E��{S�	��s�d�[��~�����e��[2�)d���y�i�P��l�&��W�u����f��F���u��JyC��Yd�:/�>Z�c�a*�t�Vd�1R'��l��S�q^R�*6�x�sw�������M��#[%m�d��� �c���N��2'����6��r�Va&E�a�&��hI�8T�Bh����DV}3��h��t����`h-�D�::�/E�(Nz����)��Bp5�Y��)%H[����Kf�H�Y�GSB�i�b�kCN�L	I�}0�k��QF.�b�tM��7L��_�G�,���N9�SZB���@�g�����{�<�e�\c��"V�����B�AG�a��o��k��S6��q����;��`��L�Zo2�!)�������J��}0��0T�HbP{�T�������+�N,,F9 ���[~�H�uKq�
�=�y���aO�Fj�`���v���h�^�h!3"����5u��k������F��]���� )��=]M�|�L��!�"�
s�qE����
�>=�fobL?����GY��x��MO%��y�	h�BY����:�p����W��;�C�����?0]cV/��:��Y3���tX�fq��.�`(���
�t<����R'�<(���U2P'��\1gYdH�k�8���M�dyc��*$m~I�>�m�����xy��qbM
b������{z�Vz��/6������KN�A����ZK6���F��I���e���k���O�� �`��A~B���H�3��!�������C`���bzY���}�Ci�U�6#h}��r�bx=(�
?��cj&+���hP�+�}������
b�����)�F�&?�v���u������..�n����TP���B����|88:�?=�|��s������O���T�4�O����*��Q'�=��o�>SH�k����N���1�� �Y��l6;Y7��&?����S�7C��^���u���=����d���x8��(��M��j<��N+Ah8�e���
VK�Q@;��oF�����V4���4J�7�'LJa��:F�6t�|���z���k������7�w�s���j�o�[�@C��b�����E>�{�����^�`o����O�w���b��"��d��X�
2\a��	�=��'"���N��DhN��4v�l��CH�W��}T�qoJh��A!R�.M�������?;E��l������SO^���0�����s)����,L�'�@���y�����	L�>��f�=�d�y����mG��m��U����������������&������_��T�Y�O��6X��jD�_�$-:;4o�a%q���:Kq��-������EM^Q���i��[�@�n�4v�4������7%m��������0���O�	�<+���p�����������_[�����?�{�4,"'F
���gg��!���#�H|m)Q�-b#M}"N7N_#�1?)�l'�w��j�=#"��m"��Y�/P���ea�}������@����������I�
_G��6����1s:��8;g>IPl� 1��'?����1�������
z?���O�$w��2���%)MZ��m�p��
�%�AF��T��p<F���g�
�n���0y�<�z����,�K�G3������$52��r��v��Y[
����`��`�������b�����������������5[Bk�4����S/'������k>va��y�����Q�5�@��A�w������]��N�k����.�\$V�X������0��y��-�����N��M�a18���Wj�]/���0,��NDp���"��0����-�l�4��9S��5]��L�O?CC��"���K_�
��^<I�#D`�b�����"��#����$��<0��u>���j�f�/���M��b6���.K-�Q��!f��WH��/���;��gI!ygQrG6T��w���n�N��h*���
ch�@j���v�o,Nv��#���$��!��.��$���p���b��%���W������D>6w�����7������;{]U��,}�Nw�Z��X���G����A�����*L�c8LRtX����;��������c�~J�)=i������wI�����{�g��2�?��+"���������������>��������;�NG����;���{�Fo�;p!�NIv����?*Sy���$H��������.�Nw��w�sO�#;����c
~0|8�z�xCH7]F�{�s�v��pe-p���W�n�Q��^h2������BO�_�������x��|7U�^�����:��bi���f���o������u��K�Q4E1��cx��~(1��&�5���;^(1���r�8D������z$���jm��������v�)�D	�-�����b�=�u#��e��?k�����s��E����[L�.�����X9X�B�	~�]�+�[S5���c�������[dH�qk�������q�Bm3WA�����9���k����������dC��$*�T�mF8c��P��}�I��-X�2��������*��������i�7o�V��b��ED��9c:3f \t�T����-�0k�#����TfE��=��s�G����[�����gl2�7�=*����+�&�����
WC��-���A�%��<�Y��������V%K���$�Xn��66(�Oi���� v"-�uC���R�'U����#�V��a�>�z��M�������"�CT�K^w+��]B����7�3�;Z���C:o�ZU<F��� 8��},��A���=�@�8��{��H���"�@<��Bm��|{�)�H���>�����@���[��Oj��"�p/���d�]D��	��XB^�>*5�.#����itE����e�P;9�����1
���\����gF��b
l�����gpNa������C��)������	m8^<�<��w�����O��>l�?���h����cy]_�2}�OG��Yg}�e����fLY�����W�b���q�n�2��^�Sa��������.��u��mYhPwa��C���eW���$����B�#�^�6-Y��[I-6a7x�.3������ac�������=��E��r�ps���9����'�&h}@a:��|�~�������W#�\����1�u�h���0�Q8��c�U�H��:j�H������y-����������F��V]���zw�B,w��SZ���������k��u{�_4=y��q�Z���F�D������,�C��YL�2=)�Z\r2js�p��U��SX�T�����)������Oy������v����~�����M��W�n�� �i�u+D,w�
:y7������~�f�~_��jE�?��"��wU�*�������2�������b�D�m_��0c���_3�r�E+�;��%�����(�
j�%���o]��35p5�
M������+]������H	M�
Z����5�/��������������I_�>����
�jF���^�9G+�\YtGT�=B�hM��8�eML3���EA��#���Z$
j����@���zFq��A�f�����A�3j�5��H�{�;���T���
����)��l���=S�.[U#"���H"��zr�~���E�Z7�<1Zl[\`l�V�aYls]���IC����h�.�� ��n:e�VV|c�����i%l�I9g�Ag!L��1
u0E�v���u<��Z�48��3tq	�����#1�!b�a��u�]�s����b>��z��o��pp(o�uJ��*�x	|�6�z����
-�l��%<���&�"��2}�i:n_�����L��U.#�A����8�d �M�L<�������A�6�A\<����}"��Y����:qc
����_dL�-N�F
��h�6�\��Hlq�b9��
J�B�������9>�6#�`�B39"�gKZ��H??�����9�������AtD|����8p�
c+�Q��o����0W��l���?>��V-yNE���J��*+��z��X�r����|����*M��fS�Biv����L�!^I�6Y����X�!�Q������\x�Z\��2���xsW�@��,�0B� Gu4��e�KW:����������L���b��F������q�83!�2��"���)8K���$����h�����ry�L_.(>Q��`e"��p��V#����/�Z��Fq�Mx�y���t���"�+}����Og|��I����`Y�~!h�cR��vox��N�i0� .����KT��r��R��K4��������/p�
U��?��F���[Y�MT]�����V�e*I*�Y�U�\bi� FX|�!������c�����4'N����>�C�/�WyoA�H�5�#\��=�����g�*@�{�����?�KO���������~��Hu�[���j1 F3�N��AZ-��eT�Ui�!S
��Dyx�	>C
���&~��.��3\�Bu���Ph�^�ZLg�&e�V�i&?�1`�L�O������8�K�*��J������NV4��`Z��*�$��P���-K��rG�SZ����b�P��J�4��h����o!_E%���|B,�&�
E���DX���N��;�S���/�Q��k����PXIW�
�5cRd	|:�%���k1�,���sp�����6�V���3�g@������	-F]��H�Xq�,N3�e�]X��v$����w���18��%Q[�SUE��{jg8
B<av)d��I'�b6"���]��Krry`	����]eWwPc�������3�v)0��V>�:���R��tu�A�S!�a�i����i;���|+8����e[���pv����'�z���bV�����-2������9��@�����^ZV�L���?Q��P�-4��H	j���:�b��������!�B[	��F����+T���pL��>��i?s^&\�������1ys_���������8/0PR"W�(�x&��{�&i#��`cb:����H�h�z�����.Ce{X�R��9���3�
�
��O�<����7�9�^�{���*��?�cBo���#��)�zQn���-T����5U��k��0:O@�L[�'S��&|��i�h��!@.��������
<�
��G��DfQ�[9Y�����:�n�kO{�2��C����v������\c���e�$�����*C��"�=���ek�s���gx-���+dBY9�0����{��g� l]L����%�Vj������d>������4o���*���M�	b��C8$w�^"]�x�`xF%&�G�B6�T�L��%q�-�����?"je!��s+-�g6z�F y0�w{�'lN�:u,y�+�2s<{K�=M.�,���O�TC)_�uy��}��N�������7�S�>��2��?�V��!����^$���mk���<����g~+�k�	��@<(�!}��# Y:"Bt4�e�cs<�	i�D�I6B���u�=�k
���X�-��������]�`>)��9���#�#N�1���D���|>�A� 6|�J���U���1����L��<[f<��&fT��;�J27�V3�6p����������#�HH�1��`��C�)�i�^��M�#����2�U�R�<��eL5��6 �h�+��}�p�/I�K��"�	�K)]��B����J�oN��|�d�3~^�J�^!�.�t"�	%t6�|e����1�����XxA$���,����=������cf�
?���d�C�n��G@���������[X� �{�V����{P�_�b�	����+��I�DHo�����?��������N�������E�6	B�-\k���-���P
�C��
���"5m��dmt��!\�7��M���f���[�|�������p�H����q�jg���\
�����t��48������U�@6��������������jxY�p[Z�]��0-��&,_t��7�D��}S����7��FB�����N��������b��Q3LQ���\+��c�JFo2��R�+R���k�t�������F�hZ���^R�
�+n���jq���pK~f�S���t=NK���/���(�uKR6��(.��ok�H�u�T7��#���,:��*���Kw�,��$����vI~�R�l:����4�h&B+����Hd���/`Q������e*f����`����n"��,����[(��~lh�}�����b`���2� ����F��.r�[�>`��	��m�w[N���A�G��~qr��sl�(�N��������/��8o���1C*��jD��
������i�l�-�a6�]}W���B����w��p��d�c��g�.�%^y�7��EX�AzRg�L2�(oLn����	��&���0��7^�:eb*�kJg8B�zi��H������Z�G�S����EBH��
]����M�I�$vZ�.C���W��<��2�o���L�C.������Q�H�8o��V�eO�2G~��N<uP:��6�D�Y]pO�h��4T�������c��i����u��U��&&�$i2���TG�
��������noXd!��B���x�7-.F����Ps1��Y����gKV�13��+V�0W	���L�i�,X�nJ�$N����	U��:���@>�?�JXzJ)+��K$|z����<CK�}�L>�a�l�A���j��6H7����%v��6����7�*|������LO=KH��f�������=q���Da:�J]<���#\;���D�f:.2v#L`%I��ow�t�`W>	�Z*C����'	��X���Xo�p���0�w�8%ZX���~��s�
����)|��+�F���u(���U��U�L+��������	��A��_X��(JX��e�$B���ji�w��*��`	+����b �a�r�B���(�Mc�N�T��t���9�;��^����V�Q��!.S���	�s����kmt5}�Bn�vf6�Mf����x����g�ur���<����(�R�R���Z�3�e�w�t���J��AD�4�Kc.�L7i���FE=)�PQC���	��k?(�� �P��HE�������)��j��/]����r��,dD��b�*��~��H�1��_MB�.��`�e�b�a��\��qqlX[��qp�J�LI�����|(2��`m���p�"��k�\]x^�'�_1��\<��
�.p5�NqM���"��h��d�E��H��x~ ��� ���^�r�\`�/p�iO������rk)�>,M5�C��3�oJ1:t+��ES��aK�N�1��4�x�d
�@�'5<�9�;���{��	��no�������ZN�w�fQ6s�ml�y��9�Z�4Ko���^�6�by��ZHO�6&9��c�W:��ay��5gx�ClE��+/G�$^��D���u.�/�"��/�q���x:�P��V,�t��k7MF��i2w�J�4<)��/����M��|s�Mc�g�������9&���=�Tg�#�F��3�7����7���Rt����������%�LpX�~��9���d�)�,�@b-RJ����Ht�t��=X��'p��\c�Zy����!����(�;,�9�m`3�|u�a�H�3��@�(
�N��6�>Z5g�sp>�]�������8��Y�5Y&~2�.;��or�?M��4&gv�H����j�si���q�d����j)�B)�D�YoH��K_�gI�1JV�����9O��o�������t}ukrO�1�
����)������U���Eb��3���������X�b
(D���HO�+���n)n!������ �'������i���2�9�]����b�$}�R������)I�a�r���U����~��x}VP.��qL1��Zgi�����r6g��\������.�����x���M2WU[����s��4b���TF��4�hK�.D;v$���Z����od�2��~�e	����<([��?��|pk��Fj��\dUy�s�a�J��'�F]�����X���?�
4,.��;��4j2�Z��j4��X��5��3��ED��|�����w��?Z �y-X����H�������YE���%����R>h{^NoB6�rzy,o6H����Z���O�����_�+)����]LpI:�u4�nKZ��e/��t`bf�����E�l
��q.;B\�z�{f���@�u�x�#��e!~�u��b����4e��
k������}����������P.������g�]�dW�|:�<C�Pp�h|fJ�QQsG"V�R�x��M����5�
�M�	�^3��U!�
�h�-��2����
M�D�V�����*K?�^g��q����Jo���nv6 �?f]��n�
Z|B�PH����P�/0�������������'�����g���!����3�;$�T"0��9�{��P)�zIZUK-��Y�����>�x�=��d��k
�;��arv���6�1�}�+�]��$O#+���v>	�'�[�A��f�c��W�o57��Uv�+�lz�4���}�&�����JC����7������m�����so�Dd"jL��h�53%�p3���iI�l�B`l9��8o��X�)���KH?_:_�11<t��FLj��,*��=�	���Z��K+c��5���9�����= ���CN����+_���A�_����%�/z`���`�p���@S�nO���R �f1j�2�V"
��GNh�5�	\,����"����CE"�����3����M��h�|�1�M��`��H�H������u�T�| P"++@ 6��@M��@����p�3���>1�����4�w;G����?�W��V�����T�?d��2~y+�qd��Qf+�&��h���|�	p@����d�f�\f3������&U�o��
-mP�%o�/����6�\�v�����g�y���o��|�7�0���#�������kn�[�Dkk��Al���x�/�]���'����aoj��"v ��c�^����3���o=|�9�M�,1�F�g��V>S]:s�8��,��O�7��2�p�8���z2��p��N�O��zW��[4��/>�D+y�Q����E�����
[��.]h����p��O��(��
�/����y�����X�o��<�@�����_VL\�Z�{Wy6(�8�.
L�:n��b��3n�X��`��Vk�0��-z���u��p��+jmg8������B�Ep��l����*!
P�	����&����@qU�R��tl�������e���?t��g���
o�,5��I*���@k��4+���wh?�aU�R`�c��W����i�cR���������v.Hm3���
�'��T,�7�H{��^�btG�������=U��s�W�YC~l����/�{5��$R�����~�	�.�A{Li8��[�e��H�*qk��}�i'�,��M��N��^V�$�J.O�~�@��eA?�4�j�����b7�o��:#Y��.m�%K����}�&^v���\�����A��$��u��g,2�5���3�!*�P��X�6�L�|����ATD`4�����R��K.a����R�1M�\�{�7liE6���W�^�������� p�p������y��\���4��m����I�)�8�������$� ^�)�R3������*=�[�|J78�j{R��:��&�=C&��A��������D�'���a7���f���z�
���V"�
�;��7�����������x���d���TN�������v@�H.xg�ZZ�P��di��E�\���}6�����M*��NY�
g��K�:Y

��Bvz6z������
n6�6�t�u����C���}����=OIKh���!r�U ��frD���pRg��������$�����(�c�Gu��%_��]��N2����{7����\�iD�����
�;��yz�����T6������X��9qKN��};^A{HU&N��M�\45/�c���U���-�M�;�-���^
�����'����_��)\�1'�]{gP���r5�cLB�� �3�D��;�%�F��?�?g(6jH�)I��Lz���]C�I���{�{�.��Hb����!TV��b���C��tEQ�"<��3�
,$NB�����d��3 ��I�)���l���i&4���F�&B�B�r�c�����!
����(S�����vr��2 �iO��0V$L/`�X�a����qH�F�f��\��	B��� *�%n�� �'�#�|��`��tT�X�?,�4�l��f2�����	��v�R%5�Fn�<��V��5���CJ>���oFf�KpA.`f'�G�0����+�k
�$�������~.X���3��I@�N.��,���
&x72����m
���8�R�F����o�x�x��#|�I����	�qv@W�,k�!�X�e�91�(�<>���f��[>�& �G�J%?�2y4�Pe���_�c�p��s�}�#��h[���n��"��s2g�d���iK4����-��C�"�8yW�V�#���m[�����}���!��4���L���	��'6/?
iQ����@��3�*����l���[������SW��9�%�f��IQ������T�_p�0/Jl�0�5�X�5������<>�5e��S��0�v�/.�������kO����P��F�������3��%A0"������a����D�\|4kB_v��00����J��6c�*�����&��C������H�RyP��l���z�9]pUo��2��LNT����&�������T��IE4>1�.��37?9?j`\2?_���-�'�%y�c�	x�hW+�C8��!��
�^�[��p����Oa�H��b��+^�j���.7W4��D�C�G���D����k���N�2W,W�x')��+�K��z�Yx�h�*'���
���j��#E��a�u�w���
��zt��-z��m��R�=\A6B���y+����/�����lm�:��%���<���?
��+&O[�X���h�o�:�q���N'�.����������)i#^_�D�S=6��o�#'��q6J����E�D�4�eX=.�i{�S�^^|���V������oA$��r���oU�o;'�D]������us�>���c,z�9r*��N���ib'��!Y0�k�.�?+�_�K��ZZ�Nr��S��GJ8�8���sG>���*��j�q��]�=|��y��e������9`S��1�w��
���6�yr�[�Nx���N/h���y�]����U���k���hH5	� ,����L���$��h}�I�g��W6l�
��a�H$��|b1p��j�~�L����.��p��=|��"n�?�@M;/��:��Fi���-q@�����������0�%��,�+&DP�����w	fi�`7��I�Eh\y�gD��!�k��h����zy?J�W��rL�n�h������n.8p%��Ai��6�nI4:�2����i�GnW0!����PV�!N�1�e��aA������I���	{�q)4�&���=\��8o���%&����R:T��;������N3��}r�����0|s=*�p���}�S��|�����������ND����J��b��@1��*hF@�@=Z�hn��H�#���)��)�4�"HJo&^���;�����*�z{V��\q�7��u q���wX���X�Z����=�y���pI��?
�D9B�
t"@up�5�A���]���R�R��.jRr*��/��_�� �#WQ���1��"�@sN�V����(���������Bjd�ps0u9%9��}�?�
�)��6�9�p"�m��P���c������(����V�i4��n�[<_@5������_�F�10".f�T91�����V �gV������-�������&9�]KO=1\���������������y����VUP{v*rT����zX��m������i����y4�`w�O������D�/��HL7�Q0dC�)���@���>H't����|�9��J>������RuU��t���b!�#���9���c���7�&�6����������W���������?wNN�����'oZ��vN���v�:8}������z@i?j�x��0�����s|���#�)$A��\��n1M�9�(C�N(��Y����^g��;���E�SL��bh���]\����I	j�+1�Y�k��4q�"�s�!��|o�la����1���d�^�!�=%��+��������b���9�r���w%����u�4��L��H~��P%�p�p�����K�j-%t���,d�:Kx�lrj��v���E-9J�t?������VK�H~�$�T���O���r�-�\��R���
��-��n)J�-�����,�3��nT�XWQc��R]rX�&�6�L�\S#�<|S�Um�J/��r%����������j�����r���%��$
$N�D����b"��-���/�,���hz8L��=WB�"�>'���J��9S�+�x&���^���\��Z�b�(C�n�=@�%Vh0����+�0�s����&�^j�=*;��TH�!4�)I�@pT��V�&��?#g����F���O��9����!���������B����d�$���}�J������S[(�"6�v#��7�_�u������
3-2hj�
���'����������y�1�q7����iO�J2o0�C���}�q�;1��C��y��E����/9�7��-9���)�3c�[��X6Y������%l���h����lUt�=o�(.h��Os�i�%bW=��8}z�D���F��`�+��!�P)U�Vpj���&I�������TQn�5�����gB�ic�{/�5�2��������������hA;/�J��*g��
���#�6�-���>��)
+h-e�o����@'����t���Z��V=��l�Sd��/k*���O�A�������A/��Y�3f�J�&�F���q�V�`�d��
6�j�6����m���7ae���h��)�_�BI&x�0S��sK9�(���c�*�9��Z&�����1�@�����
w���)	�|�;o%���*K:�pf��W�0�h�QU(��w���*�����R���r�#�phI)�pq��,D��.E4�N�t�����=���j�Bh�g@F81�3��qZ\a����-zG�v�u��
p��(��aI�C�W��1V�����tD�E�;8����ckZ��w���B��y*2�y�fV��<�W���H��^>����qY�~�p���N'�hzR�f�,�.�`M��������N���ms�N&��8��6����� m���uQ0����dw��f|�������� ��c6?����6�9�#�Q9�����,	������b ��e�������H���� �!�
��g����{/��������]�����p��C���o����z�QPm��X@�F����H�h����f;��������n/`�0�Nw)z����u�Mm/B���sz$Qe�Q���6>,}�p�0��1���#�P.(V�Kp:�7������+�^����]d�����W����N���l��s�s~���(W������1�<5~w��
�$�L����Z}�&�$����X����������|-|POj��6�&��|���|���4;;������H�8Q��W������yGz�t������N��F���oR<��4Z��0=��ON���������j����id4I��<��bS�y�B9�BM�`�=�0i�`��0��7��<�{���U�]W9O�k{{�����g�M?���K��h��>�Jij�����Wx���2�����U���$��F��xX��,S����U���}[�y
mtF�c�[�>�f�
�]u���}���0S|H����[��y��.�S�b��K�
S�=��N�_��`�����Q�iiE�p��B��^V�W���S@Tu�y��`��B�6C�{��?l(Q��Z
�����a��V�Q�$�O�?��o�:�.�x�M��V$6���Q�
�Z.X����'�m���d����/���~~��7�@�Y��n��8*L�w]+}bk��/���l���N"�W.����D�-�|<��[L�&?�~��w�g��_��N���t%�Xb����I�AM?bE:9��T��Q�-�],b������`w[A�|��/����h���'��b����i�_c��td��Q6*��+���},N�O's�o3p5��������;��c�}3����L��KwG{ ��T��g8$�L<y��5��������������]��1��3����,�^�o�}�RR�w[���H�/8���uT%����j���vDj�Ck����z(�U/���t�zx��
W29�0b��#>���V�����#��=���Z���1�=�
�J��[)<6�oe70^��`>%{��'�����}���yg���wu�����T:�*�8�N�!��P�$#����K	k�{�D�oA�Cf��qx'���
D���p��/bd�BPQ����	�L��FX��f�T2�;F��%����/��
��
����n��~U!��������}F>�s��m��M@��>
�
H���+���y<������������������x��p���#���,}���v�2q��M��n���3+F"��1\�j�e��?�1�v1�[~�,��b�m�r�Nfy�����59e	�'��y��K�����a����H�z��]��|1E#����%������o��hW;P���t��i�.���UG�WD^���5�{���M������uzS*�,Hb�4������8=��v�!����|��Q������8�_��]�U��W��n�z[�;��$H��@�����A�p��:Z�3��\�6�)0pz
��3�����u�5�������|}�N�V�+6,:=�.�t*��\��Q�2-5nn��d���_���[�zIPtH��
PC���������Y���\�ELw2�ztnKX���3��R�9��pt�bFL�aC�~n�Q����a��C98~l�98�3��m������=
V�9p�8���Z�#���fU[(��"f��?�*�vV���(�/��_����{��������w�D�1h%o��aKe$\�r�xJB�J��M��D�[��o�����_��8��Lm�0V%�����QG{��1r�D�j�)L�
������m��
{V
;Q+����W�dD��?����E�z��")�����fA^�����B�P���8+0���b��8Z�B�z>�g��TOt�z��H�)�a�P8-�6��6���vm����Fo�?7���Wx���R�aI�3�G�y�>UE��cE�p�lIf�8W�Y�
����
A������/����R��^{�C%�D���*��v��QJt$����h:IF�)�iMQVL,l)� ���l������MU���Y{e��f
J��&�����	�����<�$��s�84�q{����L���&N���q�~s�T�����W
W�;��K����c8�3�]�&��tL��P
��t
������hG�d_���'�n��/��)y+#���Cf%ki�L�F&`������9���#gD�?���W��v���YN�Gv�-(V��ja3.���X��uv�s��,�����d������R��FrN)a�i��T���W1��H�A9"���b���iMW�e/>i�<��E�>#�J3����J�n���K���L�,	gX��,��4�D��P,��E+�������Xl���Iv�����9/����Ozm2�
�X8�$_�������'�u�Z������
�}���qJ�)<��+��uz[H�`+�$�u]�M/I��\��
J�8?s�z�#�8�j��h�]pE)%��|��hS�
E�]�Q@�g4�$	��-������:�5*�#�_X��/���4g���R�8����~�"(4*&���t�A��� �&WZ
�r�k�JN*��Lr������`Ka��]:_U���^h��$\�k� ��V/������@����!5�D���XhY+�K�C�������u��?����l����tt�]�V;�&�)��O]�%v�TcG�LA�p�B����X��V�C�(&g����:18Q��
���s"K���');-`!i�uj@;n�m)��.�J�d�k=�=F���J�����F���M��2��=`Z�����������������k�|����+R��x�5pFe��
�4��9��mh�
���z?��CB���O�-�PY(���]��p��� VPz���$��0�����g����8�N{�{���1�����yW�?UsR�����5^m�"�e���9�p��3���l��� ��da��C��*N���hksP�����#o�7����5��2X��:��[�H&5M�_��8�J\�v�Z��O:z��U_���)U�s�d�
X]/����
�F&�g61�rQ��%�r(I���
�|��!q��N ]��X�o�s���R{��,)@�-� P�>%-���H��! �u�5\a�������l6�l��6o�E�)A��+~��z��\,�zK�QoI����wO��b���d�������/.=_�lv������>|�p�o��$[�������gO�'��_]�bV�|S~��P"���AO�
� {*��nO)�BF�B�IfB7��5��=)�2��}t_����~H��|i������*+�Q�����|���g2�3^�Xa�x����P�������a�@<Hb�C8�#aS&*e���*��m(�O�$�8E�@�]��b�#('�p�����wwm��k	:�l5����7����Q&	�d6����'��x�R�t[S.��<��S������y�����&��<Q��u�_A�.���	�����*WSo*d�f�����Sb���~�t���zE���A���In#s�����k�m�+��_�Y�
�^W���q	Qy
$�6�
}EIKn
6��iF��'0�����b������wN�HG �x��j���!	��#,������01(&z��L:������"���J�.���U=��"�d�S
�V����9��z�N�t�.����&��l�#���W�����
���xM��v��������u�.`&��BE���9�\p�NV(� a0��T ���"��~��	ap�"���R� ��@(���n�\����
;3��J��{�����j K=U�1�09N2�bx���zRUU�-b!�j��9����������
BiK.��5���TGw����~qu�v���#�
�,�R����%�rL���}R��C��$�
���"_L!4y��|U������F���;|o��5+�~)j��Q����0�&K���^:��<���8v�&Xh(����_���y��{��~���{��{����wT�=3��:)����~���K�M���Z�S8��W���#�EI��&�����;-����Sl�������0R%��%u�xQ%�)�v�vNU���cm����4�|�@@����n�ZZ��frF��L.X��Z\��)��{s���F�O�#�s��X)S�%I�-�v�P��>?��6����F�@�}�:��#�l? �#!S������i%;�q���M���T�ri+�z47����(����$L3�u��B�c�Qs����d���Ot$�J=!3�Z�r�_��w�x�t�z/L�%U���%19�����,{����.��'����9C`�2����=����*������~���O!�[r���202q4�]Cs��~�\X'��\��~���3�!�9�s4y
7s�yw:`>a���&��j�z�x���
��'
�:�{����Mox�������������`o�������&��^�E������U�O��%k#�G��Z��b�&: �W/��1H�~��2���n���h�8|a�kL��h�L���ajRj���x,��sD��v[�L������{p�{�&�	 �]�C`����C#�T=�_��g�Lej�3d� +n���R�F'��^^2� :�����v���^y�n���G�x��V</D��)H��G�0�%��~<&&Ja�X������w�0��:���3b��l�[�q���I��g��M8���AM���Ff���J ?�������)�w��dsPE�/_8��"MV���F�#��7d�J`��2�����Z|�������E6�.4j��S�NDw<��h��|NA��`)/�d�Z&u�
rt�iW|+��b:x���[��!P���R�p�7��M�%FYH��}�F����i�w�W�*&7(�	��I�"��l����9��pK[W �s�@��$������[��BG��d-������9�3�=zp��T/_��)��� ����Dk�!��2`�8��F
��6k��Op%�]VU��Io���w�	�f��D�]��bJ�E���R�U��hHo����4o�Qs����c��\8�iX!�(�������J�,�����L^l���@���������9�&��&1'�x�SPF��fN���D��`������o���Sz#��H^���F��o%�� j��UW������6Tk��BU�8����G#���~���Y�	3^	���|56�ux��2��LB�!U[�%L
D�Q8l����y�'�|�dlP/�1�E�Y�3l��R��#������	����f6D���\��;/��,}���H�L��%^��?$;��elV����������3�������U������j�Yr�@uC���Q����H�\74-Q�,������ �%0�0��Z����LN>l��
�'Z���@}���u��:���)=x���nkf#�X\2/�'h
��/�t@}����c%2u��	.��p����#�vS�*'�dQC��b�8�C�V9��<!l]�>��7��ql�cpa�q6g���5�I����f��h81�f��KO��0��P#�����hm���P��hG����~��X��1�W��^��	_1��~�m'��Lq�<�f?�8'u��x�����%�)�@���e� �����|8-�\F�\�'���@���M��S	J<�����x?�Qg������3�������������F����O�":Z-e�9�����:�6���:\
+���I
��x����y��4���Aq��D?=�S�iT}���	R�����������M�#���T���~�GX����mt6��u���1�%����}�u��,���N7;��w�����O2fU�<b�WY������jwtn t��*)�5�7K�d�@e����[/���G�`�/�������i�����)�7+�q�]}���������f��D:�EV��Lg���`	�~��0�������K6��&s�M��]�I:��~]T��p�t4.~������.������yx�zwp�I73���m�n����-�������K��f��~���|#�H������=Gg@
���Y}�T(�~T�E�����x�3��U^Ig��M�����Iu�N�j��Y���,UC�G��g%_�p�oU�'I"��b��
���z�lr�bp�����
*f��Q6��l�B�3=���d�x.{f�y���S0�NT����\`�!}��5{:B�d���=[z�����x]r�B�Y��0f��5���&�\�u9Ky{0fk��s
��A)e��#���C��9��YDv1�r�@4!��r�<:�I/�-�Zt����K���{��bD��I����b�q�HrRV�
�<Cj�Fk�q�{�I��D�S�� T#���e�E����Cg������T�|��e1Ve��x�#�����-,��L�D��V������0��.kIs/)�6�8g�5HRAx:c�VQ�%�u��M��Y���P��Ci��)���l����������
�t���pJGS�	�y0��P�H;:Nb7��/aX���|4OT��n�p����K�z �e�\��P�0HfJ)\�\"DH&������3F�`a�&�a"�h�0H#7���!���cf�:�.e�� ��\�X'�Y�����cj�z�(��GHi��pM_�Zq9����H(����xl�������.0��������_o8�[�=Wq���9�G������*�v����W0�*�
f���aw�_�"��I:l��*�%�3S��a�.��R��+Z8�����Q���Sb����O��S���s/h�%'6�^�$w�,K��9�w1I��T��R�%�@5|����A�4�Q�TfT:A�x�F�K�bf���T�!0��B���fb�vv��P����~��wZ�9����Yh����(S�����H t�O��\�����'����#!G��B)�2�;�w���zz����V�P��0���T>mwEb&���%)����S�����7z����Upt�������������.>� P���0�����8��J
	�_���}��D7�o�.�3�k{d�c��Kqj��~��b_��]�__�o��Ou�T�0�Co]> BCw{��yv�I��-$u�j���Z+�8)|�.(��:����J���[B�����sd�W�u��u=��:�S�'@
��
j������b:��B�dUu}�*��s�����)�V�8��^
1���o����^�C'�*5�{!)zuI����op�(�:��8n�����z<)n��4?�0|��G�jY%0�wn���^��7��������?�+�)*�����.n���������V�2���R��U��.�2Yq�a[��F�03q�������>Y9�Z6b	���h1O����B�$�h�RC/5�
���H�&�26���f�]�)sbV6��x�����(��.|��p�1����t|����_D313��6�x`��N�u�:M"zC��){&zL����\��<��X�L182�+���c=P'q�$]����B�P�>�?O�Ow��vv5:<Q!�J����2;T���k��^���J�%+�3������,���`��\c|+R�w�8�l*0���T.w�	�4�'����N.��M)���H�*j�����9BP�)G���8[\&M2TSE�`o����E�>�P#b:���f�VY�KG�G�x�
����.��&w�:��Z��~�*��H$I�"[[�����k�����f�Qp�U��B)�?9[$�5�g[�
�L�f47:'=�?���������K4��������n�Ds��u���pp<v� ���2�v�����~pv�L�k����0o�����}�s�o��5A���XlT�Ar�Fw-�� ����,sLC�$x�qai2�����p�DL��:(���u�H>n�����<>�sgo[�aE�c\"w��F��� ���7�#�k!�	�tD2!]a`^�*,ZG���[f��a3���a<b^�1#1��h+@w���&�1$��!�V�������g����p�&(pd��1�G�g2e_n���K�.�������d�U��x�op�Ev-��'��a@��������]T�\BW" �������,r�������T���9lT|I�������f���5�,�!������ouO@����c��iJ����C�8�u������Q�ho�u:���l���Hz��\����5T ��
�"��>|�e�s���D�C����E5���=�������/
#]�3�7���CY1A�c{#�ay�vck�s�H
������3��a�
��ey+MF=D����ZoD�2\F�8�GeJ9,�0�$_�*�zC#�2yx��w���#�O��� &?H�H����Q�� �w]��r��/2�_����u�W	���c�]Xd�C�m3��l�"�y���%�1~�O���<L!���z�1�*g�y�v{pJ�e	����J�*�*7$����R�d[](��K`���u��npp��])?�C�+�>.���A�W.L�Y�"�8T�"&���>S��^D��
3��]
|� $��R����M$ ���e�n���`�<�O�C�1a{Lr�yV��d�~�!x�0���;�gW���|*�yaJQ�L����a������%v�	��$�rW�u��27�bz�M�h���A1F�����~�t��#��|������TpO,��.~���a7
����X��f�-<'}�>ec�m������3g�$��	K``%��gc��%�W�'z����*B�{@��J�`�c�:(�j�	���=&��6J�9����_�{^7����x�0������De����r�0]0N��-l��+K&�6sh�� ��PT����9)�A�H4��2��0&D��r{���_���g���E��
7.G�8BV��D�-���"����
j��0���<
������z�F���=$�#�"'g7����a�oQ!��|��� ���t$+����
����p�,+���s�G%�*~�����I% 7uU�F-i����r�A#��X�����0x~�����fL���N������q]Tz����y��������o���}��s��M�����_������6�S�;�����x��0���
���V\y��N��jUc�cf�N.���2�C|����&�B~�
�w9`�@��6���=�=!�b<D�t<^3�k�����Q"d>3�caGZ���C�����B�����&^��DN?h&�1x5X-E�����&c#�x�����>>��`��W���L�l��-�!IY('�����W^gR�%����~�~���@,�C�s�x�O\z��f�s9�}����5�C���)�h���zHp\,���T��o��������+�V�Xl�W��!\nY����^I���7C|��H����`�0�y���qK������7�|K��X����=������������|w'�I���������ATzc%��yP��&	\�5oE�=�+{B�8��4�W��@B��P��}�K��j�Q�n�V����I���*���A+�Y�V4	|�����I�#���'?r��t���	�r2�)����;�C@��6���SP���Kxu�����>�*SP�B���&��fS��E��k��" �xps��C�k]�JV#�:Fz���
k��A��6�,�I<H%��e�� ��,�&�A���9	�!���>G~���nG��	*6�!{� �4:����
#\�:Magv;�������a���s/��O7��1�����Nlw�iPl�����W��<q��.x�2��7S4F}�q���Z�]�m$�i>�<y\Oj@X���	����J���	��U)����{������r�@K�B$�uf��L�����?W\�I�4V���0f�����[�e�>F������t�^�
���d�l��N�X�M"���5
���G
�&�Q��<��o%kC��@�\�	�(n�<�3�2o�fe����h�&����yZ+��F��BaW���$@P�,W)��Z��fr,�uH���z=yA\qUj)�
V�b{:�SH�oS���\|�V�z��w-!"J%�&�0[���A�0�2��WS ��J�>��\������%D{��<�H�d+�p X��e�g�XZ�m�f�t�v�� �����bAI�b:��&4
7�RRU�M�x�Z�B����m�#
?BJ�m}9������5���rV�r�/�[���8��4B��8

w����s�B������8(a8K�8'���]�Jd�W����k��m5�����|�f|�eV�dv=����D�\�`(r��hr@����^O�� ��N�Az�@tQ4(���Wz���<���P���_�v����W�5H������plV�d��>�}P�^C�Z`����l�&���R2�#���J�4�m��p������I8Y���bF�f�D_������-@/�-��qoc#��1������A���qPMT7�g	�lh/p�`ID�����zQK���5�G9>��A����(eN�"_6������������
���������,���;!F�p������<c��0�n���:I����_�=1�T�v��,B�<������U�0���Bj_����3��5��%��cz�?�+����(@M�-��Pox8��h�EG2��\�F�.<�S:��O.�X"E^��Q�u7vb���a+���Kr�h���s�'�	t��I�l�.��J���.��.@��P���H�]PC���z�
@XO�}
0\H���+�/K��S�q�v1�g&��L����fv.�EN�� ���A���t�)f����X	�b�Tc����iA�^�b����?Jt��
�p�@S��t"+05��A��dJ���*����`�r�(V�AI��:%�$%�8��p��d$F��t~���j� mZ[,#
���a`
�Ri������e|��@�-�����@�J�	�^�0�t��]���Y�K�_���?=L?-��������Axh�����a�5>�H ��ZB���-����\t����EE�N@1�k�=%����K78B^�����t��Jc��s)U�N����R JB��i�#?�FFM��I����6�
����)�GT��G����ow5�_=b�4!�,^�-c�*��e:(����p:<�
��*����������n^`6�:�'(r1K��"J���<�����%`�v������0+��t�TX"e��Q��8��E
��$����#�{j�u�_!�k`��1������(go(�-/`��]dUT�Q�����3���q\%��_iyc��x���o�����HA%P�"��l�P��t���s��=�"+���(Y�s;��"B�>S�� UQ����/
�]gb�W�pv�����E!S����1�9�u�}���*����])pV���k�.~F�`Z
.9b_�_&ZC3���X�����{����i�%�J�M��n2�{P6+�B�5��P�|��g6��J0A��5,*�6���7/���&�/�fF����@��6�LP*�I�l��+���t0q�����17�� �}�Dg0����a��cD��U�Q�<V���!���T�1�rA��5�%�Fj�K8L���jQ{�����Y�,���?:��<�	c��K�TW�9*�A����))9���/����*����$�"�T�b�t���2+Do
��u����QY<�@�"�����m0VV3o�*���"�9`W����c�A�/w1^l�N�l&.�gK���)������5���]�E�pe��2��);j	o�������|����;k.�Cu�S+Eq��>����O$����28�5g)f��;]dxjF�u\'J�^[*T��C�D��=������|���Hx2��+)zly����+���s���rV�J\�dXc"�V��a2TW�����9Y���d��zP�����u������@������{�Z����X��K��/��F���Z'���T{���H1����]�������J7[]J���4�L��e����'� ���O
,�k$�E+M�&��#�I�a�X�s/���G�8����t
;�\MC����{�NSO���E�~�M�i��:.������C��E�&c����N"���5�V�)�
�q_���������Vc���D'��s��lIKFn:�V`��QVp�o)���*�
�h�<{N1&�5&�����S�����i�j�D3mV����"O�^�r_U��l=�vo�\�����!�98�xD&q�����^
�f���A/A�J�u���JN��]���K�<��7�s%#�(	y�"��?��o���Qq�$Y�<#�+�1��3��MQ��,��o��=�_���������(�����$��F�.ui|���O��s�P�XY������n����ury�~?�`>{���0���9������4��47��u�&�� 7P=���T]l����8I�Ab?����H��������~�e:b�;g���{��*'T
��g)8(�2=�C>�zzC���!�s���t�m���$9z��������Dm<�9yX8xHF�T�	k�d����R�9!���M>j�RqF����Y1g��1�0��g��$"#6�c��q��!����n��.n	�}@�}>�a��7�����\i�]gr��JE��i�"����;�9�����W
��%�`�j���
�dE����a���
]0,2�C�������c1c�wII��7\{���X�@)�(vT|�G#�YK��t��"53M>���n"�������t�Lfi_����f��GC_�w���,����-�u�!�h��NH��!Z�&�;c}�Ae�������1�?�������ly���a	1�N�_9�a�N��aZ��`9����LQG�/��X}X�TU�z|�c��/Ge����a/�Q�Z#Oy�d���T�@+W�����������7�Q��3�+��-l�V�-������m�m�W�%W��3O��mT'#k�E�*�Z�Yr����=���X�y���1B�5��GX�J�V"]�J�� 3����E;��F���l��y�\�@��,�64���2��)[�h�5<TU����Q������^\�3��N�00����l���������4 #����0�q2d	��5-�a�	U��F�l��9i+�����J�X2��UC:�����������G
N-�is�o5[���Jgb�z^X���|���wMV�L7�n��
"�TZL�g%���5<;RMw�Ro���U1������YG��d��������-�iPu
�>������	�����F����RWxT�F( ,|B�R�r��EqV a,p�<���������Wu��(��Q��	_zi7"�=�}�s��� s���v�~"�B�%���w�_B|w�u�FB���>��	s�!k����K�#c�J��D���@��]��c������� ��2��] '�MbI�*y8l:G�
D��Jida���N:���.�Q#��t\-��z(�u��?����`s0�-k5��k�n)~��et�
|��isE�M4��`F��Hj�<���G�[p�_E�����.�a�����Iu.���5���+�N�B�pi�c��<��{�\����H�Wg�]�{Y��k�}A���0���(	�b ����H��\���p[>c�#��d��g0q�����C�j>�#�}�w�Kp��y�|t�j�������:�����h�z��CKC��0b:�c��7yw�{��Ch������Q4�����)@� �3��h���{��rr�3�Kf_1��,���+��a�=:(��X�-Ba��D�y>��	`�����s���l�7��F��W�u4�����	_�GY��7>s��ys��l�|�h������X���O����G��=�s�y8L=<�cw����l�
���n�JgHB�� IA[���������b��&!���y'����P�C��8��U���UeF]�w����!���g����WM$�"QX^_m����M�2k�:y��n>1�c�f��l�	x�W%�\bs����/��\Ye�i���P�>t�p�0�S��!g�u;����?�������x��d���������W�C8N���u1%��S�?���bX���^-c�Uz�g�A]E���_$e/�X�/�g����*b�B�.B��Q�)X��~�3�-�CG������������u)��� ��HzX��e$�V�_S�ID@L)D$�I6(OBDo����� ��A�pX�y�1���u\G�d����}�
���=r������`0,$�F����)R�`��!�"��e���Mr��7���h>�4��c��Kz��d���`#��a�

H	��)�]O�8�~�;��I��x'�+��XkPd��I�%�F�z����J���-�j�\o��D���7��E�jP|e�c�eLv5;IU�Khpt����FE|�b���(������ju�9��#\�	�I�D{������s���3~C�P�P�3�����k$�!�����q>�z+I�%�u/�H��Z�����?��4	�R�Xks�	���%����fe�r����IY���*�dx!����b�^`�5��r�RER�'58��M4�������8���L83��G~�Ri�^�a��d������U�vE����JT����d��^���
7�H�5�_�,����"��"S�����	����=a����x����
����5�q7#��0<�8sx���%�� �Q\��l���|C����!+���{�k~3mB��_K��sR��}?�+��l8F#o.�Iz47�hX���HW��%��iq�I���Rh���z�x��5�f�jc�g9����6�e6��Y�Jr�������fn�8����������so&�j�_}��~oG�'�>�V���{�T+�12����M�$��U�J�-V��r,�{�%�����<��j�/����,jI�>8:8o���>y\��j+�A��;�xn���L-7�=�����g����q�6�a%�.E�TL�h@1��]�zV������&���f��0��@0�u��r���#G�����l�jM�����H��Wv���td�+����R����'u����O��]4u������>��G���a��KO���rd�����B��f!����T����n�x	-%�4�."NCS�'f������]}h0��a��;�������������G����i�:8�9����Y�����)<�}��<(����������� 
M��o��E��������!C����I���������
��?�U��B��3�p�-T��VWl���l����OM�V��,(�����e��Tw��#��QBqqg�~	�HADA9����������s�wL���B�ae��YlJL?��` �D�_�_��vvVq`<\pW�~���6N�?u5��[���q?*3��H K��`?9���s��/���Q�z��~zT�%�I�e���V���Pa
�/�v� ��c����V|,�x~���]D��+��5cM=������u�P��0gh��(�~1H�����h�J�
�Ne:7G��
��\���H��\�z�!��?�>,��K6[��L�(����[������vfm�@TN�� �&��c��30ih����b_����Y�m)�Db��d�,\�E6;-E
��������f��)�M�t��t�yu;����?���j%,?]����:����4]0���XY!����[�{%�� rZ����/<u60��J���,l�dJ2��r#1*��K�r��ra�����I@�U��j1��p���������Y��Yb�8!�|���{%mz�@�'v)\��f�_�(��o�>����RU�{��n����� ���B�6!�����A��`������`��-���;@M����r�����+���CB��G)��.��5���
��������k��
U��^�"���{�n:Jd�WjO����iw+��_������4����z_aW�&��_uO����uW��lh���ld��h�x����D�aiT[��f��3�Ki2
sc|	+X�o����y��I1/F���w��09c�!��'J�CuN�[a5��CF���5�7d�SL��l���:|�D��4\��t�'T�&[�%x\
$&AE��m���T~�YK9.��&.Dr,sT��2QsF3�>n|�@@nsB����h��h9�R����C
����FQ�W������i
�=�2�7��R����6���&�5�V&T������J$��
(e�N��Qk�h����#�)��D����.��>��c�*T�`{9��\���O���ZJ������r J�/��a� ^�
�|�����i���~���������\�x�^��������'-WW��
};��K�z�%>���*z1Ds�����C��97�uO�3�_��*L?�>�����.�4V���^~�yh�J������R�1����`��8}�6�SU�(����JJ{V�t>*i,���Ej.
��������W����R�����A ��a�D#�S����r�>��~�_�.5��v�HT76����;��2+�Q�W���J9F����X�L(E� ���3C�Sd8�JJ\���3�jV�C->�Q�z�������9���
^���5���Y�w]����&�;n/�tSt;���s*6��Z����?7��U��I���XX�r81����d���dNe
��]��W�����s~�5�O{��0���:������(w�e����j��(�>�|��Y0 c8�-"c ?�Z���1ta��z�uzr�]�x�vbB0J�qAG%b*~�����H� '�y)jV�A���51Oa�F��hh�aZU�+c>�t��������o����j�<)����8�7��	�����"n�l��#"�^>�����2rX�&[K�{i���b��5��{w���_����=�.��4��	�_���x{|B���q�?�qm������i��)XX�B�j�h��b�9/��h�������Q�T���?�w�:8;�S�' �����
D/�_�qw�d�qv9��c�$�P�J1�6N��
��	�F�4�&)
�,?�q�~d���G�b��o��������`R�#���f(X`�: 
s����<[��b�y��z{�LU���Rm�4s����UF���F��J%����voH�8�#�Z�t���l���z�XAJ��?
�{ZO��z��� /$�&���� ��b�	������`9=�UX�����_�+����J?����q���W�X�W�j5l"_�w�ca����Xy
b��n�}�n��q�������a�����y�>�v�F�$�X��*fc�$O=�n]<o6�n}�>�������wO��nll,����.�����d���'�����_�	�����g��������W
����S_M��iR!�C.���m�����@�N���m�+}�W7�|
��P��k(�z���Z��	'�%��8�OR�������f>j����v��@�p��V�Y�6������{Q���.�{i����Y����c����O�;��/�	nd��n�qK�2���Zu[4!��[�b����K]"_�T���V���mt5���_�C�����j<z���.%9�$6��wp����Hg�1����������=� YSp6�L�����5��F��g�%,��U?����E�V�����A�\��`�?b�����D�S��?g�d���k���������#"�'O��;q�r�UZq�(XY}�p%���5g��uE���q�x�b�3���JxJ�q���@�h>�~�f�Oq���X���_��v��������
N�<�\Gp	d��~RO��/�;t�8��x����������G�G�,=}�x�=Rc��������k���>��$L\S��(������c�
�R�����4/���6�3����n���1H{��,cHw��2H<�4��$�
����E�.��>���'p�G���_8����J�H�x�>��Y~{����9A�s�=$�Ok�Au���-��X'��90#�@�I������N����D0S����q��a�M6��Y�X��������=c���p��{�����!S�f��;A���^jKk�1����#�0	5?�7��V���r��������7�Zsi��3�|,7{�C��U�s\��������q�{���I����z���4x���������$�>~�������4��T���`x=���Z�"8�LDB�Y��}����v���3;�GI ��n%� �F
9����n�� ���=~t����J�����lHqQ�]_2�7�=l��U0/���}l��kJ�+�0�I$�����n�Mv4��Jt$i1���Zi�b>������p��(�5"D���3��G���r�^YXN�,���e�k�t$��w��`�I5%���12%��d?����3������wv�;$��Y/�	31���	W����1�'[OY�mz���w��*w������p?��8�S!+�����b����{e������k��@$vIs(@M��83��I����?�IXF�������p�?���������z3Q<��
����D�|0a%�QS�����v#i6�uZ������� �X"`@��H/�,�p�@�=���yVe|9�si(4�So���Y���]C�Rt�O9��
�a(D	O��������%�=�.j�L��F����I�?��~5���x����JG"��|��J�O�L�������i���Cfg�0��Y�A�����*=��M���t����z�����������z�d�E|��
n��������a�<�����{�����<�6d���5
f]2�/�"�~�u���F�����Vb�G�����?�������G���>0���;�sp;��@{�U�	%���gy?G�
,u@J��)D��v��&k��R?��>y�qg����SdX
�
���`X]�����f����&�Z�yz���x���
%����x�8��I]�e�
0�M<�����U8��F����K�{��0;���s�[zI��pj���#�PaD���B��B/J���	���}����N���:�����L�O��|������yf�?^�0��~Na��L���w:e0��������BaW}`�[ ����,��P.���m2��#*�B�uoH]��gO�^��w�'��C���E���_����0�-L�����
�����V%�U��Q�ER"giz�r9�A�@���@Q�
��������54c�	 -Z�^.#fcv��]�\����6F���F�R�vU{,m��,�4��T��W�h!�*LOp���;��b��^i}_$e���w#&��������:����g�F:w���66���u���A�^���v�jLu7r�/��lo{]?e��R���z�1����I^��3
������?�����f������'�b�x��lc�m����g�Q���<&����0��z:,>)"=�?�a"�WV�����B����*S�j�/f�� aFC�|K�-���P
"ab���]��(U�SH4�u�6���z����J9I�l``������h ��������`*�:�	����2,Z�B�(�}(U�6e����t��pp��}��5H�\��G�d�p��}���7�;�����@F��=��Y�����d��t&>�B+C�cD}>6��/�<�����?��P�P{�o�v�MA�)6�XCo3G���	"-�\t�g�g�7�[�<�J��@��2��bw�c�]�����3l���Z@�X;F,���_�H�ac�����6�
D�V�7-�P8�h�*�����3jO���K�1&C��o���K���io�&kO'�;�w�_����'�z�������0��:G���;Z������O���^�g��
�|��Vb,���-��a��+��qG|�QyJJs3fr�����3#��na����c��������h��1��m#!�n�I����f����w��v#r���t�4�=}�e�gOJ�g~�������BW�Cl+���A6n�8� �Ds#��%���Aq[�Hc�=>��rM=~"���.r��D�v�@��B��IQ0�]�8��7�k!.��IP<
�%��
��P�A:Ax#��k� 2��������z��?vb���f����'gp��\����LZr��DH�?!+����bZ �;!����O���
�{��87�W��".��*-�6?�0I��xD�n�����p�?�����W�
����<{J4���2
pb�9��(A��Mb&F)F��d\ �P�fr��m��4t
/�euc��]}����|�|E��E��&[�m���%|�E�]h�g��r�'���h^Df���(O1:���V���-)��f�����
x�;�4,��yQ���1���HA��4VP���cJE�.��X���Hv��0�����7C�x�~�F�:��g6[ �N���@`w�1p�=k<��z�s�
v'o��Z�f+�gt���-�FT���AY��U�=�V�����&��X����>U��9�t��&������T��Pp��X�\9UNB��FlG������"��^���6��bz-��)�L6a0�����B
���dBk��}����x��]<�;��-��v:�q�7:������q�5v�f��hH��n�$�����g�4�?01�G��VC��}�3�2.�vi��:�z��Z��X'a�TI�m\��q�[�6� 
�:�I���K	P�S��G��M�Ar���@@m#Kj����M���}�����*�\)�j:*��Q_p��Oj|a%v���z��Tz1m��C&����T�K0��RP9�XN��L�Q��0!�����]kS"��A�%+��R�� 3ai{HE���lm/�d��R	x����aRF��H �5^0���O�!����`���
����'��S�@�/������D14�����W�����t�8o�"�6fjA���k�E<;�x�a������ ^$���1��`���f����������swD��Q�� i��q�`��ms��w�g~�����q]�(��_����6�N;X�� ��p���]V���{�����{�\$*+�����~l� �w(	/�$��D$D�|z`�`��J�U!��N3�hg|3v�]�9X���c�&�u��E���#I�J���\i�����Z�?���krP86�����+��q��U�q#��I�����"�w���t��KPJ�����+����5��j2��G3z��3����}�~���
��}�ie!����(aX>b,�\�2�b�����v���>+��O;�o�)����o��fd��:���8M��Eq������st�m@'o��&�i�f�9�!i����D��[���O�\�ZrwA������L�Q���R&�{�}�
}Ed����o2���t��Z)ni�����m�����9;��<�?9<��9?8>j���	�����}8���eL�>)�|0��?�	�4�E�
�u���U�(�kr��8��	�y�4L�y��lQq����O0��d�P}���I
H�K����� L��/kh���oa���t"�?;x���I����s�
8n}Sv��R08�D&�X��"
c��?zX���,�>����W�'\�O�Y����g�?�k�����3^����o1��!��{:�pLXK\�"�B�|pU(�pT��1�)��X��l4���'���:����'�)�B�9��B����V�a(�|������4���M>�d�����nK@
���S��`t�(k��*�%k_���<����$�a;�RT�Bz�Ot��V���am�"�RO������]��y��p��J'VKc%uU�y
u*0�w@T�=��$��I�e�8����rm"n��Ftw��q\L��D�boc��3q�,������kn����o�W��O;�����wO��_<{>���T������� H��'���z�|���~{��bd�3x���Y�����������������������;y�Eo]��s���d�r�v'�f�z/r����:�v&����nY�(��������������!�jR3+����R �R8���H�'�@+�z�;���>��&�7��P,$��R^�d8���EN{�f�	i '"�|�]��s8O�g�^����.f�mD��L��qN�b�����yi�%����)�������w���X�{������l��>�u����)R}����m�������!p��W�1����A�*��q�9��O�T��F6�F���'/^��H�6�
u���)����[������d��0X�V��'X{������-1�S��)��8,ASC<�@G�#j��O��*��6���X:��B�����Z�:��`�	����qwRw��P�'�[�Wkn+�O������[X�W9�<4�`��a��nm5G���c���
����Z�L<�6��~K\�4��&/7��$���J@R>r4�6
�w�hfh0z�g�G(*(���O�iI�JF�M&pQd��}U�O�Q��b�;E�|�@l��%D������[g&V��HTV�W��*�~:�	~��7��n�6���\hh�$0I�d`��=����6� �'o�wNZ����k��~=~�����&�Z G��g���E�\_��+�16���"UD� '~��o�	� �$W�.r���������~A.�OX��Cd.���mu��L��AA�P,u��c���%C����{����"�k����!zE�"�4��9���J��L>�^J�����A��,�j�����.z9$�]�S��!NK{�]�[X���,��J������rB��}��h��&�t?c��"x��[���Gxf_���I�Q���ul���
�������m��c���1j����L4#
�����?%��0}|wp�:8�y��`�8e~:���5k�~����~M��xT�\	����* 8Dh� �/�'�8�3NQmc����0L��r���e�p�Q�K{0�u 8g#�z�3�[���!:�������r�{��@������'tw��_^	��M�9��:+����D�C�p�07���i���c`����u�N���k���+��q�07��u��B���O�����	E
>�����V��l$�Y��O����wO?6N9K�d���v*�K{z��3���������v.C��4Sg�BY�X��LI���(
�{b2�Q��~����y�_��|"8��YG�d
�g�F���;����{wp~��'���_�O��?t`:��%s\L8&�t\�kE�1�3z�qi����a�����"C4���4���51N5�����z����x���Tl������g��T�2 ��q���7���a�	=6����0��wv��K�"Z����R����G�x��V7�d��%�EI�� ����<�g��r�����8�E�}��p�h�1�\������|H�
�l�p�XdM)�:�5c�����ux����X0�<�z�����K�}6p�rI�35����#x�����P��c���������sJlx}|�\�������}4TR�5��o]�������?<?0��Dp����	��:����/�f�����$.��S��Z���1�.��^er���{E��s=�o\������q�4R3�B������]���
\yWn�w97�^v�2����.h�
�����-uA�%T��������p�n��@��/5�9D�J�k'��m���%6H�ZqOC=g�k;���#|����P��%���lZH	Rqe�����{����A%���6�x�s0s�&4AfU*�3��mJ��C�2xX]�V�&��Af�T���-"h���Tl�"8�ij��M!-&N5B��C��d�����`�4�r��11��[c�-��`$�}��q��z6���[xaZ�R�P�r���U��|��]��+�g���Ty��PC/��
����4������N�H!�R�9�S�Pr�����u��H����e���_�' ^P�H��>=����WX�:
}����:��hR�`���N/� ���q&���~�,�7����C5�	��G���+<��������bV9�Qc)wX���A��bU	,�����w���y�wt�����k>�W8�� !�=��(��g��P�l�9yh�FY\�m��sd�3�3��l#@�R^%5������;�1��q���q��Z��?�� 8�K|�Z3{��n�4���a��h��>�e_�@�H�(��Xk��_.�'3�lm7d��-��Ph��L���/b9���"�K�rv��/�
|�0�����;
<��d������;�iD��f���\�;:��
6���2	�O�Taq��>��������^)1��^YN��^v	����������!�j��I��n�	�����b����nT^O�:
�]�u5#��!�l�U}2����XO2a/��s��3^rff��r�e���7�
�RI���N�5��k<";����b�as7��j��=V>�|Lw�\�l,K���<���<�z�lfO2��X>z�]'z%����)�����nJn1��].0v�"v�03m��c]��E����H �����5���`]������p
�&<U^:	b�T�u����+�e�cY��O�����f����>���������M(
�B��	,����!N+����#4i������:(q��}qQ����l$����$���f���2��z�Yd�0��m��R��axt���k��8;�@���h$8���8���S��N�3��8:�����!%{I��`���3�)���{�1����M��S���o��B���������f�����~<���>*��k������t�.|��|<<~s�����z�������aN�����P�(1�c��I�W7����!,�[����g������[��{����a�;R��e�j�1+�`�)�i_T,vY���nP��t�^f���+
���P��1Z��@f�^������XL|��e���8�M�~��+\\�����N��bc����[�[6��6aH��.�*4��J+E��8f���H�����,E@���Z�#}�m������nE���{(��r����%��	�}o�|�����\a��?NjI���e6i!��$l���������{h�03��������������H*0\��=�@Q��KK���`��������19q��A�1����~y����dm-Y������o���
�
����Fy�A������G7��b�$�$�'	���o�{B��!�����S�Dk0��ZUs���<�nK����Y����
W]��N��w�^����k<�>���7��E�F��%hxp���6x
2�J��T��4>���}���F34�c��9��W�7���d��$���Vb��_�	����OpU����t�=)$�t���UL���'�B��������?�;��W�'�\������^�e�g'o��s1���?�)���AE�� X���>{�s/`X���/�zY����q��EV O�/���x����g�B�����^j�Qn���h���9sO��i���L�f��9�r���#nA!o�wp����$��� ��*7��XEN���^�D�}�E41���,zpEf�A�W�p�Qlnkr��R@9�s~d��~������}�3�@&l
I�(�|`��$� D.���������M�������P��_0��	
��Y�'�y���w���\�jWJ�nN3]NYIp�5Y5/X�WEF�5���lv:O���)I����MODf�~�Vu�LP��Z��&G�����3^��rHNI��d�$#��|����%��G�c

����=���x�����%��'{h�����f<�����K
\R��~�P�$zY��]P�ad�<�a}�EBC�(�{���{�������ByU��2_�����k����lAO�����u0�O�+��ja����������������c1j���Wz�]����� l�f���������w�::���y����������������r����u�v���i��������t�ba"hg
Fbi��gRL�	!(��OuL-HX����^�f��
�%,FJ
���
��Q!&����j�E���"]�+��������J:�����p����OR�dJ��7������Rl�$��%
]���/Dz��i��$)\|A�s^B>hM5�����MX�,��gI�l�B���G�'�(MOL�T��p�����3���qZ\5�����C�����*��W����&����&����	������.��^F����F�p�J�!���H� �Q����oz&��PU�A��|�)��1���^�_��#�2�s���H��#p\��k~L�M,���cx_��J�4_�J ���&�n36v��Y";���*��H�Q�:�L���1p������9U��QF%V���}^�>�.9��M�Hz�Q��#������R����
txDds[�taJ�r\�]�bB��c�UhK�C�Iz��b�^A��XA�4e�KY�A3~B����d�xN�CJ�K�aq�4��\���Nz8e��wAM>�3�P�U�80!;f"z@�����02gS��>�����^��vNOw��z�������c&�p��](���e�x��d���v�;4��w�U�X��,0n�>;k��c��O�J>.�o=���fL����d�7|���+����RR����7��c�~|B	l��n����!9h�*�4�v�k��PU������4����B�axNp���t�B�a�M(���?�M�����v����4�#N�����+�q�,n�Q�-�H����`����^���Ee���2k���r[)������y��t�b����v�!C�s����r[����p��9��: b��������+���O-B�	������_����4�q�yPg����Q^q��r��JEF��Lb>��������6����"^T�z�_�/��������	���W�;?wz/�F�����N���;9>='8���(�	�!<f�S�������XcdZ����R��x�=4}�����J�������i&c�~����v������M��W�j!e_�mF��>`@�����BJ	l��2���:��������#g�(�� ��`�����T�s��z8�5p�"oBiB���R��(dF���������k���U���+�r���SQ�e4��"{�i3%�"�"����p������`�$�f���C���K��V���J���������(fI��x�U�cA'N^���f��p�{�Q�_�u��s�)��v��4d�*�`N/w��b�O��aU.�����y��}��~�uO<�=-���%������U�N�����v�"�W��b���|`uW:���bW-����[E�������f��q������Z������:s�X>����(��x,��i������.�����9[����G"h���K�w������^���=�}\����Ox���f�p��nu�v�oq������?o��sp�������w�T��R�(��2��/DB�����w������n�'[N�	�J�=	������%���������5��]c���w�p����4��+�_{>�@I!lm'��p���5�Y�#z����Cxo:J>�)k���8E��m��n8�l���7��o�r�+%k�c��-KJjf
7P�
�����W�@8�o�������/��&7�EVAlH�2��[�p
|�����d_�����y��#X) ����}/�j0�����G�YX�nvO<@ C�o��<"c������^� ����[XW�����_-��`�&�*��L@w])�~�eRSki��-���1��h�~}!�&��f�A��������n/�~��o�|�� b�-m$�&�P��VL�I?�T��b_Q2I��/x�������z��5%���� P�)~@|�T*�zm�L7����'&T�k��u��[�p���*���v�!e<O���Dg��sn��o8�������>�1�b���3
��i�.$�����#�5���kHE���
O+R��_�Td�����1X�N����a�=�2NJ"G����76�3�g���t���S7�or���Bp������:��6�)r��Co�PBm-!���;Go�[Gg�������U�|o�p�|&��cw�cmh8���(���Q����Y����i`r�YAeLU)2��-?72)S�"q�h�9c=�w����&+����gK�
(���ipoy'xQ;�bt�+������QB~�:E�(�$7P��0�����}�8 !H�u>��'���wI�C��-Q�j����a������H1�KC@�Gh���IX8�N���4�	������w���L	=(���
��������L��2Q�ykVM1@��PI���WR����]X&�5�T��������|��_��3oG�x�3q�z=��	����[2m�+\)-�}���0���Xh����z:�U�4h�(��F�w�f�_��|�V4{�D��3��s����p ��Is�M�[�-�)�&r���H�`"��U�uf�Ef������L���d�>1B����,�x�%]������O��`�XtJ��o1�s����dsn��92�C��X�m!1={z����5J��r��{�Q���WhA���ql�c����������qL�/���E��%���Si���)��L1����@�
)�E)�taao�j\f��FI�'\	�D��b]�L�_:K;��@��45_�1�>�����,,��9����y
��/�����c����)�5��44�.�t}��F���������P���!V�%�����-:�(	���y��D>$-w�wK�
?{�j��V�?���Xim��r��.+�	85�1�O�:w����Qt�0�.��FNP i1@`:��~m�#�/��K���0x����]���8������Y8l-�p�0:�X�h(U�2�����
A��uF�D��HRw���z��?W������W^i���\�h� 1�����]����2
����M&X@\�����+�.G����q(�Y�(UP�)��U��]�$ �������b�.������Y5L4/:��������b���#������zE%��}����#Y�I!�/�Z����V|#����+������B;�'�����;R�c�2��]���Y�������O�L�������+r��#>��d���)��T]X>�X���lt2��>���hk��
va���0�P�(����h81j4j��:��Jf���'�����{,��2'��t��
�$H+pi��Ud���`�*��h����'�C5g�\nX!���2��E(��v�7nf��<����9�<����0\���d�:^��%��)*s��:*���	^C�sN/
�k��0���wYQ����m�D��>q�1���q��z�VmA���]�.��Ho��f(+��
�i��X���(�z=|���C!�fy�����Gj�17�x���M�������|�EW�Z����,c&Bn�N��N���vF��m��$�H���E)h{��-D^�1��.1h�!��$+�e��+2�92�R�0�*�J�Y�W�����W��\��Oi�,���[�.�����w���3��<}�S;B�M�Y��/���	�;���Y;o��J2)��
��n��Un�7O�����������6HxJ�}����H�����A��*���J�-V�#g�mTZV��w��]cl1��|�|����>`�qx,"I�/��KM��� �ZB�m��c��&�),�����DY�a��m'e��.G������nX��l�1.z���+���J6;�V�
�n�|���AV}bs T��V��@
�~��3�F������hQsz�c������'x���K&�@�
x;0{����/]�n��hN
[.����d�q+���#������	�>(	�)���p]G�<�,I|�C_���Yj�z0�������KK�R�O�e ���	���X�od^���A�T�������it3*�P[�	_�i��7�D��:�|��W�����h)�eTt���/5��+��v+��/�l�Uu�]^x%��W~)�)k1�F@�
��"e_qL/�cz�����Wy�3w����-����gY)���jTMu�%�wVW�sg�O%��e4��������ug�"S�]��;���[I�����k�����[��f}�8�n�)������HBO��&k�##��_���[�X��w��/�T��E#�a.�8v�x�;(>���P?_��#d=eK��-v�"#���������m�ShtY	/3`���Q���sI�"�+��Y��d\��?9�Q�D��������iN��"�.:�?�d��4�NT��O��{(s��Q��7��"��#�� ��?&�-���v�s������&��X�9M��x���F����b|�F�c�k�����	�t������9q	?�_��-��8q����nUB�����7�A�8����dk���%Y�eH��!��YV�Rw��?.�.�vV�z4�������O&1�����(�f�_=Grp�B�B�
eM��p�N�p=N�t�o�Q���$eP�bT��������F�kq��R�Lq��<"��+��1<H�=�o6��H;��b�U�x���a���#k0�Q�/<��<�	�
�3fpJ���D� �~�G�+zJ��Q.Z��m�=�I6������=oq��].Q�ml�m�_�z��h8�O����*v�ON�C>?��F�t�>&d)�������oF.I(Gv�9�d:� �[I}\�N�}��#3��kOvDwi�����n�]n��Rr���-���CH��^�T���B���>���v���u�pV��w,�e^���dtU=�)]U��B1v4B��h�MO�Vp��#M��Eu�d`j��M\U��|�/���w��n�CA��Z1*z�QL:}�%����8�0#�"C�3<5�&	�����Qc�k��7a���c��@�3Y��z(
~�S����.�T�xK|/*
W��O/dm�L�����io�����$Mk����o�����L��S�8)|e��������OX������ ������$Su��D:��l����^��8�d�����V���S��Pn����� �G������g��gZ�7��+z)`�	��_�]��
���p���*�(�7h�Yp��%VT��|-����6���FH��:��?n��
[��%022�3�(#S���X2���ju&�=7�K�����Q�'� �e%�e� }��2n����Z�����^5PMyU����K';��?���o�E�;��o����{S����*~��6}a�L�{�D���4�YP�M��3:\7Od������#0��P���=�Tr>d@fF�.&��`��p�rI��7�1���\�������8��Q+����)�2l���;WA�<N?��j�����%�}�~+;��g�[^J�/[r,����]�t�oE���K�r��tU�*��yJ�q|�I.���s�Z��lS���������FD�@�������h����,���:�K��m����cI�����]^�@G���(��sZ�|��rYa�K�|?l4���}|Z����*��I�u���	�ijS�_��H�g���3�����D�?r^�;E�Y���!@-����5(�pmp><�a�?��T�����9�q���%x��>A��,)Z��4�!��E�y������e*��y�
���M�.���� �_*G[�����{G�P\5��$��gF�g?&���wh4�,�JQ�MX;�"Ld�8���9R*WP�DW�f��q�����]��E	\������9�t��{��"h���H������������:[�M���G@��tw=���Q�v��j��#�������������JD��M�Y�M�wa��e�m�����G��+�'��}������
H���U�'j($�bw������
Li�������f��i�(���^T��)��u8��6����7"�4�%�K�%0	�k�8Lw�����d�VMX5O=��=�	_��a{W����g���"�dz��g������lV.����d���������pd�jQ�e�w�
��s
������,w[\��&��_���}&g,7V�����s?�X<4 (�vP�+k�,����/	�p�-k�������F�(��ff|2+�
t�� ()�� '��\H-7,��gV�3A����0���="�;�O��x^o/<2�]
[:o
Hp
�pB<�Ay5�"�p`���'���]�1������e��k�f�
'e�x��;0c<��1�
#�3�J��l,�f���|M�������v!��/�=8��h�?RJr�W�����n7C��K��S���J� �l4.����`v2|?��^&�8���Mz���[�0�n&��
jK�� x���5���q���B=�,r���Mdtl?[�u	���d6s?
0�D*��l����)�dI��AC�/P�������G�
|ZQX R�{�A�w.�J����?R��K�Z�e_e���������z�S�����3o4��u�������(;����q���4�����"e���D���]�����]�V�Yl��9�|�#:ZD)��./��$������/}��[:T>����w?j�`O��i������!�3k�]�vy���5[��W�4+UZ���2���.L��d
Cg&�r��/�4&x���v���p�U�����Q��A5�v�
��k����~W@g��s
.�$5x�]
��kp��n�K�_{�8a��� ;�^�����8#���������ZZ����o���wD��f��k�Z�%�=g5j�+�?�3��?�#p��l�c�ZVV����`��I�S��/�����2�������~�a42�,5�L�@(�A����J���zQQ���KFj����>6�}�D1{���)�o��p�0���(��G}r2�@�}�h�d6����7�����W�w��?�U*��;����Uq�<��UF��o���t�����������K���;�I�'u6���!$w���=<#����&��W���"��4���$�+:��-�NK���d��^�4�����<��qn��v����_)�����^-��~��q�/�)�j�Z�m�)
0005-wal_decoding-Implement-VACUUM-FULL-CLUSTER-support-v.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0007-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0011-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
0012-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
�@�R0012-wal_decoding-design-document-v2.4-and-snapshot-build.patch�[is7���_���W�x���k�LK���,)��I��u�3 9��`�4����O7�9h���}���fp4�x�@��4���D�������|z����w�?>�����#59>�?�����$�j.z'b������^�',3��O�?�*�}�\��/e,S��
;���q&35�uZ�w(���{}|�Ox���z��|�����xw=�;�Y����{���_y���������/�T���A�P`O�c9��$k�q�4�2V,�;G�F��n�z����U�wS5OfAw�d��a�lt{����}���yG�@�����{#��$����8���t6��
���:����Wo�/�*�<���}Q��/hu-�����o��x�Z���~��m/U��_�������W����o:A����h��A&d�+1����X-�%UzA����_��t��L��i���j���0l��_��������8z-���/�n�����B����F���9����Z�P���$��0k��r�N\':��U�h4^2_��
���o���d,i-����^�I>'�:�,�g�$]3��8��_s���/2���*�A8�p+:^5� ��8��4�s�	|�����]��"-��Ou&��y(q��l%�Jd� �sw�X)_c�$������X�$����r&3��_�c3�$��J�UI��d�x�w37��.}�2o���<�!�4���A4��v�$���P�r,-c�dX�RM�&��>��,�f"��=�c��@@m�=ad�i�m����H�u�-�z�KfRLU�R��� =O�r]�
����<\��4�8M�	v�����aq�8\	�-f����#Y�������2BjV$J�6���2@x�0����k9�A��K�`�)G'�5H��2���,�{�&��*��s��I�C�BZ�t����/�u�A�C3���N+r�R%$�d����P�S�P@�_�5,+��R#>@��@)�OY����[h�L�Y�����b���,���n��,���#�v�t����r����%��?��{���P��A�4��*F�nlt_���:UUb�)�^���98�l%��d���N����t���J�yJ����`fE&��6�xoD��dB��:��8�.�:#�kZ�!��A������Th����{/zF1��0���J���.&�`����������H����Ac�+�h���U�����O~���H�y��K�9����4���B�4?��-���1e��)l�	%k]	�#2p���,'�,�25_���K��E��<%%��n#��~
��,��>Bltd9�2��H��&���	Tm%�<MA,$g3�+�����c8f	 ��sB3:h�j41�MP�� �����f��^ay�~��&	�+I���B�f�hR���*�_A�c��zN���(�a 	���<�|�b{&fy�$���KX,��{=[i6�I�d�U
�]M``�����1(��h>��'��Z��UE��b�-�z?,����=���9����\{T����
JL���2&��^�"���cL��% ��b@�wO�A(3��q��"��
�!
�� Yj�<5.�:/�}^(��L.�]"Lx�z����t���$sQ���{MP��E%�Y�4�`��"E��Y������3�������{�8����N���S�h�"���Y��?s���5�t�zZ�	�}L����^
�)<�5��E�ko��D9�i�I[F!b�+���R��-��2��l�q�����Y<������Z~�-~F�lG�V��ja���~�`���#C��q�1����e��Q��`�SEl�PVFPV�8��sh�UL<�%�q�0	p���?vi�^��,Mb2��a����NP� ��,�����m�`��s�NN�s���������B)]�c�Sjl'c(�M�Gz�<�:����:"J2� X����I�h��Fs��B�M���<� ��>��.8&BI����"p
��U�������)rh�!=4�OLLb�V�����r�2�&�9{�8�l��s���	)��ad�js''QD�27�R���������U����$�n;���}K��B]D��rNac}mN=*	��0��b�Jt��;/�ju�B����W��&X���R�A
���s��i+$j�M>����Y^9��O�&���l��`C/z&��}���"��g�,V�������YB�t?����yF����q~�����"����"���fN(uKs��"��������r�Y-��(�*R��t�q1��E����y�*�2��y+�;�d��e����&�!A��HZ�"���0 r�M1����]j�|k'��1��vUp������+�I(1c����A\|���P���Y�� S�v��	�g` q�0 bKD�H!���$��5O�,+�&}?�8b�j��Ze���[Z ����W	������cm�q7���\���g�F�$�+�0�����6���T3LSJc�!�x�Z�@ZS�\rU�:���u�-*0���u�j���&��]�6w93������Z9�T��/�5���1��hK�&�\�j�0j*���lI�3�u��%����t��M����h���/�����\,?�RI%��sY������l�.�4���f���;@�G����fqBEB�&4I���O�m���zs�c$�[_��>�!�����+g���A9�����e��b�!%�l\��TK1�����+pn�������;t	ID���`��Pn
�An�i��1y2$��Uz*�+�!���,�����S���1C����6)JPS�����i��e�X� ������d}��uNL<TQ�����{#R�������������7_���h��R�"����Z���YYD)}_�+����,9��Om����xe*�����#���L8p�"(q�x���H�'W=LI��C��2d����P��!6�Z�;Wy6�4��|�;�q�p�Z$��0/�r(Qd���4[���*	S��]f��'^p���2�E\��eA���=�l�r>�\�*69����B�x]HW��`k�q&��x�������h3��Pn��H����6Ty���4.���_ry(?�3E&���r-�8����)SS_�Pe��2���1�i�P)�0��E�<�*&�S�g�]�q5�����F��S
�A�/����?P���i4�M�]*���6�'�O��}�'�� ^��-��y�,��O���C�W�]���W��P��?T?�On����+Hnn�l[<T�����+t��6���^���`ts���G?0sh�����
��}�:��@vyG�b��M���*l�(	�����t)��n���.������>25[wy�%w�y::����.7�J9���&����v��YL52����@�[��/~��"��C��a���O]Z�X�m�kzR=�a��&7��V��#����b+-$
R=��t���f����Ee�F&�9���f��r�)�\�c��m��U�4����"�)��>o��hu4��������oF�mq���3|��>���0'��sn������/�E	ZE�p������]��>����o��+��������3w���o6��}�a!�ax0HvF�A	�n����>g?l�Q;����g���?��8�,X�i���F����I[�a���4��1)�Z�����0x`R-N�]��b.
�� 8�Ox���{LW��LHg���-l�,9&l4���%u*&]pZ���s��Q��f���
NGdd3�(�E��Z[N��2F�Q�#�(��$Tx�j4��k��FWdnab���	,3������b���LV�9���
�=!U��������O+�����E��dS�����U�f�*�dE_CQkl4M0��Y��{C7�H$��8�-m��	]��w��ZfJ9HL�q�g�*`q�WTN����������y?�f
t�k������3i�a[�vhGd>L����iF!��/�t�+���f��te�e@h����&;��2b&�nb*);"R�k:�7#dn���o�H��P9j������,���0�������l���^���*-�Zf`�k�����"�a�u���mF]p\q�w�g�B����l�2��V�k��)	��3�p?EQ���R��Fs�<��@��@%���*mFe���X��k��Xy��V��������"�#5�� ]�jV&���XB��l�I��B�����+��b������B\�������`�u�[
��������Bq���BqX(wI1u,o0~\�+��AI��.��P�N��k����/����O�����{���^��Z3����+�����Y>�4W�k�������R�t� {�f��Sur��6�CEZ)�P9����h3n�����=KR�t���JWQ3�	�� ���QCn�l����R�TV�,�����m���_3��I^�#��B���.�lm�b0w�`����=�k�u5Y�K%�n���-ea:����[�D�L.�%4�p2�E����k�����*6,	��,Ll@�$������7uA�Sgg�6\�y��.y���@�����&��w�|�u~����NF�AH��"N�g���T���r���2���8��
�H��J|-����C*h�|��?��)���4�����8��\��k#}���F��������)�/��X�:`�eTq�m���	��9	p�D�m<�	!���v��k����f*��������~�A���j���M��!�J�C��D����2]��:�+o��J'����:w����C��������23���bYS5�6k[��rl[4L�����d���v��
�'}\��E�nd��[e���s��aB�D\Q�����4�������F��~%�%��/c���(�����s��ARon��)c k��f�O���]�;8��6�.�����R	M���23$��%��\B3�_�?�	�sGE�$�E����J���zvv105s����I�����3G����Ww������G3-z|j���G���%i�7���6�7W����������ft&v&;��h��>����7���H�
_]�x5��B=��+;C��/o��w�W�vr�yvsum'��$F�����V�?�C�������N����|�a|0�u��R7������/�W��w��mX�I������o��o����0Mf���A��\����I�`w�r��K�p'����-�Zf�]�_����[���������z�����3���l?��������o.�X��>O�:Z4�$�i��]^������v��]���s��o/.�S�!e�1�EHi�I
 C�2�k��R�7l^�I����w(L�*���n���w���q�16��.�oF��2�Mi�8��R�������3��m"�9|�p��HL$��bu9�������G����?�Gd��������F��}3����=�Qq��Wf��l���r��:6l�D�����{taD�(�4��vOv������g'OwZ�y5�?{�c��mxs	.�`��>0J��!YNl�@�;���A����)�;�����[��+���s���8X?��}w���>�����6,v��b��X����+����{�����S����}�O�����=�@������:����_�H�'qT�bGl������ZfH�<i��j.e�_�%��� �}�����%���8d+�e������7�&E7�mrf����b������������_�f[��_n1����sV{0U_����U���=[���+�|U��z�m�w�Wf���������DT��G��	��Y���bT����Fu�A��(��C��kG+�mQ�%�B%7(����_sW��F�d?�"�����kj����s0xa�]��SC���X(i�@����'�����^j��C�A��o���I���<��;�(����*����+4����zN������s�f5r�����&��u8��}��^��a��22�5=����4����j��4a�u�aX^C�!��Y�FQ�Y �Q4�zc|���5��#^j��$���U�I�Np7)S�4�����
�Ev�Z6pIx�m�3EP��+������Lp�djJR��C�a0�����Qn_��O�����V�I�v��������4��5��-mT�#�Iz���OC��g2���GT�/o���S�5}�1'�{~������%s�Y0��yU^:@V��F���������936��J�<��k&S������� �� 7��@Ob����z4(~T����e=��_�~�������=��NZ>r���G�a��~�D���i&�5(���^"��6�������2^E
���c�al;�M��x������k$A�8gV�R5�����	#�*�9�,%���.��1��"�0�Nn������'3�_����&����g�?��!m�r�������6��7;(������x����m�|�����s��x^��S�A���abG����HE�y������>. ���=�7��x�W�q���1<�
�	&����)��i�y�Y�����K��"���m��*_jp8��@��#�8nbB��3Q��n��� ����%@8F��c����j�S��D� `�1�����G�4G8D,��p�M���)������=����
*��-�����n��2?�^F�\��3fT��E�Yt�S��eY~�_��T�JT���V�!���Y��r7��O��N��x�d��eLt`��e?h0Os�����(�#��-�?�@g��y��,=W�����w{z��������j�Bd��1�f���������_,?���b`K ��bd��x�$>�7��6J�3Z`�	 QE�k�<���I�h�����0Ca;�4r�\��o|��}	p2|����t���E$QqZ������HQX�GM4��<�	�I�5�2�TIB�[�z�aDM�E'>����$9&��.�nR��Z����
t2�7E���f�L��8�&�P���%
��I��+�?E_�|bb?�&��H�`d��B��_��Nr�<S��r�>�)�W6��#GhE��F���md��BT������N�Y%�,
2ziU�n�Z�HB�X����L�b����R%j�|�b�a��J�����5��dB33'�
E����������5��p�����e��
���(l��ISCo�x��n���g1E�8r�<Jc1��d�����M��K�F�(�]�he�(q+�|n��h�N�[`�OJ��=R��J��p!����

�S����������U�`�.���>T�*��Ms�����n�f(�S�d<��@�Y�z���b��cS��o�+�w���^Z��3���r��S���i�
�7gN��S��Uk.�^!����%@H2~���5x?�*�������
��_t`�jPq�n(���Xgv~e�7r"����EJ�<'��t������5����&�du�-d$��K�__�����5U�}�{�j##����fCiG��/1k��e9-e���\���V�H}�x�/^�2y��T������P ��1�O�$�9G���K�%�/��5��{t��z8�ROj��3��.u��gqw������Bt��{��=�'���F����!�D
���6���gTL��)��r�m�}�	8�lO!�W��s����p��~�b������i�j(�z�7pn����kr��7��Cx\�S���K�af8�F��|'T�d�OgW%���J���]�y+����T]�R��Rx���P�7�Nq��^7km��m���*(q��e+�*}	���\��}���:8��$j���b/"�>�h�l�Mn:�n��������������9.*X�O���/������j|F;Q[oP`44��������U�Va��2�+3Z�F��g9�"h�^�b�m�N���d0z�#sU�x.�8�����]�s��	��M�$D�(2rL��e� _@	:dr���< $��b!Gj�?��2��7F)��6"���o(z�0
2�K�F��)�#Hqhy5�|�l���h=�M����0�a=�n�������i��7�f�l{YZ��WpT#��(c6���2��kh��S�n��c�����t6����:�IE7�%�HZ*II��.(��
T#^2/D�+"���"B3����F��5;Nc(u��U�V3����L#��Q�\X��9��>��..�������i���F4��9���.���.�<�8�|�{g0�4C.(�����cuU�Hr8�T�EP��-E��9�X76�� >���i$��Gw����E5>�����]�
P�DTw����~p���'1$����y��c�1��A`F������_�.D�<�]�����}:��`�>z~p�?�q�������l�������yS���'�,��}f����~z����l�tB���j�m�Dt���S<�D�I�$�����$w�;�����u��sqX��&e��u#[��=]�$�3��c�
D�z�����\�i�H�]L{���K��,'�n%$�*VM�Z,�����d�x���'G??y�=�gN{����`��N�<^3���3�������O�A���j��V!o`�*����<���������g��w�B�k��C�&��[����99:�:<��V|�z���X��/�
��"we'�'}X���*�S"�y��H�������/�eCR��/om`Y��'�A��o���o����U�+CI��a@��t�m;��7|V�,>�zq��u�� ~��[x�}�D�z�4��G�_�����7���l@���J��X�������m���nc�����`�Zd;�8�C"�����E���fZ�k���D��,�9w�
HR��q�!s%y�9
�SS��1����F)�u���:G�KWr6`'>����� ���	��*���I.���Z����U��7���?O�����y�����G
����o7�.z��?�+�1Sx��_�|�������a���bM������5�d}�8x��G�C�1��p��`{� h��}s�`���m�J�l�/�f����*lm��.��5���.G��IQ������kY�������D���f�s��/�#����B�'6����G7�I[ei��/��~L��������o����QK_���Ba/��JPg�u����NiE'�u���!�3����]$-%�f���5kb�&{��������6�)�
����9��	��+�uV�K�A��#��+U���K�<�����������b����lv>(��C�$d-o��1dPm��V�"��������jB�rC�v�.�B�>w��%01
��]�5�(i�t�h�/V�
���"�+21���������V���~|���[���|�p"����$������.Y:�NE���������6����uhZ{�Ms",-�0K����eGb�W;��k7(s=��JC����K��^r�;l����������6��&a0D[��F��#tL�2FI�L����� ���w:��e�HwL���!�t+���3$��0����t��U������|�n,�	'A�����^<@��@CU��y�����8�*z�_*�V��[�a�����V�i=-�A0t��C��L���,�Ny�3��S}vw�Q�����r*E�%������N^~QN�S����;���T��^ZHj5�p����h
����4?� Y_oo��5�D�$���p�O*/���;B9@,	�����,�4�cY(��%�7�;�3�fD��]O��8����|izo�@&�{"��%X++O$�w�8�m1��U��Wm(��n�:��QO���xa@@&���e�s)I���� k��}4�KF�c�#E�@s��^]����JHA�i�� ����#�Hz/����1@+a��%{��]M�R�/(|a�H{���g�]��q��z��q�b�������|�0�UN�0\ �M">bXX�_����`�W���a&��u�-8����2
�5�F|UXtP�,�1pB��Q�C�Zn��gS��0���Hb���v�T+[uC#[��X����v�Q&&��P�,gL���r�S������_�##�.�����C,�����i
K�6^12 E���i\���o��jV�?M �,Ki����`
�b0���P�3^���&���i�::95�4b��#�(�r���PU�g��[���:6�4
�����+���F���Nu�~Pl��-}	1���ZH�j��A"@���ol�����G���^��N����kC�����O�4���|����T7����L���J��'C@z����iu�?�2Eb���?haTK���rQNk2<a�p?';'� �x/��/*�D�����Hg!�t���Dk�$��E�XOkP<��j����\On�jB��e/o�������]��r ���w�;U��T/��cR�$�\g����N�%���z�h��JO!
�D5�e�f�� 6�-k��8'��rr�kU��RFT�=�~����:N�NB^HQ��$=54P8�v�����z������W���&���rRK�8������z������}d�����>>���R�G�3����=W��KB�b]L5��Ymir�A�F��K��K�ZR�JJ�%�`R���G��6�M�(�+t���b,�3���?�lFP]�!Y'S]4����En��v}Pl�~���>���Y�)� ��a�VvJ}��+pC)���Fs��a�g�vV9�)I�2�_���2y�]����V�H7�;s�w�P�g5p:<&
�=m>b���W$�~�pW���j[�����"�6�������Y%2(s�L��4q���(u6���J��Y-�8?���f��U�I
�%0�K`k!��t�����
������kY���a���O�����Em�x��{�b��������O�P#�(�8���^�:Q���h����u��n��S�{�A�\���d�X�3c�1?��4���;�z������S�n�5)9���gqa�Y���P�f_++G������N�I6������$P4e�����d�������S������'��PNf�s��-B��x���$�6�l���X��%��SaX�'�����^�ni2����W�����y�y�������&����cd�RQ�X>kQ����\��f��
�����s���	��/�������������f��!�~4�,Z]w���Vf�n�}�pW�N��wKPd+�X4����>���w$B�]��[�7�Cy�8~�cc�?�+vdT�b��H����X�s[a��+;���+�o��|�o��,�����>J�n�k��j���X�.��tW�b�N~��3�������x�z��W�����|�[��_v}������2��e��>V,)�����������u����<��G��9�������)w���{�W(�l�yH�c�XR�G}�(����y5��1��j�X���l�8L�j
�%FQZ��sKmP�����k�"��#���0��}��7��Rd�)�J;��D�d�����,�������4g��p�SQ`�g�6���,��#��������]��e��5�3k�C)Gu}=��Dbo7���Mt�B]�H7�Z�O��q�Z�t�#����U�]�`X���Z2W���Z�� X����Q�W�m�i���Q>r�A�JyC�`��M�oR��P[+�PH"��/Y��6
w*��l������A�1�$�xY�^�y-�+zY���kF�����
����W�xp�g�"//v>k�^���e�����2�%O�?�I^������J��e�9l����d�Q���t�����^����P��:���<���2|.�Sc��/���e�r�@�8��]���J�u�CswFp����:#o�����,z�(L��R��sdUmL1�/@�����������KX6�����)�!�V�QXUj����^�T��s
A�M��u�FM:�M�������N���p������/b<����~b�;e����Z��V�owQ��Gwv�6w^�K��)����]��������>_~i�e�$!+��/m�������A���_�4���5SO�����)bn���3q8�I�4�9�lC�2�)c���������)�b�H����Cw!�B���1�g�fw�������&�B�:�Q�p�au�6�|�-N�mGK���-_�&����1n��4n��I��PvY�"\F���aor�������TE:|��7��s��B2e���hQ�r��a((��y�
�?!��G����5��B���W�u���ZX��r�l�<l�$���w�������$��{P�_��h������F
O�`��2��<d��v2�P<?���K*z�S�x���*�z
����������xN��� ��aq���1�p
��H��
��P�j0H|1�fg
E����X�~"����@56��(�V���7�V���{�RB�y������� ��T�bq>�R4E��@qh��������T��"���%������F��d��8W�v�_��^����E�n�?�������c�@A���[u��99�T00�K�~�S��F��|��}����W�^��f��\�u*D��i��E����N��$�+��G�3�;��0��e�""+*2S���b��>r��QAX���	�����a/��r�*�z�]K���$J����v=�?t�
��!g(�=�	��g���O��YR�p�i����]�!�`�j5h8m����m����H�l����]m(j�S��N����FjQ�k���O(��8�if<S�VkV��=���^J�E����_3]_7�6�Z�
>��D4��^�)3��c�����0\I�D�U0��1S�L���%�k2y�H�9%�3�.�I���_��~�r�����tx0�������!%���
0013-wal_decoding-Temporarily-add-logical-decoding-regres.patch.gzapplication/x-patch-gzipDownload
�@�R0013-wal_decoding-Temporarily-add-logical-decoding-regres.patch�X�n�F����d�ER��4Nm�6�����
zKr)m�Z2��my��R�%��i�@�v!�<��������h
��mwj�����^�m6i�u:N��vH�^��i���u$`@cpZ`����m;�9���KH������;���A$��r+��gD�.�*C��
�;5p�]��-0m���A��I}��_���^�S�:�����Q@�(`b��!���$��� ��O8,(�t����V4Q	��
�35���J�b@"��~[����.F�4�R�r�V�*���8_YG���(+L�����m���B�
c�8�|�����r�u���P��V�����\��G�}A��ZaCC]xhcB�����1
��l�a��H�#|I�0�
�f�^�s�c�D�u�|�S�o�Tq��`F��*�1S@����+Nkz�~u{����-�j���m]����H975O�tr�l���[prb���Q<��A�U�tQ���^C��\z_����F�aiY�F	�T�%��L�<
h�����KGG0��RUM�l���rU:6�\c�����#d�I�Y���o��
������+V��.l�Vs:>�?���N�{��W�]�0~��S��2��8� �*)"��%�0_]�{�t����[�C(n �&Z��)��������~��fJ�l�
cvC2���9N�T(P����(�)OJ���P�A��9��k�$Y�z1g�+�����G�(g�<'+
����1Dnt�"��l�m�����j���(�JI�"'��#��W������EE6��D��7�q�qj�gWpp`�?�~��g8�<��/���`�N����G�l�{�	�|����q�J����.F���E�����u����Z������[��3�^�w���?���[��)-�`(�Q���cu,M����-����j����Kf��%��~���x����s-w���Y���m0<��ix�O��[��"Yc{v���B�l��������%�d��;��y�U��=m*2|�����g��xK� �KJ��h{v�������E������]Eg�P��v�	f������
P�$1�q�3`*�<,g�LT<��4��
�H��\����GL�c-����%
��������B��/B�	��f~����&Di1HG%��I��?#I2�H�0}<�1a�!4�U�A�b7����=h�+L{��;��[�)7�9S���	n8���1����@�.�Lc���0H}V���h���4�+��Q���J�c����OW��@@9�!Xg�T�9�a����L&������`aV&M�������#�\?����5���v���E���vj� s]�\��e1�P`l��|�i��@2���!�0��/�I��C���h���� ���/�8%bq�O�����fh����%�P��F�a��Ccdq}7�kQ}rL��m�����"�MW�-�M�M��T���>���!��l�o�~���$l��������o��a�������0��c���%}�
�T3���Z��
#150Andres Freund
andres@2ndquadrant.com
In reply to: Steve Singer (#148)
Re: logical changeset generation v6.5

On 2013-11-10 14:45:17 -0500, Steve Singer wrote:

On 11/10/2013 09:41 AM, Andres Freund wrote:

Still give me the following:
update disorder.do_inventory set ii_in_stock=2 where ii_id=251;
UPDATE 1
test1=# LOG: tuple in table with oid: 35122 without primary key
Hm. Could it be that you still have an older "test_decoding" plugin
lying around? The current one doesn't contain that string
anymore. That'd explain the problems.
In v6.4 the output plugin API was changed that plain heaptuples are
passed for the "old" key, although with non-key columns set to
NULL. Earlier it was a "index tuple" as defined by the indexes
TupleDesc.

Grrr, yah that was the problem I had compiled but not installed the newer
plugin. Sorry.

Heh, happened to me several times during development ;)

Which I suspect means oldtuple is back to null

Which is legitimate though, if you don't update the primary (or
explicitly chosen candidate) key. Those only get logged if there's
actual changes in those columns.
Makes sense?

Is the expectation that plugin writters will call
RelationGetIndexAttrBitmap(relation,INDEX_ATTR_BITMAP_IDENTITY_KEY);
to figure out what the identity key is.

I'd expect them to check whether relreplident is FULL, NOTHING or
DEFAULT|INDEX. In the latter case they can check
Relation->rd_replidindex. The bitmap doesn't really seem to be helpful?

How do we feel about having the decoder logic populate change.oldtuple with
the identity on UPDATE statements when it is null?

Not really keen - that'd be a noticeable overhead. Note that in the
cases where DEFAULT|INDEX is used, you can just use the new tuple to
extract what you need for the pkey lookup since they now have the same
format and since it's guaranteed that the relevant columns haven't
changed if oldtup is null and there's a key.

What are you actually doing with those columns? Populating a WHERE
clause?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#151Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#149)
Re: logical changeset generation v6.6

On Mon, Nov 11, 2013 at 12:00 PM, Andres Freund <andres@2ndquadrant.com> wrote:

[ updated patch-set ]

I'm pretty happy with what's now patch #1, f/k/a known as patch #3,
and probably somewhere else in the set before that. At any rate, I
refer to 0001-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gz.

I think the documentation still needs a bit of work. It's not
particularly clean to just change all the places that refer to the
need to set wal_level to archive (or hot_standby) level to instead
refer to archive (or hot_standby, logical). If we're going to do it
that way, I think we definitely need a connecting word between
hot_standby and logical, specifically "or". But I'm wondering if
would be better to instead change those places to say archive (or any
higher setting).

You've actually changed the meaning of this section (and not in a good way):

         be set at server start. <varname>wal_level</> must be set
-        to <literal>archive</> or <literal>hot_standby</> to allow
-        connections from standby servers.
+        to <literal>archive</>, <literal>hot_standby</> or <literal>logical</>
+        to allow connections from standby servers.

I think that the previous text meant that you needed archive - or, if
you want to allow connections, hot_standby. The new text loses that
nuance.

I'm tempted to think that we're better off calling this "logical
decoding" rather than "logical replication". At least, we should
standardize on one or the other. If we go with "decoding", then fix
these:

+                * For logical replication, we need the tuple even if
we're doing a
+/* Do we need to WAL-log information required only for Hot Standby
and logical replication? */
+/* Do we need to WAL-log information required only for logical replication? */
(and we should go back and correct the instance already added to the
ALTER TABLE documentation)

Is there any special reason why RelationIsLogicallyLogged(), which is
basically a three-pronged test, does one of those tests in a macro and
defers the other two to a function? Why not just put it all in the
macro?

I did some performance testing on the previous iteration of this
patch, just my usual set of 30-minute pgbench runs. I tried it with
wal_level=hot_standby and wal_level=logical. 32-clients, scale factor
300, shared_buffers = 8GB, maintenance_work_mem = 4GB,
synchronous_commit = off, checkpoint_segments = 300,
checkpoint_timeout = 15min, checkpoint_completion_target = 0.9. The
results came out like this:

hot_standby tps = 15070.229005 (including connections establishing)
hot_standby tps = 14769.905054 (including connections establishing)
hot_standby tps = 15119.350014 (including connections establishing)
logical tps = 14713.523689 (including connections establishing)
logical tps = 14799.242855 (including connections establishing)
logical tps = 14557.538038 (including connections establishing)

The runs were interleaved, but I've shown them here grouped by the
wal_level in use. If you compare the median values, there's about a
1% regression there with wal_level=logical, but that might not even be
significant - and if it is, well, that's why this feature has an off
switch.

-        * than its parent.  Musn't recurse here, or we might get a
stack overflow
+        * than its parent.  May not recurse here, or we might get a
stack overflow

You don't need this change; it doesn't change the meaning.

+        * with fewer than PGPROC_MAX_CACHED_SUBXIDS. Note that it is fine
+        * if didLogXid isn't set for a transaction even though it appears
+        * in a wal record, we'll just superfluously log something.

It'd be good to rewrite this comment to explain under what
circumstances that can happen, or why it can't happen but that it
would be OK if it did.

I think we'd better separate the changes to catalog.c from the rest of
this. Those are changing semantics in a significant way that needs to
be separately called out. In particular, a user-created table in
pg_catalog will be able to have indexes defined on it, will be able to
be truncated, will be allowed to have triggers, etc. I think that's
OK, but it shouldn't be a by-blow of the rest of this patch.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#152Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#151)
Re: logical changeset generation v6.6

Hi,

On 2013-11-12 12:13:54 -0500, Robert Haas wrote:

On Mon, Nov 11, 2013 at 12:00 PM, Andres Freund <andres@2ndquadrant.com> wrote:

[ updated patch-set ]

I'm pretty happy with what's now patch #1, f/k/a known as patch #3,
and probably somewhere else in the set before that. At any rate, I
refer to 0001-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gz.

Cool.

I think the documentation still needs a bit of work. It's not
particularly clean to just change all the places that refer to the
need to set wal_level to archive (or hot_standby) level to instead
refer to archive (or hot_standby, logical). If we're going to do it
that way, I think we definitely need a connecting word between
hot_standby and logical, specifically "or".

Hm. I tried to make it "archive, hot_standby or logical", but I see I
screwed up along the way.

But I'm wondering if
would be better to instead change those places to say archive (or any
higher setting).

Works for me. We'd need to make sure there's a clear ordering
recognizable in at least one place, but that's a good idea anyway.

You've actually changed the meaning of this section (and not in a good way):

be set at server start. <varname>wal_level</> must be set
-        to <literal>archive</> or <literal>hot_standby</> to allow
-        connections from standby servers.
+        to <literal>archive</>, <literal>hot_standby</> or <literal>logical</>
+        to allow connections from standby servers.

I think that the previous text meant that you needed archive - or, if
you want to allow connections, hot_standby. The new text loses that
nuance.

Yea, that's because it was lost on me in the first place...

I'm tempted to think that we're better off calling this "logical
decoding" rather than "logical replication". At least, we should
standardize on one or the other. If we go with "decoding", then fix
these:

I agree. It all used to be "logical replication" but this feature really
isn't about the replication, but about the extraction part.

+                * For logical replication, we need the tuple even if
we're doing a
+/* Do we need to WAL-log information required only for Hot Standby
and logical replication? */
+/* Do we need to WAL-log information required only for logical replication? */
(and we should go back and correct the instance already added to the
ALTER TABLE documentation)

Is there any special reason why RelationIsLogicallyLogged(), which is
basically a three-pronged test, does one of those tests in a macro and
defers the other two to a function? Why not just put it all in the
macro?

We could, I basically didn't want to add too much inlined code
everywhere when wal_level != logical, but the functions reduced in size
since.

I did some performance testing on the previous iteration of this
patch, just my usual set of 30-minute pgbench runs. I tried it with
wal_level=hot_standby and wal_level=logical. 32-clients, scale factor
300, shared_buffers = 8GB, maintenance_work_mem = 4GB,
synchronous_commit = off, checkpoint_segments = 300,
checkpoint_timeout = 15min, checkpoint_completion_target = 0.9. The
results came out like this:

hot_standby tps = 15070.229005 (including connections establishing)
hot_standby tps = 14769.905054 (including connections establishing)
hot_standby tps = 15119.350014 (including connections establishing)
logical tps = 14713.523689 (including connections establishing)
logical tps = 14799.242855 (including connections establishing)
logical tps = 14557.538038 (including connections establishing)

The runs were interleaved, but I've shown them here grouped by the
wal_level in use. If you compare the median values, there's about a
1% regression there with wal_level=logical, but that might not even be
significant - and if it is, well, that's why this feature has an off
switch.

That matches my test and is imo pretty ok. The overhead is from a slight
increase in wal volume because during FPWs we do not just log the FPW
but also the tuples.
It will be worse if primary keys were changed regularly though.

-        * than its parent.  Musn't recurse here, or we might get a
stack overflow
+        * than its parent.  May not recurse here, or we might get a
stack overflow

You don't need this change; it doesn't change the meaning.

I thought that "Musn't" was a typo, because of the missing t before the
n. But it obviously doesn't have to be part of this patch.

+        * with fewer than PGPROC_MAX_CACHED_SUBXIDS. Note that it is fine
+        * if didLogXid isn't set for a transaction even though it appears
+        * in a wal record, we'll just superfluously log something.

It'd be good to rewrite this comment to explain under what
circumstances that can happen, or why it can't happen but that it
would be OK if it did.

Ok.

I think we'd better separate the changes to catalog.c from the rest of
this. Those are changing semantics in a significant way that needs to
be separately called out. In particular, a user-created table in
pg_catalog will be able to have indexes defined on it, will be able to
be truncated, will be allowed to have triggers, etc. I think that's
OK, but it shouldn't be a by-blow of the rest of this patch.

Completely agreed. As evidenced by the fact that the current change
doesn't update all relevant comments & code. I wonder if we shouldn't
leave the function the current way and just add a new function for the
new behaviour.
The hard thing with that would be coming up with a new
name. IsSystemRelationId() having a different behaviour than
IsSystemRelation() seems strange to me, so just keeping that and
adapting the callers seems wrong to me.
IsInternalRelation()? IsCatalogRelation()?

Thanks for the review,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#153Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#152)
Re: logical changeset generation v6.6

On Tue, Nov 12, 2013 at 12:50 PM, Andres Freund <andres@2ndquadrant.com> wrote:

Completely agreed. As evidenced by the fact that the current change
doesn't update all relevant comments & code. I wonder if we shouldn't
leave the function the current way and just add a new function for the
new behaviour.
The hard thing with that would be coming up with a new
name. IsSystemRelationId() having a different behaviour than
IsSystemRelation() seems strange to me, so just keeping that and
adapting the callers seems wrong to me.
IsInternalRelation()? IsCatalogRelation()?

Well, I went through and looked at the places that were affected by
this and I tend to think that most places will be happier with the new
definition. Picking one at random, consider the calls in cluster.c.
The first is used to set the is_system_catalog flag that is passed to
finish_heap_swap(), which controls whether we queue invalidation
messages after doing the CLUSTER. Well, unless I'm quite mistaken,
user-defined relations in pg_catalog will not have catalog caches and
thus don't need invalidations. The second call in that file is used
to decide whether to warn about inserts or deletes that appear to be
in progress on a table that we have x-locked; that should only apply
to "real" system catalogs, because other things we create in
pg_catalog won't have short-duration locks. (Maybe the
user-catalog-tables patch will modify this test; I'm not sure, but if
this needs to work differently it seems that it should be conditional
on that, not what schema the table lives in.)

If there are call sites that want the existing test, maybe we should
have IsRelationInSystemNamespace() for that, and reserve
IsSystemRelation() for the test as to whether it's a bona fide system
catalog.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#154Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#153)
Re: logical changeset generation v6.6

On 2013-11-12 13:18:19 -0500, Robert Haas wrote:

On Tue, Nov 12, 2013 at 12:50 PM, Andres Freund <andres@2ndquadrant.com> wrote:

Completely agreed. As evidenced by the fact that the current change
doesn't update all relevant comments & code. I wonder if we shouldn't
leave the function the current way and just add a new function for the
new behaviour.
The hard thing with that would be coming up with a new
name. IsSystemRelationId() having a different behaviour than
IsSystemRelation() seems strange to me, so just keeping that and
adapting the callers seems wrong to me.
IsInternalRelation()? IsCatalogRelation()?

Well, I went through and looked at the places that were affected by
this and I tend to think that most places will be happier with the new
definition.

I agree that many if not most want the new definition.

If there are call sites that want the existing test, maybe we should
have IsRelationInSystemNamespace() for that, and reserve
IsSystemRelation() for the test as to whether it's a bona fide system
catalog.

The big reason that I think we do not want the new behaviour for all is:

* NB: TOAST relations are considered system relations by this test
* for compatibility with the old IsSystemRelationName function.
* This is appropriate in many places but not all. Where it's not,
* also check IsToastRelation.

the current state of things would allow to modify toast relations in
some places :/

I'd suggest renaming the current IsSystemRelation() to your
IsRelationInSystemNamespace() and add IsCatalogRelation() for the new
meaning, so we are sure to break old users.

Let me come up with something like that.

(Maybe the
user-catalog-tables patch will modify this test; I'm not sure, but if
this needs to work differently it seems that it should be conditional
on that, not what schema the table lives in.)

No, they shouldn't change that. We might want to allow such locking
semantics at some points, but that'd be a separate patch.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#155Steve Singer
steve@ssinger.info
In reply to: Andres Freund (#150)
Re: logical changeset generation v6.5

On 11/11/2013 02:06 PM, Andres Freund wrote:

On 2013-11-10 14:45:17 -0500, Steve Singer wrote:

Not really keen - that'd be a noticeable overhead. Note that in the
cases where DEFAULT|INDEX is used, you can just use the new tuple to
extract what you need for the pkey lookup since they now have the same
format and since it's guaranteed that the relevant columns haven't
changed if oldtup is null and there's a key.

What are you actually doing with those columns? Populating a WHERE
clause?

Yup building a WHERE clause

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#156Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#154)
1 attachment(s)
Re: logical changeset generation v6.6

On 2013-11-12 19:24:39 +0100, Andres Freund wrote:

On 2013-11-12 13:18:19 -0500, Robert Haas wrote:

On Tue, Nov 12, 2013 at 12:50 PM, Andres Freund <andres@2ndquadrant.com> wrote:

Completely agreed. As evidenced by the fact that the current change
doesn't update all relevant comments & code. I wonder if we shouldn't
leave the function the current way and just add a new function for the
new behaviour.
The hard thing with that would be coming up with a new
name. IsSystemRelationId() having a different behaviour than
IsSystemRelation() seems strange to me, so just keeping that and
adapting the callers seems wrong to me.
IsInternalRelation()? IsCatalogRelation()?

Well, I went through and looked at the places that were affected by
this and I tend to think that most places will be happier with the new
definition.

I agree that many if not most want the new definition.

If there are call sites that want the existing test, maybe we should
have IsRelationInSystemNamespace() for that, and reserve
IsSystemRelation() for the test as to whether it's a bona fide system
catalog.

The big reason that I think we do not want the new behaviour for all is:

* NB: TOAST relations are considered system relations by this test
* for compatibility with the old IsSystemRelationName function.
* This is appropriate in many places but not all. Where it's not,
* also check IsToastRelation.

the current state of things would allow to modify toast relations in
some places :/

So, I think I found a useful defintion of IsSystemRelation() that fixes
many of the issues with moving relations to pg_catalog: Continue to
treat all pg_toast.* relations as system tables, but only consider
initdb created relations in pg_class.
I've then added IsCatalogRelation() which has a narrower definition of
system relations, namely, it only counts toast tables if they are a
catalog's toast table.

This allows far more actions on user defined relations moved to
pg_catalog. Now they aren't stuck there anymore and can be renamed,
dropped et al. With one curious exception: We still cannot move a
relation out of pg_catalog.
I've included a hunk to allow creation of indexes on relations in
pg_catalog in heap_create(), indexes on catalog relations are prevented
way above, but maybe that should rather be a separate commit.

What do you think?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Don-t-regard-user-defined-relations-in-pg_catalog-as.patchtext/x-patch; charset=us-asciiDownload
>From 91a22bd7fb998b609e73a69b941999596ce4569f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Nov 2013 16:15:16 +0100
Subject: [PATCH] Don't regard user defined relations in pg_catalog as system
 tables.

This allows for a more consistent behaviour of user defined relations
in pg_catalog. As before it's not possible to create relations in
pg_catalog, but now they can, after being moved there, manipulated
normally. Before it was impossible to move or drop user defined
relations in pg_catalog, now that's allowed.

To that end, modify IsSystemRelation/Class() to not regard user
defined tables in pg_catalog as system relations and add
IsCatalogRelation/Class() which don't regard toast relations as
catalog relation unless they are a catalog relation's toast table.

This is also preparation for logical decoding which needs
IsCatalogRelation().
---
 src/backend/access/heap/heapam.c          |  2 +-
 src/backend/catalog/aclchk.c              |  2 +-
 src/backend/catalog/catalog.c             | 69 ++++++++++++++++++++++++++-----
 src/backend/catalog/heap.c                | 11 ++++-
 src/backend/commands/cluster.c            |  2 +-
 src/backend/commands/indexcmds.c          |  5 ++-
 src/backend/commands/tablecmds.c          |  8 ++--
 src/backend/commands/trigger.c            |  2 +-
 src/backend/optimizer/util/plancat.c      |  2 +-
 src/backend/rewrite/rewriteDefine.c       |  4 +-
 src/backend/tcop/utility.c                |  2 +-
 src/include/catalog/catalog.h             |  4 +-
 src/test/regress/expected/alter_table.out | 34 +++++++++++++++
 src/test/regress/sql/alter_table.sql      | 28 +++++++++++++
 14 files changed, 148 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index a21f31b..bbb71c7 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2465,7 +2465,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 	 * because the heaptuples data structure is all in local memory, not in
 	 * the shared buffer.
 	 */
-	if (IsSystemRelation(relation))
+	if (IsCatalogRelation(relation))
 	{
 		for (i = 0; i < ntuples; i++)
 			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 06aa766..5a46fd9 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3628,7 +3628,7 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
 	 * themselves.	ACL_USAGE is if we ever have system sequences.
 	 */
 	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
-		IsSystemClass(classForm) &&
+		IsSystemClass(table_oid, classForm) &&
 		classForm->relkind != RELKIND_VIEW &&
 		!has_rolcatupdate(roleid) &&
 		!allowSystemTableMods)
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 577206c..885ba27 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -102,17 +102,18 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
  *		NB: TOAST relations are considered system relations by this test
  *		for compatibility with the old IsSystemRelationName function.
  *		This is appropriate in many places but not all.  Where it's not,
- *		also check IsToastRelation.
+ *		also check IsToastRelation or use IsCatalogRelation().
  *
- *		We now just test if the relation is in the system catalog namespace;
- *		so it's no longer necessary to forbid user relations from having
- *		names starting with pg_.
+ *		We now just test if the relation is either in the toast namespace, or
+ *		in the system namespace and created during initdb. This way it's no
+ *		longer necessary to forbid user relations from having names starting
+ *		with pg_ and it allows manipulating user defined relations in
+ *		pg_catalog.
  */
 bool
 IsSystemRelation(Relation relation)
 {
-	return IsSystemNamespace(RelationGetNamespace(relation)) ||
-		IsToastNamespace(RelationGetNamespace(relation));
+	return IsSystemClass(RelationGetRelid(relation), relation->rd_rel);
 }
 
 /*
@@ -122,12 +123,60 @@ IsSystemRelation(Relation relation)
  *		search pg_class directly.
  */
 bool
-IsSystemClass(Form_pg_class reltuple)
+IsSystemClass(Oid relid, Form_pg_class reltuple)
 {
-	Oid			relnamespace = reltuple->relnamespace;
+	if (IsToastClass(reltuple))
+		return true;
+	return IsCatalogClass(relid, reltuple);
+}
+
+/*
+ * IsCatalogRelation
+ *		True iff the relation is a system catalog relation, i.e. a relation
+ *		created in pg_catalog by initdb and their corresponding toast
+ *		relations.
+ *
+ *		In contrast to IsSystemRelation() toast relations are *not* included
+ *		in this set unless they are a catalog relation's toast table.
+ */
+bool
+IsCatalogRelation(Relation relation)
+{
+	return IsCatalogClass(RelationGetRelid(relation), relation->rd_rel);
+}
 
-	return IsSystemNamespace(relnamespace) ||
-		IsToastNamespace(relnamespace);
+/*
+ * IsCatalogClass
+ *		True iff the relation is a system catalog relation.
+ *
+ * Check IsCatalogRelation() for details.
+ */
+bool
+IsCatalogClass(Oid relid, Form_pg_class reltuple)
+{
+	Oid         relnamespace = reltuple->relnamespace;
+
+	/*
+	 * Never consider relations outside pg_catalog/pg_toast to be catalog
+	 * relations.
+	 */
+	if (!IsSystemNamespace(relnamespace) && !IsToastNamespace(relnamespace))
+		return false;
+
+	/* ----
+	 * Check whether the oid was assigned during initdb, when creating the
+	 * initial template database. Minus the relations in information_schema
+	 * excluded above, these are integral part of the system.
+	 * We could instead check whether the relation is pinned in pg_depend, but
+	 * this is noticeably cheaper and doesn't require catalog access.
+	 *
+	 * This test is safe since even a oid wraparound will preserve this
+	 * property (c.f. GetNewObjectId()) and it has the advantage that it works
+	 * correctly even if a user decides to create a relation in the pg_catalog
+	 * namespace.
+	 * ----
+	 */
+	return relid < FirstNormalObjectId;
 }
 
 /*
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a910f81..6f2e142 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -256,10 +256,17 @@ heap_create(const char *relname,
 	Assert(OidIsValid(relid));
 
 	/*
-	 * sanity checks
+	 * Don't allow creating relations in pg_catalog directly, even though it
+	 * is allowed to move user defined relations there. Semantics with search
+	 * paths including pg_catalog are too confusing for now.
+	 *
+	 * But allow creating indexes on relations in pg_catalog even if
+	 * allow_system_table_mods = off, upper layers already guarantee it's on a
+	 * user defined relation, not a system one.
 	 */
 	if (!allow_system_table_mods &&
-		(IsSystemNamespace(relnamespace) || IsToastNamespace(relnamespace)) &&
+		((IsSystemNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
+		 IsToastNamespace(relnamespace)) &&
 		IsNormalProcessingMode())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index f6a5bfe..afc6a78 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1354,7 +1354,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 			 * ones the dependency changes would change.  It's too late to be
 			 * making any data changes to the target catalog.
 			 */
-			if (IsSystemClass(relform1))
+			if (IsSystemClass(r1, relform1))
 				elog(ERROR, "cannot swap toast files by links for system catalogs");
 
 			/* Delete old dependencies */
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2155252..9b97e44 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1824,6 +1824,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
+		Oid			relid = HeapTupleGetOid(tuple);
 
 		if (classtuple->relkind != RELKIND_RELATION &&
 			classtuple->relkind != RELKIND_MATVIEW)
@@ -1835,7 +1836,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
 			continue;
 
 		/* Check user/system classification, and optionally skip */
-		if (IsSystemClass(classtuple))
+		if (IsSystemClass(relid, classtuple))
 		{
 			if (!do_system)
 				continue;
@@ -1850,7 +1851,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
 			continue;			/* got it already */
 
 		old = MemoryContextSwitchTo(private_context);
-		relids = lappend_oid(relids, HeapTupleGetOid(tuple));
+		relids = lappend_oid(relids, relid);
 		MemoryContextSwitchTo(old);
 	}
 	heap_endscan(scan);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0b31f55..92dd505 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -910,7 +910,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
 					   rel->relname);
 
-	if (!allowSystemTableMods && IsSystemClass(classform))
+	if (!allowSystemTableMods && IsSystemClass(relOid, classform))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
@@ -1248,7 +1248,7 @@ truncate_check_rel(Relation rel)
 		aclcheck_error(aclresult, ACL_KIND_CLASS,
 					   RelationGetRelationName(rel));
 
-	if (!allowSystemTableMods && IsSystemRelation(rel))
+	if (!allowSystemTableMods && IsCatalogRelation(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
@@ -2104,7 +2104,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	if (!pg_class_ownercheck(myrelid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
 					   NameStr(classform->relname));
-	if (!allowSystemTableMods && IsSystemClass(classform))
+	if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
@@ -10870,7 +10870,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
 
 	/* No system table modifications unless explicitly allowed. */
-	if (!allowSystemTableMods && IsSystemClass(classform))
+	if (!allowSystemTableMods && IsSystemClass(relid, classform))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d86e9ad..0008fc6 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1174,7 +1174,7 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 	/* you must own the table to rename one of its triggers */
 	if (!pg_class_ownercheck(relid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
-	if (!allowSystemTableMods && IsSystemClass(form))
+	if (!allowSystemTableMods && IsSystemClass(relid, form))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 954666c..de981cb 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -128,7 +128,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	 * Don't bother with indexes for an inheritance parent, either.
 	 */
 	if (inhparent ||
-		(IgnoreSystemIndexes && IsSystemClass(relation->rd_rel)))
+		(IgnoreSystemIndexes && IsSystemRelation(relation)))
 		hasindex = false;
 	else
 		hasindex = relation->rd_rel->relhasindex;
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 2eca531..3641d66 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -265,7 +265,7 @@ DefineQueryRewrite(char *rulename,
 				 errmsg("\"%s\" is not a table or view",
 						RelationGetRelationName(event_relation))));
 
-	if (!allowSystemTableMods && IsSystemRelation(event_relation))
+	if (!allowSystemTableMods && IsCatalogRelation(event_relation))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
@@ -858,7 +858,7 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or view", rv->relname)));
 
-	if (!allowSystemTableMods && IsSystemClass(form))
+	if (!allowSystemTableMods && IsSystemClass(relid, form))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6a7bf0d..939d761 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -110,7 +110,7 @@ CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
 	if (noCatalogs)
 	{
 		if (!allowSystemTableMods &&
-			IsSystemClass((Form_pg_class) GETSTRUCT(tuple)))
+			IsSystemClass(relOid, (Form_pg_class) GETSTRUCT(tuple)))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("permission denied: \"%s\" is a system catalog",
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index 44b6f38..493493f 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -25,9 +25,11 @@ extern char *GetDatabasePath(Oid dbNode, Oid spcNode);
 
 extern bool IsSystemRelation(Relation relation);
 extern bool IsToastRelation(Relation relation);
+extern bool IsCatalogRelation(Relation relation);
 
-extern bool IsSystemClass(Form_pg_class reltuple);
+extern bool IsSystemClass(Oid relid, Form_pg_class reltuple);
 extern bool IsToastClass(Form_pg_class reltuple);
+extern bool IsCatalogClass(Oid relid, Form_pg_class reltuple);
 
 extern bool IsSystemNamespace(Oid namespaceId);
 extern bool IsToastNamespace(Oid namespaceId);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 7366392..232a233 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2337,3 +2337,37 @@ FROM (
                   0 | t
 (1 row)
 
+-- Checks on creating and manipulation of user defined relations in
+-- pg_catalog.
+--
+-- XXX: It would be useful to add checks around trying to manipulate
+-- catalog tables, but that might have ugly consequences when run
+-- against an existing server with allow_system_table_mods = on.
+SHOW allow_system_table_mods;
+ allow_system_table_mods 
+-------------------------
+ off
+(1 row)
+
+-- disallowed because of search_path issues with pg_dump
+CREATE TABLE pg_catalog.new_system_table();
+ERROR:  permission denied to create "pg_catalog.new_system_table"
+DETAIL:  System catalog modifications are currently disallowed.
+-- instead create in public first, move to catalog
+CREATE TABLE new_system_table(id serial primary key, othercol text);
+ALTER TABLE new_system_table SET SCHEMA pg_catalog;
+-- XXX: it's currently impossible to move relations out of pg_catalog
+ALTER TABLE new_system_table SET SCHEMA public;
+ERROR:  cannot remove dependency on schema pg_catalog because it is a system object
+-- move back, will currently error out, already there
+ALTER TABLE new_system_table SET SCHEMA pg_catalog;
+ERROR:  table new_system_table is already in schema "pg_catalog"
+ALTER TABLE new_system_table RENAME TO old_system_table;
+CREATE INDEX old_system_table__othercol ON old_system_table (othercol);
+INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata');
+UPDATE old_system_table SET id = -id;
+DELETE FROM old_system_table WHERE othercol = 'somedata';
+TRUNCATE old_system_table;
+ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
+ALTER TABLE old_system_table DROP COLUMN othercol;
+DROP TABLE old_system_table;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 0d3b79b..b555430 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1567,3 +1567,31 @@ FROM (
     FROM pg_class
     WHERE relkind IN ('r', 'i', 'S', 't', 'm')
     ) mapped;
+
+-- Checks on creating and manipulation of user defined relations in
+-- pg_catalog.
+--
+-- XXX: It would be useful to add checks around trying to manipulate
+-- catalog tables, but that might have ugly consequences when run
+-- against an existing server with allow_system_table_mods = on.
+
+SHOW allow_system_table_mods;
+-- disallowed because of search_path issues with pg_dump
+CREATE TABLE pg_catalog.new_system_table();
+-- instead create in public first, move to catalog
+CREATE TABLE new_system_table(id serial primary key, othercol text);
+ALTER TABLE new_system_table SET SCHEMA pg_catalog;
+
+-- XXX: it's currently impossible to move relations out of pg_catalog
+ALTER TABLE new_system_table SET SCHEMA public;
+-- move back, will currently error out, already there
+ALTER TABLE new_system_table SET SCHEMA pg_catalog;
+ALTER TABLE new_system_table RENAME TO old_system_table;
+CREATE INDEX old_system_table__othercol ON old_system_table (othercol);
+INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata');
+UPDATE old_system_table SET id = -id;
+DELETE FROM old_system_table WHERE othercol = 'somedata';
+TRUNCATE old_system_table;
+ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
+ALTER TABLE old_system_table DROP COLUMN othercol;
+DROP TABLE old_system_table;
-- 
1.8.5.rc1.dirty

#157Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#143)
Re: logical changeset generation v6.5

On 11/9/13, 5:56 AM, Andres Freund wrote:

ISTM ecpg's regression tests should be built (not run!) during
$(recurse) not just during make check.

Actually, I did just the opposite change some years ago. The rationale
is, the build builds that which you want to install.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#158Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#152)
14 attachment(s)
Re: logical changeset generation v6.7

On 2013-11-12 18:50:33 +0100, Andres Freund wrote:

You've actually changed the meaning of this section (and not in a good way):

be set at server start. <varname>wal_level</> must be set
-        to <literal>archive</> or <literal>hot_standby</> to allow
-        connections from standby servers.
+        to <literal>archive</>, <literal>hot_standby</> or <literal>logical</>
+        to allow connections from standby servers.

I think that the previous text meant that you needed archive - or, if
you want to allow connections, hot_standby. The new text loses that
nuance.

Yea, that's because it was lost on me in the first place...

I think that's because the nuance isn't actually in the text - note that
it is talking about max_wal_senders and talking about connections
*from*, not *to* standby servers.
I've reformulated the wal_level paragraph and used "or higher" in
several places now.

Ok, so here's a rebased version of this. I tried to fix all the issues
you mentioned, and it's based on the split off IsSystemRelation() patch,
I've sent yesterday (included here).

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Don-t-regard-user-defined-relations-in-pg_catalog-as.patch.gzapplication/x-patch-gzipDownload
0002-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Add-option-to-use-user-defined-tables-a.patch.gzapplication/x-patch-gzipDownload
0005-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
0006-wal_decoding-Implement-VACUUM-FULL-CLUSTER-support-v.patch.gzapplication/x-patch-gzipDownload
0007-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0011-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0012-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
0013-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
0014-wal_decoding-Temporarily-add-logical-decoding-regres.patch.gzapplication/x-patch-gzipDownload
#159Fabrízio de Royes Mello
fabriziomello@gmail.com
In reply to: Andres Freund (#158)
Re: logical changeset generation v6.7

On Thu, Nov 14, 2013 at 11:46 AM, Andres Freund <andres@2ndquadrant.com>
wrote:

On 2013-11-12 18:50:33 +0100, Andres Freund wrote:

You've actually changed the meaning of this section (and not in a

good way):

be set at server start. <varname>wal_level</> must be set
-        to <literal>archive</> or <literal>hot_standby</> to allow
-        connections from standby servers.
+        to <literal>archive</>, <literal>hot_standby</> or

<literal>logical</>

+ to allow connections from standby servers.

I think that the previous text meant that you needed archive - or, if
you want to allow connections, hot_standby. The new text loses that
nuance.

Yea, that's because it was lost on me in the first place...

I think that's because the nuance isn't actually in the text - note that
it is talking about max_wal_senders and talking about connections
*from*, not *to* standby servers.
I've reformulated the wal_level paragraph and used "or higher" in
several places now.

Ok, so here's a rebased version of this. I tried to fix all the issues
you mentioned, and it's based on the split off IsSystemRelation() patch,
I've sent yesterday (included here).

Hello,

I'm trying to apply the patches but show some warnings/errors:

$ gunzip -c
/home/fabrizio/Downloads/0002-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gz
| git apply -
warning: src/backend/access/transam/xlog.c has type 100755, expected 100644

$ gunzip -c
/home/fabrizio/Downloads/0005-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gz
| git apply -
warning: src/backend/access/transam/xlog.c has type 100755, expected 100644

$ gunzip -c
/home/fabrizio/Downloads/0006-wal_decoding-Implement-VACUUM-FULL-CLUSTER-support-v.patch.gz
| git apply -
warning: src/backend/access/transam/xlog.c has type 100755, expected 100644

$ gunzip -c
/home/fabrizio/Downloads/0007-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gz
| git apply -
warning: src/backend/access/transam/xlog.c has type 100755, expected 100644

$ gunzip -c
/home/fabrizio/Downloads/0011-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gz
| git apply -
error: patch failed: src/bin/pg_basebackup/streamutil.c:210
error: src/bin/pg_basebackup/streamutil.c: patch does not apply

The others are applied correctly. The permission warning must be fixed and
0011 bust be rebased.

Regards,

--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL

Show quoted text

Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello

#160Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#158)
Re: logical changeset generation v6.7

On Thu, Nov 14, 2013 at 8:46 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-11-12 18:50:33 +0100, Andres Freund wrote:

You've actually changed the meaning of this section (and not in a good way):

be set at server start. <varname>wal_level</> must be set
-        to <literal>archive</> or <literal>hot_standby</> to allow
-        connections from standby servers.
+        to <literal>archive</>, <literal>hot_standby</> or <literal>logical</>
+        to allow connections from standby servers.

I think that the previous text meant that you needed archive - or, if
you want to allow connections, hot_standby. The new text loses that
nuance.

Yea, that's because it was lost on me in the first place...

I think that's because the nuance isn't actually in the text - note that
it is talking about max_wal_senders and talking about connections
*from*, not *to* standby servers.
I've reformulated the wal_level paragraph and used "or higher" in
several places now.

Ok, so here's a rebased version of this. I tried to fix all the issues
you mentioned, and it's based on the split off IsSystemRelation() patch,
I've sent yesterday (included here).

OK, I've committed the patch to adjust the definition of
IsSystemRelation()/IsSystemClass() and add
IsCatalogRelation()/IsCatalogClass(). I kibitzed your decision about
which function to use in a few places - specifically, I made all of
the places that cared about allow_system_table_mods uses the IsSystem
functions, and all the places that cared about invalidation messages
use the IsCatalog functions. I don't think any of these changes are
more cosmetic, but I think it may reduce the chance of errors or
inconsistencies in the face of future changes.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#161Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#158)
1 attachment(s)
Re: logical changeset generation v6.7

On Thu, Nov 14, 2013 at 8:46 AM, Andres Freund <andres@2ndquadrant.com> wrote:

[ new patches ]

Here's an updated version of patch #2. I didn't really like the
approach you took in the documentation, so I revised it.

Apart from that, I spent a lot of time looking at
HeapSatisfiesHOTandKeyUpdate. I'm not very happy with your changes.
The idea seems to be that we'll iterate through all of the HOT columns
regardless, but that might be very inefficient. Suppose there are 100
HOT columns, the last one is the only key column, and only the first
one has been modified. Once we look at #1 and determine that it's not
HOT, we should zoom forward and skip over the next 98, and only look
at the last one; your version does not behave like that.

I think there's also some confusion in your version about what ends up
in the attnum values: they're normally adjusted by
FirstLowInvalidHeapAttributeNumber, but when bms_first_member returns
-1 then they're not. But that's not a great thing, because -1 is
actually a valid attribute number. I've taken a crack at rewriting
this logic, and the result looks cleaner and simpler to me, but I
haven't tested it beyond the fact that it passes make check. See what
you think.

I haven't completely reviewed every bit of this in depth yet, but it's
1:15am, so I'm going to post what I have and throw in the towel for
tonight.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

logical-logging-rmh.patchapplication/octet-stream; name=logical-logging-rmh.patchDownload
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 1712974..5040e99 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -587,7 +587,7 @@ tar -cf backup.tar /usr/local/pgsql/data
 
    <para>
     To enable WAL archiving, set the <xref linkend="guc-wal-level">
-    configuration parameter to <literal>archive</> (or <literal>hot_standby</>),
+    configuration parameter to <literal>archive</> or higher,
     <xref linkend="guc-archive-mode"> to <literal>on</>,
     and specify the shell command to use in the <xref
     linkend="guc-archive-command"> configuration parameter.  In practice
@@ -1251,7 +1251,7 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
       If more flexibility in copying the backup files is needed, a lower
       level process can be used for standalone hot backups as well.
       To prepare for low level standalone hot backups, set <varname>wal_level</> to
-      <literal>archive</> (or <literal>hot_standby</>), <varname>archive_mode</> to
+      <literal>archive</> or higher, <varname>archive_mode</> to
       <literal>on</>, and set up an <varname>archive_command</> that performs
       archiving only when a <emphasis>switch file</> exists.  For example:
 <programlisting>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 77a9303..3830ef0 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1606,10 +1606,12 @@ include 'filename'
         <varname>wal_level</> determines how much information is written
         to the WAL. The default value is <literal>minimal</>, which writes
         only the information needed to recover from a crash or immediate
-        shutdown. <literal>archive</> adds logging required for WAL archiving,
-        and <literal>hot_standby</> further adds information required to run
-        read-only queries on a standby server.
-        This parameter can only be set at server start.
+        shutdown. <literal>archive</> adds logging required for WAL archiving;
+        <literal>hot_standby</> further adds information required to run
+        read-only queries on a standby server; and, finally
+        <literal>logical</> adds information necessary to support logical
+        decoding.  Each level includes the information logged at all lower
+        levels.  This parameter can only be set at server start.
        </para>
        <para>
         In <literal>minimal</> level, WAL-logging of some bulk
@@ -1623,24 +1625,30 @@ include 'filename'
          <member><command>COPY</> into tables that were created or truncated in the same
          transaction</member>
         </simplelist>
-        But minimal WAL does not contain
-        enough information to reconstruct the data from a base backup and the
-        WAL logs, so either <literal>archive</> or <literal>hot_standby</>
-        level must be used to enable
-        WAL archiving (<xref linkend="guc-archive-mode">) and streaming
-        replication.
+        But minimal WAL does not contain enough information to reconstruct the
+        data from a base backup and the WAL logs, so <literal>archive</> or
+        higher must be used to enable WAL archiving
+        (<xref linkend="guc-archive-mode">) and streaming replication.
        </para>
        <para>
         In <literal>hot_standby</> level, the same information is logged as
         with <literal>archive</>, plus information needed to reconstruct
         the status of running transactions from the WAL. To enable read-only
         queries on a standby server, <varname>wal_level</> must be set to
-        <literal>hot_standby</> on the primary, and
+        <literal>hot_standby</> or higher on the primary, and
         <xref linkend="guc-hot-standby"> must be enabled in the standby. It is
-        thought that there is
-        little measurable difference in performance between using
-        <literal>hot_standby</> and <literal>archive</> levels, so feedback
-        is welcome if any production impacts are noticeable.
+        thought that there is little measurable difference in performance
+        between using <literal>hot_standby</> and <literal>archive</> levels,
+        so feedback is welcome if any production impacts are noticeable.
+       </para>
+       <para>
+        In <literal>logical</> level, the same information is logged as
+        with <literal>hot_standby</>, plus information needed to allow
+        extracting logical changesets from the WAL. Using a level of
+        <literal>logical</> will increase the WAL volume, particularly if many
+        tables are configured for <literal>REPLICA IDENTITY FULL</literal> and
+        many <command>UPDATE</> and <command>DELETE</> statements are
+        executed.
        </para>
       </listitem>
      </varlistentry>
@@ -2197,9 +2205,9 @@ include 'filename'
         disabled. WAL sender processes count towards the total number
         of connections, so the parameter cannot be set higher than
         <xref linkend="guc-max-connections">.  This parameter can only
-        be set at server start. <varname>wal_level</> must be set
-        to <literal>archive</> or <literal>hot_standby</> to allow
-        connections from standby servers.
+        be set at server start. <varname>wal_level</> must be set to
+        <literal>archive</> or higher to allow connections from standby
+        servers.
        </para>
        </listitem>
       </varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index c8f6fa8..e2e5ac9 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1861,8 +1861,9 @@ LOG:  database system is ready to accept read only connections
     Consistency information is recorded once per checkpoint on the primary.
     It is not possible to enable hot standby when reading WAL
     written during a period when <varname>wal_level</> was not set to
-    <literal>hot_standby</> on the primary.  Reaching a consistent state can
-    also be delayed in the presence of both of these conditions:
+    <literal>hot_standby</> or <literal>logical</> on the primary.  Reaching
+    a consistent state can also be delayed in the presence of both of these
+    conditions:
 
       <itemizedlist>
        <listitem>
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 31496a3..7b3a32e 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -85,12 +85,14 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
-				HeapTuple newtup, bool all_visible_cleared,
-				bool new_all_visible_cleared);
+				HeapTuple newtup, HeapTuple old_key_tup,
+				bool all_visible_cleared, bool new_all_visible_cleared);
 static void HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
-							 bool *satisfies_hot, bool *satisfies_key,
-							 HeapTuple oldtup, HeapTuple newtup);
+						  Bitmapset *hot_attrs,
+						  Bitmapset *key_attrs, Bitmapset *id_attrs,
+						  bool *satisfies_hot, bool *satisfies_key,
+						  bool *satisfies_id,
+						  HeapTuple oldtup, HeapTuple newtup);
 static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -108,6 +110,8 @@ static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static bool ConditionalMultiXactIdWait(MultiXactId multi,
 						   MultiXactStatus status, int *remaining,
 						   uint16 infomask);
+static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool *copy);
 
 
 /*
@@ -2103,11 +2107,24 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		xl_heap_insert xlrec;
 		xl_heap_header xlhdr;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[3];
+		XLogRecData rdata[4];
 		Page		page = BufferGetPage(buffer);
 		uint8		info = XLOG_HEAP_INSERT;
+		bool		need_tuple_data;
+
+		/*
+		 * For logical decoding, we need the tuple even if we're doing a
+		 * full page write, so make sure to log it separately. (XXX We could
+		 * alternatively store a pointer into the FPW).
+		 *
+		 * Also, if this is a catalog, we need to transmit combocids to
+		 * properly decode, so log that as well.
+		 */
+		need_tuple_data = RelationIsLogicallyLogged(relation);
+		if (RelationIsAccessibleInLogicalDecoding(relation))
+			log_heap_new_cid(relation, heaptup);
 
-		xlrec.all_visible_cleared = all_visible_cleared;
+		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.target.node = relation->rd_node;
 		xlrec.target.tid = heaptup->t_self;
 		rdata[0].data = (char *) &xlrec;
@@ -2126,18 +2143,36 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 */
 		rdata[1].data = (char *) &xlhdr;
 		rdata[1].len = SizeOfHeapHeader;
-		rdata[1].buffer = buffer;
+		rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;
 		rdata[1].buffer_std = true;
 		rdata[1].next = &(rdata[2]);
 
 		/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
 		rdata[2].data = (char *) heaptup->t_data + offsetof(HeapTupleHeaderData, t_bits);
 		rdata[2].len = heaptup->t_len - offsetof(HeapTupleHeaderData, t_bits);
-		rdata[2].buffer = buffer;
+		rdata[2].buffer = need_tuple_data ? InvalidBuffer : buffer;
 		rdata[2].buffer_std = true;
 		rdata[2].next = NULL;
 
 		/*
+		 * Make a separate rdata entry for the tuple's buffer if we're
+		 * doing logical decoding, so that an eventual FPW doesn't
+		 * remove the tuple's data.
+		 */
+		if (need_tuple_data)
+		{
+			rdata[2].next = &(rdata[3]);
+
+			rdata[3].data = NULL;
+			rdata[3].len = 0;
+			rdata[3].buffer = buffer;
+			rdata[3].buffer_std = true;
+			rdata[3].next = NULL;
+
+			xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+		}
+
+		/*
 		 * If this is the single and first tuple on page, we can reinit the
 		 * page instead of restoring the whole thing.  Set flag, and hide
 		 * buffer references from XLogInsert.
@@ -2146,7 +2181,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 		{
 			info |= XLOG_HEAP_INIT_PAGE;
-			rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
+			rdata[1].buffer = rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
 		}
 
 		recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -2272,6 +2307,8 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 	Page		page;
 	bool		needwal;
 	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
 
 	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
 	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
@@ -2358,7 +2395,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 		{
 			XLogRecPtr	recptr;
 			xl_heap_multi_insert *xlrec;
-			XLogRecData rdata[2];
+			XLogRecData rdata[3];
 			uint8		info = XLOG_HEAP2_MULTI_INSERT;
 			char	   *tupledata;
 			int			totaldatalen;
@@ -2388,7 +2425,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			/* the rest of the scratch space is used for tuple data */
 			tupledata = scratchptr;
 
-			xlrec->all_visible_cleared = all_visible_cleared;
+			xlrec->flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 			xlrec->node = relation->rd_node;
 			xlrec->blkno = BufferGetBlockNumber(buffer);
 			xlrec->ntuples = nthispage;
@@ -2420,6 +2457,13 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 					   datalen);
 				tuphdr->datalen = datalen;
 				scratchptr += datalen;
+
+				/*
+				 * We don't use heap_multi_insert for catalog tuples yet, but
+				 * better be prepared...
+				 */
+				if (need_cids)
+					log_heap_new_cid(relation, heaptup);
 			}
 			totaldatalen = scratchptr - tupledata;
 			Assert((scratchptr - scratch) < BLCKSZ);
@@ -2431,17 +2475,34 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 
 			rdata[1].data = tupledata;
 			rdata[1].len = totaldatalen;
-			rdata[1].buffer = buffer;
+			rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;
 			rdata[1].buffer_std = true;
 			rdata[1].next = NULL;
 
 			/*
+			 * Make a separate rdata entry for the tuple's buffer if
+			 * we're doing logical decoding, so that an eventual FPW
+			 * doesn't remove the tuple's data.
+			 */
+			if (need_tuple_data)
+			{
+				rdata[1].next = &(rdata[2]);
+
+				rdata[2].data = NULL;
+				rdata[2].len = 0;
+				rdata[2].buffer = buffer;
+				rdata[2].buffer_std = true;
+				rdata[2].next = NULL;
+				xlrec->flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+			}
+
+			/*
 			 * If we're going to reinitialize the whole page using the WAL
 			 * record, hide buffer reference from XLogInsert.
 			 */
 			if (init)
 			{
-				rdata[1].buffer = InvalidBuffer;
+				rdata[1].buffer = rdata[2].buffer = InvalidBuffer;
 				info |= XLOG_HEAP_INIT_PAGE;
 			}
 
@@ -2561,6 +2622,8 @@ heap_delete(Relation relation, ItemPointer tid,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL; /* replica identity of the tuple */
+	bool		old_key_copied = false;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -2734,6 +2797,12 @@ l1:
 	/* replace cid with a combo cid if necessary */
 	HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
 
+	/*
+	 * Compute replica identity tuple before entering the critical section so
+	 * we don't PANIC upon a memory allocation failure.
+	 */
+	old_key_tuple = ExtractReplicaIdentity(relation, &tp, &old_key_copied);
+
 	START_CRIT_SECTION();
 
 	/*
@@ -2786,9 +2855,13 @@ l1:
 	{
 		xl_heap_delete xlrec;
 		XLogRecPtr	recptr;
-		XLogRecData rdata[2];
+		XLogRecData rdata[4];
 
-		xlrec.all_visible_cleared = all_visible_cleared;
+		/* For logical decode we need combocids to properly decode the catalog */
+		if (RelationIsAccessibleInLogicalDecoding(relation))
+			log_heap_new_cid(relation, &tp);
+
+		xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
 		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
 											  tp.t_data->t_infomask2);
 		xlrec.target.node = relation->rd_node;
@@ -2805,6 +2878,37 @@ l1:
 		rdata[1].buffer_std = true;
 		rdata[1].next = NULL;
 
+		/*
+		 * Log replica identity of the deleted tuple if there is one
+		 */
+		if (old_key_tuple != NULL)
+		{
+			xl_heap_header xlhdr;
+
+			xlhdr.t_infomask2 = old_key_tuple->t_data->t_infomask2;
+			xlhdr.t_infomask = old_key_tuple->t_data->t_infomask;
+			xlhdr.t_hoff = old_key_tuple->t_data->t_hoff;
+
+			rdata[1].next = &(rdata[2]);
+			rdata[2].data = (char*)&xlhdr;
+			rdata[2].len = SizeOfHeapHeader;
+			rdata[2].buffer = InvalidBuffer;
+			rdata[2].next = NULL;
+
+			rdata[2].next = &(rdata[3]);
+			rdata[3].data = (char *) old_key_tuple->t_data
+				+ offsetof(HeapTupleHeaderData, t_bits);
+			rdata[3].len = old_key_tuple->t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+			rdata[3].buffer = InvalidBuffer;
+			rdata[3].next = NULL;
+
+			if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
+				xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_TUPLE;
+			else
+				xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+		}
+
 		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE, rdata);
 
 		PageSetLSN(page, recptr);
@@ -2850,6 +2954,9 @@ l1:
 
 	pgstat_count_heap_delete(relation);
 
+	if (old_key_tuple != NULL && old_key_copied)
+		heap_freetuple(old_key_tuple);
+
 	return HeapTupleMayBeUpdated;
 }
 
@@ -2934,9 +3041,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
+	Bitmapset  *id_attrs;
 	ItemId		lp;
 	HeapTupleData oldtup;
 	HeapTuple	heaptup;
+	HeapTuple	old_key_tuple = NULL;
+	bool		old_key_copied = false;
 	Page		page;
 	BlockNumber block;
 	MultiXactStatus mxact_status;
@@ -2952,6 +3062,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		iscombo;
 	bool		satisfies_hot;
 	bool		satisfies_key;
+	bool		satisfies_id;
 	bool		use_hot_update = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
@@ -2979,8 +3090,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * Note that we get a copy here, so we need not worry about relcache flush
 	 * happening midway through.
 	 */
-	hot_attrs = RelationGetIndexAttrBitmap(relation, false);
-	key_attrs = RelationGetIndexAttrBitmap(relation, true);
+	hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
+	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
+	id_attrs = RelationGetIndexAttrBitmap(relation,
+										   INDEX_ATTR_BITMAP_IDENTITY_KEY);
 
 	block = ItemPointerGetBlockNumber(otid);
 	buffer = ReadBuffer(relation, block);
@@ -3038,9 +3151,9 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 * is updates that don't manipulate key columns, not those that
 	 * serendipitiously arrive at the same key values.
 	 */
-	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs,
+	HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, id_attrs,
 								 &satisfies_hot, &satisfies_key,
-								 &oldtup, newtup);
+								 &satisfies_id, &oldtup, newtup);
 	if (satisfies_key)
 	{
 		*lockmode = LockTupleNoKeyExclusive;
@@ -3510,6 +3623,17 @@ l2:
 		PageSetFull(page);
 	}
 
+	/*
+	 * Compute replica identity tuple before entering the critical section so
+	 * we don't PANIC upon a memory allocation failure if either we're
+	 * configured to always log the full old tuple or if one of the key columns
+	 * changed.
+	 */
+	if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
+		!satisfies_id)
+		old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
+											   &old_key_copied);
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -3585,11 +3709,23 @@ l2:
 	/* XLOG stuff */
 	if (RelationNeedsWAL(relation))
 	{
-		XLogRecPtr	recptr = log_heap_update(relation, buffer,
-											 newbuf, &oldtup, heaptup,
-											 all_visible_cleared,
-											 all_visible_cleared_new);
+		XLogRecPtr	recptr;
+
+		/*
+		 * For logical decoding we need combocids to properly decode the
+		 * catalog.
+		 */
+		if (RelationIsAccessibleInLogicalDecoding(relation))
+		{
+			log_heap_new_cid(relation, &oldtup);
+			log_heap_new_cid(relation, heaptup);
+		}
 
+		recptr = log_heap_update(relation, buffer,
+								 newbuf, &oldtup, heaptup,
+								 old_key_tuple,
+								 all_visible_cleared,
+								 all_visible_cleared_new);
 		if (newbuf != buffer)
 		{
 			PageSetLSN(BufferGetPage(newbuf), recptr);
@@ -3640,6 +3776,9 @@ l2:
 		heap_freetuple(heaptup);
 	}
 
+	if (old_key_tuple != NULL && old_key_copied)
+		heap_freetuple(old_key_tuple);
+
 	bms_free(hot_attrs);
 	bms_free(key_attrs);
 
@@ -3727,63 +3866,72 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
 /*
  * Check which columns are being updated.
  *
- * This simultaneously checks conditions for HOT updates and for FOR KEY
- * SHARE updates.  Since much of the time they will be checking very similar
- * sets of columns, and doing the same tests on them, it makes sense to
- * optimize and do them together.
+ * This simultaneously checks conditions for HOT updates, for FOR KEY
+ * SHARE updates, and REPLICA IDENTITY concerns.  Since much of the time they
+ * will be checking very similar sets of columns, and doing the same tests on
+ * them, it makes sense to optimize and do them together.
  *
- * We receive two bitmapsets comprising the two sets of columns we're
+ * We receive three bitmapsets comprising the three sets of columns we're
  * interested in.  Note these are destructively modified; that is OK since
  * this is invoked at most once in heap_update.
  *
  * hot_result is set to TRUE if it's okay to do a HOT update (i.e. it does not
  * modified indexed columns); key_result is set to TRUE if the update does not
- * modify columns used in the key.
+ * modify columns used in the key; id_result is set to TRUE if the update does
+ * not modify columns in any index marked as the REPLICA IDENTITY.
  */
 static void
-HeapSatisfiesHOTandKeyUpdate(Relation relation,
-							 Bitmapset *hot_attrs, Bitmapset *key_attrs,
+HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs,
+							 Bitmapset *key_attrs, Bitmapset *id_attrs,
 							 bool *satisfies_hot, bool *satisfies_key,
+							 bool *satisfies_id,
 							 HeapTuple oldtup, HeapTuple newtup)
 {
 	int			next_hot_attnum;
 	int			next_key_attnum;
+	int			next_id_attnum;
 	bool		hot_result = true;
 	bool		key_result = true;
-	bool		key_done = false;
-	bool		hot_done = false;
+	bool		id_result = true;
 
-	next_hot_attnum = bms_first_member(hot_attrs);
-	if (next_hot_attnum == -1)
-		hot_done = true;
-	else
-		/* Adjust for system attributes */
-		next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
+	/* If REPLICA IDENTITY is set to FULL, id_attrs will be empty. */
+	Assert(bms_is_subset(id_attrs, key_attrs));
+	Assert(bms_is_subset(key_attrs, hot_attrs));
 
+	/*
+	 * If one of these sets contains no remaining bits, bms_first_member
+	 * will return -1, and after adding FirstLowInvalidHeapAttributeNumber
+	 * we'll get an attribute number that can't possibly be real, and thus
+	 * won't match any actual attribute number.
+	 */
+	next_hot_attnum = bms_first_member(hot_attrs);
+	next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
 	next_key_attnum = bms_first_member(key_attrs);
-	if (next_key_attnum == -1)
-		key_done = true;
-	else
-		/* Adjust for system attributes */
-		next_key_attnum += FirstLowInvalidHeapAttributeNumber;
+	next_key_attnum += FirstLowInvalidHeapAttributeNumber;
+	next_id_attnum = bms_first_member(id_attrs);
+	next_id_attnum += FirstLowInvalidHeapAttributeNumber;
 
 	for (;;)
 	{
-		int			check_now;
 		bool		changed;
+		int			check_now;
 
-		/* both bitmapsets are now empty */
-		if (key_done && hot_done)
-			break;
-
-		/* XXX there's probably an easier way ... */
-		if (hot_done)
-			check_now = next_key_attnum;
-		if (key_done)
+		/*
+		 * Since the HOT attributes are a superset of the key attributes and
+		 * the key attributes are a superset of the id attributes, this logic
+		 * is guaranteed to identify the next column that needs to be
+		 * checked.
+		 */
+		if (next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
 			check_now = next_hot_attnum;
+		else if (next_key_attnum > FirstLowInvalidHeapAttributeNumber)
+			check_now = next_key_attnum;
+		else if (next_id_attnum > FirstLowInvalidHeapAttributeNumber)
+			check_now = next_id_attnum;
 		else
-			check_now = Min(next_hot_attnum, next_key_attnum);
+			break;
 
+		/* See whether it changed. */
 		changed = !heap_tuple_attr_equals(RelationGetDescr(relation),
 										  check_now, oldtup, newtup);
 		if (changed)
@@ -3792,34 +3940,42 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation,
 				hot_result = false;
 			if (check_now == next_key_attnum)
 				key_result = false;
-		}
+			if (check_now == next_id_attnum)
+				id_result = false;
 
-		/* if both are false now, we can stop checking */
-		if (!hot_result && !key_result)
-			break;
+			/* if all are false now, we can stop checking */
+			if (!hot_result && !key_result && !id_result)
+				break;
+		}
 
+		/*
+		 * Advance the next attribute numbers for the sets that contain
+		 * the attribute we just checked.  As we work our way through the
+		 * columns, the next_attnum values will rise; but when each set
+		 * becomes empty, bms_first_member() will return -1 and the attribute
+		 * number will end up with a value less than
+		 * FirstLowInvalidHeapAttributeNumber.
+		 */
 		if (check_now == next_hot_attnum)
 		{
 			next_hot_attnum = bms_first_member(hot_attrs);
-			if (next_hot_attnum == -1)
-				hot_done = true;
-			else
-				/* Adjust for system attributes */
-				next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
+			next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
 		}
 		if (check_now == next_key_attnum)
 		{
 			next_key_attnum = bms_first_member(key_attrs);
-			if (next_key_attnum == -1)
-				key_done = true;
-			else
-				/* Adjust for system attributes */
-				next_key_attnum += FirstLowInvalidHeapAttributeNumber;
+			next_key_attnum += FirstLowInvalidHeapAttributeNumber;
+		}
+		if (check_now == next_id_attnum)
+		{
+			next_id_attnum = bms_first_member(id_attrs);
+			next_id_attnum += FirstLowInvalidHeapAttributeNumber;
 		}
 	}
 
 	*satisfies_hot = hot_result;
 	*satisfies_key = key_result;
+	*satisfies_id = id_result;
 }
 
 /*
@@ -6004,14 +6160,17 @@ log_heap_visible(RelFileNode rnode, Buffer heap_buffer, Buffer vm_buffer,
 static XLogRecPtr
 log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
+				HeapTuple old_key_tuple,
 				bool all_visible_cleared, bool new_all_visible_cleared)
 {
 	xl_heap_update xlrec;
-	xl_heap_header xlhdr;
+	xl_heap_header_len xlhdr;
+	xl_heap_header_len xlhdr_idx;
 	uint8		info;
 	XLogRecPtr	recptr;
-	XLogRecData rdata[4];
+	XLogRecData rdata[7];
 	Page		page = BufferGetPage(newbuf);
+	bool		need_tuple_data = RelationIsLogicallyLogged(reln);
 
 	/* Caller should not call me on a non-WAL-logged relation */
 	Assert(RelationNeedsWAL(reln));
@@ -6027,9 +6186,12 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	xlrec.old_infobits_set = compute_infobits(oldtup->t_data->t_infomask,
 											  oldtup->t_data->t_infomask2);
 	xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
-	xlrec.all_visible_cleared = all_visible_cleared;
+	xlrec.flags = 0;
+	if (all_visible_cleared)
+		xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED;
 	xlrec.newtid = newtup->t_self;
-	xlrec.new_all_visible_cleared = new_all_visible_cleared;
+	if (new_all_visible_cleared)
+		xlrec.flags |= XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED;
 
 	rdata[0].data = (char *) &xlrec;
 	rdata[0].len = SizeOfHeapUpdate;
@@ -6042,33 +6204,86 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	rdata[1].buffer_std = true;
 	rdata[1].next = &(rdata[2]);
 
-	xlhdr.t_infomask2 = newtup->t_data->t_infomask2;
-	xlhdr.t_infomask = newtup->t_data->t_infomask;
-	xlhdr.t_hoff = newtup->t_data->t_hoff;
+	xlhdr.header.t_infomask2 = newtup->t_data->t_infomask2;
+	xlhdr.header.t_infomask = newtup->t_data->t_infomask;
+	xlhdr.header.t_hoff = newtup->t_data->t_hoff;
+	xlhdr.t_len = newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
 
 	/*
-	 * As with insert records, we need not store the rdata[2] segment if we
-	 * decide to store the whole buffer instead.
+	 * As with insert records, we need not store the rdata[2] segment
+	 * if we decide to store the whole buffer instead unless we're
+	 * doing logical decoding.
 	 */
 	rdata[2].data = (char *) &xlhdr;
-	rdata[2].len = SizeOfHeapHeader;
-	rdata[2].buffer = newbuf;
+	rdata[2].len = SizeOfHeapHeaderLen;
+	rdata[2].buffer = need_tuple_data ? InvalidBuffer : newbuf;
 	rdata[2].buffer_std = true;
 	rdata[2].next = &(rdata[3]);
 
 	/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
-	rdata[3].data = (char *) newtup->t_data + offsetof(HeapTupleHeaderData, t_bits);
+	rdata[3].data = (char *) newtup->t_data
+		+ offsetof(HeapTupleHeaderData, t_bits);
 	rdata[3].len = newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
-	rdata[3].buffer = newbuf;
+	rdata[3].buffer = need_tuple_data ? InvalidBuffer : newbuf;
 	rdata[3].buffer_std = true;
 	rdata[3].next = NULL;
 
+	/*
+	 * Separate storage for the FPW buffer reference of the new page in the
+	 * wal_level >= logical case.
+	*/
+	if(need_tuple_data)
+	{
+		rdata[3].next = &(rdata[4]);
+
+		rdata[4].data = NULL,
+		rdata[4].len = 0;
+		rdata[4].buffer = newbuf;
+		rdata[4].buffer_std = true;
+		rdata[4].next = NULL;
+		xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
+
+		/* We need to log a tuple identity */
+		if (old_key_tuple)
+		{
+			/* don't really need this, but its more comfy to decode */
+			xlhdr_idx.header.t_infomask2 = old_key_tuple->t_data->t_infomask2;
+			xlhdr_idx.header.t_infomask = old_key_tuple->t_data->t_infomask;
+			xlhdr_idx.header.t_hoff = old_key_tuple->t_data->t_hoff;
+			xlhdr_idx.t_len = old_key_tuple->t_len;
+
+			rdata[4].next = &(rdata[5]);
+			rdata[5].data = (char *) &xlhdr_idx;
+			rdata[5].len = SizeOfHeapHeaderLen;
+			rdata[5].buffer = InvalidBuffer;
+			rdata[5].next = &(rdata[6]);
+
+			/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
+			rdata[6].data = (char *) old_key_tuple->t_data
+				+ offsetof(HeapTupleHeaderData, t_bits);
+			rdata[6].len = old_key_tuple->t_len
+				- offsetof(HeapTupleHeaderData, t_bits);
+			rdata[6].buffer = InvalidBuffer;
+			rdata[6].next = NULL;
+
+			if (reln->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
+				xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_TUPLE;
+			else
+				xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
+		}
+	}
+
 	/* If new tuple is the single and first tuple on page... */
 	if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
 		PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 	{
+		XLogRecData *rcur = &rdata[0];
 		info |= XLOG_HEAP_INIT_PAGE;
-		rdata[2].buffer = rdata[3].buffer = InvalidBuffer;
+		while (rcur != NULL)
+		{
+			rcur->buffer = InvalidBuffer;
+			rcur = rcur->next;
+		}
 	}
 
 	recptr = XLogInsert(RM_HEAP_ID, info, rdata);
@@ -6175,6 +6390,180 @@ log_newpage_buffer(Buffer buffer)
 }
 
 /*
+ * Perform XLogInsert of a XLOG_HEAP2_NEW_CID record
+ *
+ * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
+ * tuples.
+ */
+static XLogRecPtr
+log_heap_new_cid(Relation relation, HeapTuple tup)
+{
+	xl_heap_new_cid xlrec;
+
+	XLogRecPtr	recptr;
+	XLogRecData rdata[1];
+	HeapTupleHeader hdr = tup->t_data;
+
+	Assert(ItemPointerIsValid(&tup->t_self));
+	Assert(tup->t_tableOid != InvalidOid);
+
+	xlrec.top_xid = GetTopTransactionId();
+	xlrec.target.node = relation->rd_node;
+	xlrec.target.tid = tup->t_self;
+
+	/*
+	 * If the tuple got inserted & deleted in the same TX we definitely have a
+	 * combocid, set cmin and cmax.
+	 */
+	if (hdr->t_infomask & HEAP_COMBOCID)
+	{
+		Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID));
+		Assert(!(hdr->t_infomask & HEAP_XMIN_INVALID));
+		xlrec.cmin = HeapTupleHeaderGetCmin(hdr);
+		xlrec.cmax = HeapTupleHeaderGetCmax(hdr);
+		xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
+	}
+	/* No combocid, so only cmin or cmax can be set by this TX */
+	else
+	{
+		/*
+		 * Tuple inserted.
+		 *
+		 * We need to check for LOCK ONLY because multixacts might be
+		 * transferred to the new tuple in case of FOR KEY SHARE updates in
+		 * which case there will be a xmax, although the tuple just got
+		 * inserted.
+		 */
+		if (hdr->t_infomask & HEAP_XMAX_INVALID ||
+			hdr->t_infomask & HEAP_XMAX_LOCK_ONLY)
+		{
+			xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr);
+			xlrec.cmax = InvalidCommandId;
+		}
+		/* Tuple from a different tx updated or deleted. */
+		else
+		{
+			xlrec.cmin = InvalidCommandId;
+			xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr);
+
+		}
+		xlrec.combocid = InvalidCommandId;
+	}
+
+	rdata[0].data = (char *) &xlrec;
+	rdata[0].len = SizeOfHeapNewCid;
+	rdata[0].buffer = InvalidBuffer;
+	rdata[0].next = NULL;
+
+	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID, rdata);
+
+	return recptr;
+}
+
+/*
+ * Build a heap tuple representing the configured REPLICA IDENTITY to represent
+ * the old tuple in a UPDATE or DELETE.
+ *
+ * Returns NULL if there's no need to log a identity or if there's no suitable
+ * key in the Relation relation.
+ */
+static HeapTuple
+ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool *copy)
+{
+	TupleDesc	desc = RelationGetDescr(relation);
+	Relation	idx_rel;
+	TupleDesc	idx_desc;
+	char		replident = relation->rd_rel->relreplident;
+	HeapTuple	key_tuple = NULL;
+	bool		copy_oid = false;
+	bool		nulls[MaxHeapAttributeNumber];
+	Datum		values[MaxHeapAttributeNumber];
+	int			natt;
+
+	*copy = false;
+
+	if (!RelationIsLogicallyLogged(relation))
+		return NULL;
+
+	if (replident == REPLICA_IDENTITY_NOTHING)
+		return NULL;
+
+	if (replident == REPLICA_IDENTITY_FULL)
+	{
+		/*
+		 * When logging the entire old tuple, it very well could contain
+		 * toasted columns. If so, force them to be inlined.
+		 */
+		if (HeapTupleHasExternal(tp))
+		{
+			*copy = true;
+			tp = toast_flatten_tuple(tp, RelationGetDescr(relation));
+		}
+		return tp;
+	}
+
+	/* needs to already have been fetched? */
+	if (relation->rd_indexvalid == 0)
+		RelationGetIndexList(relation);
+
+	if (!OidIsValid(relation->rd_replidindex))
+	{
+		elog(DEBUG4, "Could not find configured replica identity for table \"%s\"",
+			 RelationGetRelationName(relation));
+		return NULL;
+	}
+
+	idx_rel = RelationIdGetRelation(relation->rd_replidindex);
+	idx_desc = RelationGetDescr(idx_rel);
+
+	/* deform tuple, so we have fast access to columns */
+	heap_deform_tuple(tp, desc, values, nulls);
+
+	/* set all columns to NULL, regardless of whether they actually are */
+	memset(nulls, 1, sizeof(nulls));
+
+	/*
+	 * Now set all columns contained in the index to NOT NULL, they cannot
+	 * currently be NULL.
+	 */
+	for (natt = 0; natt < idx_desc->natts; natt++)
+	{
+		int attno = idx_rel->rd_index->indkey.values[natt];
+
+		if (attno == ObjectIdAttributeNumber)
+			copy_oid = true;
+		else if (attno < 0)
+			elog(ERROR, "system column in index");
+		else
+			nulls[attno - 1] = false;
+	}
+
+	key_tuple = heap_form_tuple(desc, values, nulls);
+	*copy = true;
+	RelationClose(idx_rel);
+
+	/* XXX: we could also do this unconditionally, the space is used anyway */
+	if (copy_oid)
+		HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));
+
+	/*
+	 * If the tuple, which by here only contains indexed columns, still has
+	 * toasted columns, force them to be inlined. This is somewhat unlikely
+	 * since there's limits on the size of indexed columns, so we don't
+	 * duplicate toast_flatten_tuple()s functionality in the above loop over
+	 * the indexed columns, even if it would be more efficient.
+	 */
+	if (HeapTupleHasExternal(tp))
+	{
+		HeapTuple oldtup = tp;
+		tp = toast_flatten_tuple(oldtup, RelationGetDescr(relation));
+		heap_freetuple(oldtup);
+	}
+
+	return key_tuple;
+}
+
+/*
  * Handles CLEANUP_INFO
  */
 static void
@@ -6535,7 +6924,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6584,7 +6973,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record)
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	/* Make sure there is no forward chain link in t_ctid */
@@ -6618,7 +7007,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6689,7 +7078,7 @@ heap_xlog_insert(XLogRecPtr lsn, XLogRecord *record)
 
 	PageSetLSN(page, lsn);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	MarkBufferDirty(buffer);
@@ -6752,7 +7141,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->node);
 		Buffer		vmbuffer = InvalidBuffer;
@@ -6835,7 +7224,7 @@ heap_xlog_multi_insert(XLogRecPtr lsn, XLogRecord *record)
 
 	PageSetLSN(page, lsn);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	MarkBufferDirty(buffer);
@@ -6874,7 +7263,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 		HeapTupleHeaderData hdr;
 		char		data[MaxHeapTupleSize];
 	}			tbuf;
-	xl_heap_header xlhdr;
+	xl_heap_header_len xlhdr;
 	int			hsize;
 	uint32		newlen;
 	Size		freespace;
@@ -6883,7 +7272,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		BlockNumber block = ItemPointerGetBlockNumber(&xlrec->target.tid);
@@ -6961,7 +7350,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool hot_update)
 	/* Mark the page as a candidate for pruning */
 	PageSetPrunable(page, record->xl_xid);
 
-	if (xlrec->all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	/*
@@ -6985,7 +7374,7 @@ newt:;
 	 * The visibility map may need to be fixed even if the heap page is
 	 * already up-to-date.
 	 */
-	if (xlrec->new_all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED)
 	{
 		Relation	reln = CreateFakeRelcacheEntry(xlrec->target.node);
 		BlockNumber block = ItemPointerGetBlockNumber(&xlrec->newtid);
@@ -7043,13 +7432,13 @@ newsame:;
 	if (PageGetMaxOffsetNumber(page) + 1 < offnum)
 		elog(PANIC, "heap_update_redo: invalid max offset number");
 
-	hsize = SizeOfHeapUpdate + SizeOfHeapHeader;
+	hsize = SizeOfHeapUpdate + SizeOfHeapHeaderLen;
 
-	newlen = record->xl_len - hsize;
-	Assert(newlen <= MaxHeapTupleSize);
 	memcpy((char *) &xlhdr,
 		   (char *) xlrec + SizeOfHeapUpdate,
-		   SizeOfHeapHeader);
+		   SizeOfHeapHeaderLen);
+	newlen = xlhdr.t_len;
+	Assert(newlen <= MaxHeapTupleSize);
 	htup = &tbuf.hdr;
 	MemSet((char *) htup, 0, sizeof(HeapTupleHeaderData));
 	/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
@@ -7057,9 +7446,9 @@ newsame:;
 		   (char *) xlrec + hsize,
 		   newlen);
 	newlen += offsetof(HeapTupleHeaderData, t_bits);
-	htup->t_infomask2 = xlhdr.t_infomask2;
-	htup->t_infomask = xlhdr.t_infomask;
-	htup->t_hoff = xlhdr.t_hoff;
+	htup->t_infomask2 = xlhdr.header.t_infomask2;
+	htup->t_infomask = xlhdr.header.t_infomask;
+	htup->t_hoff = xlhdr.header.t_hoff;
 
 	HeapTupleHeaderSetXmin(htup, record->xl_xid);
 	HeapTupleHeaderSetCmin(htup, FirstCommandId);
@@ -7071,7 +7460,7 @@ newsame:;
 	if (offnum == InvalidOffsetNumber)
 		elog(PANIC, "heap_update_redo: failed to add tuple");
 
-	if (xlrec->new_all_visible_cleared)
+	if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED)
 		PageClearAllVisible(page);
 
 	freespace = PageGetHeapFreeSpace(page);		/* needed to update FSM below */
@@ -7322,6 +7711,12 @@ heap2_redo(XLogRecPtr lsn, XLogRecord *record)
 		case XLOG_HEAP2_LOCK_UPDATED:
 			heap_xlog_lock_updated(lsn, record);
 			break;
+		case XLOG_HEAP2_NEW_CID:
+			/*
+			 * Nothing to do on a real replay, only used during logical
+			 * decoding.
+			 */
+			break;
 		default:
 			elog(PANIC, "heap2_redo: unknown op code %u", info);
 	}
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index e14c053..39c53d0 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -184,6 +184,15 @@ heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
 						 xlrec->infobits_set);
 		out_target(buf, &(xlrec->target));
 	}
+	else if (info == XLOG_HEAP2_NEW_CID)
+	{
+		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
+
+		appendStringInfo(buf, "new_cid: ");
+		out_target(buf, &(xlrec->target));
+		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
+						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
+	}
 	else
 		appendStringInfoString(buf, "UNKNOWN");
 }
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 1d70494..dd67b1f 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -28,6 +28,7 @@ const struct config_enum_entry wal_level_options[] = {
 	{"minimal", WAL_LEVEL_MINIMAL, false},
 	{"archive", WAL_LEVEL_ARCHIVE, false},
 	{"hot_standby", WAL_LEVEL_HOT_STANDBY, false},
+	{"logical", WAL_LEVEL_LOGICAL, false},
 	{NULL, 0, false}
 };
 
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index e975f8d..48203d2 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -47,6 +47,7 @@
 #include "access/twophase.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
+#include "access/xlog.h"
 #include "access/xlogutils.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
@@ -1920,7 +1921,8 @@ RecoverPreparedTransactions(void)
 			 * the prepared transaction generated xid assignment records. Test
 			 * here must match one used in AssignTransactionId().
 			 */
-			if (InHotStandby && hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS)
+			if (InHotStandby && (hdr->nsubxacts >= PGPROC_MAX_CACHED_SUBXIDS ||
+								 XLogLogicalInfoActive()))
 				overwriteOK = true;
 
 			/*
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index bab048d..04d4af9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -146,6 +146,7 @@ typedef struct TransactionStateData
 	int			prevSecContext; /* previous SecurityRestrictionContext */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
+	bool		didLogXid;		/* has xid been included in WAL record? */
 	struct TransactionStateData *parent;		/* back link to parent */
 } TransactionStateData;
 
@@ -175,6 +176,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* previous SecurityRestrictionContext */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
+	false,						/* didLogXid */
 	NULL						/* link to parent state block */
 };
 
@@ -393,6 +395,19 @@ GetCurrentTransactionIdIfAny(void)
 }
 
 /*
+ *	MarkCurrentTransactionIdLoggedIfAny
+ *
+ * Remember that the current xid - if it is assigned - now has been wal logged.
+ */
+void
+MarkCurrentTransactionIdLoggedIfAny(void)
+{
+	if (TransactionIdIsValid(CurrentTransactionState->transactionId))
+		CurrentTransactionState->didLogXid = true;
+}
+
+
+/*
  *	GetStableLatestTransactionId
  *
  * Get the transaction's XID if it has one, else read the next-to-be-assigned
@@ -433,6 +448,7 @@ AssignTransactionId(TransactionState s)
 {
 	bool		isSubXact = (s->parent != NULL);
 	ResourceOwner currentOwner;
+	bool log_unknown_top = false;
 
 	/* Assert that caller didn't screw up */
 	Assert(!TransactionIdIsValid(s->transactionId));
@@ -468,6 +484,20 @@ AssignTransactionId(TransactionState s)
 	}
 
 	/*
+	 * When wal_level=logical, guarantee that a subtransaction's xid can only
+	 * be seen in the WAL stream if its toplevel xid has been logged
+	 * before. If necessary we log a xact_assignment record with fewer than
+	 * PGPROC_MAX_CACHED_SUBXIDS. Note that it is fine if didLogXid isn't set
+	 * for a transaction even though it appears in a WAL record, we just might
+	 * superfluously log something. That can happen when an xid is included
+	 * somewhere inside a wal record, but not in XLogRecord->xl_xid, like in
+	 * xl_standby_locks.
+	 */
+	if (isSubXact && XLogLogicalInfoActive() &&
+		!TopTransactionStateData.didLogXid)
+		log_unknown_top = true;
+
+	/*
 	 * Generate a new Xid and record it in PG_PROC and pg_subtrans.
 	 *
 	 * NB: we must make the subtrans entry BEFORE the Xid appears anywhere in
@@ -521,6 +551,9 @@ AssignTransactionId(TransactionState s)
 	 * top-level transaction that each subxact belongs to. This is correct in
 	 * recovery only because aborted subtransactions are separately WAL
 	 * logged.
+	 *
+	 * This is correct even for the case where several levels above us didn't
+	 * have an xid assigned as we recursed up to them beforehand.
 	 */
 	if (isSubXact && XLogStandbyInfoActive())
 	{
@@ -531,7 +564,8 @@ AssignTransactionId(TransactionState s)
 		 * ensure this test matches similar one in
 		 * RecoverPreparedTransactions()
 		 */
-		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS)
+		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS ||
+			log_unknown_top)
 		{
 			XLogRecData rdata[2];
 			xl_xact_assignment xlrec;
@@ -550,13 +584,15 @@ AssignTransactionId(TransactionState s)
 			rdata[0].next = &rdata[1];
 
 			rdata[1].data = (char *) unreportedXids;
-			rdata[1].len = PGPROC_MAX_CACHED_SUBXIDS * sizeof(TransactionId);
+			rdata[1].len = nUnreportedXids * sizeof(TransactionId);
 			rdata[1].buffer = InvalidBuffer;
 			rdata[1].next = NULL;
 
 			(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT, rdata);
 
 			nUnreportedXids = 0;
+			/* mark top, not current xact as having been logged */
+			TopTransactionStateData.didLogXid = true;
 		}
 	}
 }
@@ -1735,6 +1771,7 @@ StartTransaction(void)
 	 * initialize reported xid accounting
 	 */
 	nUnreportedXids = 0;
+	s->didLogXid = false;
 
 	/*
 	 * must initialize resource-management stuff first
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index de19d22..fe1d9d0 100755
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1191,6 +1191,8 @@ begin:;
 	 */
 	WALInsertSlotRelease();
 
+	MarkCurrentTransactionIdLoggedIfAny();
+
 	END_CRIT_SECTION();
 
 	/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0275240..aa31429 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3321,7 +3321,7 @@ reindex_relation(Oid relid, int flags)
 
 	/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
 	if (is_pg_class)
-		(void) RelationGetIndexAttrBitmap(rel, false);
+		(void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL);
 
 	PG_TRY();
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0008fc6..4eff184 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2355,7 +2355,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	 * concurrency.
 	 */
 	modifiedCols = GetModifiedColumns(relinfo, estate);
-	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, true);
+	keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
+										 INDEX_ATTR_BITMAP_KEY);
 	if (bms_overlap(keyCols, modifiedCols))
 		lockmode = LockTupleExclusive;
 	else
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index d294a5a..048a189 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -816,10 +816,10 @@ PostmasterMain(int argc, char *argv[])
 	}
 	if (XLogArchiveMode && wal_level == WAL_LEVEL_MINIMAL)
 		ereport(ERROR,
-				(errmsg("WAL archival (archive_mode=on) requires wal_level \"archive\" or \"hot_standby\"")));
+				(errmsg("WAL archival (archive_mode=on) requires wal_level \"archive\", \"hot_standby\" or \"logical\"")));
 	if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
 		ereport(ERROR,
-				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\" or \"hot_standby\"")));
+				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\", \"hot_standby\" or \"logical\"")));
 
 	/*
 	 * Other one-time internal sanity checks can go here, if they are fast.
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d0acca8..145ab8b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3832,17 +3832,28 @@ RelationGetIndexPredicate(Relation relation)
  * be bms_free'd when not needed anymore.
  */
 Bitmapset *
-RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
+RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
 	Bitmapset  *indexattrs;
-	Bitmapset  *uindexattrs;
+	Bitmapset  *uindexattrs; /* unique keys */
+	Bitmapset  *idindexattrs; /* replica identity index */
 	List	   *indexoidlist;
 	ListCell   *l;
 	MemoryContext oldcxt;
 
 	/* Quick exit if we already computed the result. */
 	if (relation->rd_indexattr != NULL)
-		return bms_copy(keyAttrs ? relation->rd_keyattr : relation->rd_indexattr);
+		switch(attrKind)
+		{
+			case INDEX_ATTR_BITMAP_IDENTITY_KEY:
+				return bms_copy(relation->rd_idattr);
+			case INDEX_ATTR_BITMAP_KEY:
+				return bms_copy(relation->rd_keyattr);
+			case INDEX_ATTR_BITMAP_ALL:
+				return bms_copy(relation->rd_indexattr);
+			default:
+				elog(ERROR, "unknown attrKind %u", attrKind);
+		}
 
 	/* Fast path if definitely no indexes */
 	if (!RelationGetForm(relation)->relhasindex)
@@ -3869,13 +3880,16 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 	 */
 	indexattrs = NULL;
 	uindexattrs = NULL;
+	idindexattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
 		Relation	indexDesc;
 		IndexInfo  *indexInfo;
 		int			i;
-		bool		isKey;
+		bool		isIDKey;/* replica identity index */
+		bool		isKey;/* key member */
+
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -3887,6 +3901,8 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 			indexInfo->ii_Expressions == NIL &&
 			indexInfo->ii_Predicate == NIL;
 
+		isIDKey = indexOid == relation->rd_replidindex;
+
 		/* Collect simple attribute references */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 		{
@@ -3896,6 +3912,11 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 			{
 				indexattrs = bms_add_member(indexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isIDKey)
+					idindexattrs = bms_add_member(idindexattrs,
+												 attrnum - FirstLowInvalidHeapAttributeNumber);
+
 				if (isKey)
 					uindexattrs = bms_add_member(uindexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
@@ -3917,10 +3938,21 @@ RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	relation->rd_indexattr = bms_copy(indexattrs);
 	relation->rd_keyattr = bms_copy(uindexattrs);
+	relation->rd_idattr = bms_copy(idindexattrs);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
-	return keyAttrs ? uindexattrs : indexattrs;
+	switch(attrKind)
+	{
+		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
+			return idindexattrs;
+		case INDEX_ATTR_BITMAP_KEY:
+			return uindexattrs;
+		case INDEX_ATTR_BITMAP_ALL:
+			return indexattrs;
+		default:
+			elog(ERROR, "unknown attrKind %u", attrKind);
+	}
 }
 
 /*
@@ -4895,3 +4927,40 @@ unlink_initfile(const char *initfilename)
 			elog(LOG, "could not remove cache file \"%s\": %m", initfilename);
 	}
 }
+
+/* check RelationIsAccessibleInLogicalDecoding's comment */
+bool
+RelationIsAccessibleInLogicalDecodingInternal(Relation relation)
+{
+	Assert(XLogLogicalInfoActive());
+
+	if (!RelationNeedsWAL(relation))
+		return false;
+
+	/*
+	 * We need access to all system tables from decoding snapshots.
+	 */
+	if (IsCatalogRelation(relation))
+		return true;
+
+	return false;
+}
+
+/* check RelationIsLogicallyLogged's comment */
+bool
+RelationIsLogicallyLoggedInternal(Relation relation)
+{
+	Assert(XLogLogicalInfoActive());
+
+	/* no need to log anything for unlogged tables and similar things */
+	if (!RelationNeedsWAL(relation))
+		return false;
+
+	/*
+	 * For now we don't decode changes to system tables, so don't log them.
+	 */
+	if (IsCatalogRelation(relation))
+		return false;
+
+	return true;
+}
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7a18e72..bd0d69c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -168,7 +168,7 @@
 
 # - Settings -
 
-#wal_level = minimal			# minimal, archive, or hot_standby
+#wal_level = minimal			# minimal, archive, hot_standby or logical
 					# (change requires restart)
 #fsync = on				# turns forced synchronization on or off
 #synchronous_commit = on		# synchronization level;
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index fde483a..8c6cf24 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -77,6 +77,8 @@ wal_level_str(WalLevel wal_level)
 			return "archive";
 		case WAL_LEVEL_HOT_STANDBY:
 			return "hot_standby";
+		case WAL_LEVEL_LOGICAL:
+			return "logical";
 	}
 	return _("unrecognized wal_level");
 }
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 4381778..82338ca 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -55,6 +55,20 @@
 #define XLOG_HEAP2_VISIBLE		0x40
 #define XLOG_HEAP2_MULTI_INSERT 0x50
 #define XLOG_HEAP2_LOCK_UPDATED 0x60
+#define XLOG_HEAP2_NEW_CID		0x70
+
+/*
+ * xl_heap_* ->flag values
+ */
+/* PD_ALL_VISIBLE was cleared */
+#define XLOG_HEAP_ALL_VISIBLE_CLEARED		(1<<0)
+/* PD_ALL_VISIBLE was cleared in the 2nd page */
+#define XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED	(1<<1)
+#define XLOG_HEAP_CONTAINS_OLD_TUPLE		(1<<2)
+#define XLOG_HEAP_CONTAINS_OLD_KEY			(1<<3)
+#define XLOG_HEAP_CONTAINS_NEW_TUPLE		(1<<4)
+#define XLOG_HEAP_CONTAINS_OLD 						\
+	(XLOG_HEAP_CONTAINS_OLD_TUPLE | XLOG_HEAP_CONTAINS_OLD_KEY)
 
 /*
  * All what we need to find changed tuple
@@ -78,10 +92,10 @@ typedef struct xl_heap_delete
 	xl_heaptid	target;			/* deleted tuple id */
 	TransactionId xmax;			/* xmax of the deleted tuple */
 	uint8		infobits_set;	/* infomask bits */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
+	uint8		flags;
 } xl_heap_delete;
 
-#define SizeOfHeapDelete	(offsetof(xl_heap_delete, all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapDelete	(offsetof(xl_heap_delete, flags) + sizeof(uint8))
 
 /*
  * We don't store the whole fixed part (HeapTupleHeaderData) of an inserted
@@ -100,15 +114,23 @@ typedef struct xl_heap_header
 
 #define SizeOfHeapHeader	(offsetof(xl_heap_header, t_hoff) + sizeof(uint8))
 
+typedef struct xl_heap_header_len
+{
+	uint16      t_len;
+	xl_heap_header header;
+} xl_heap_header_len;
+
+#define SizeOfHeapHeaderLen	(offsetof(xl_heap_header_len, header) + SizeOfHeapHeader)
+
 /* This is what we need to know about insert */
 typedef struct xl_heap_insert
 {
 	xl_heaptid	target;			/* inserted tuple id */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
+	uint8		flags;
 	/* xl_heap_header & TUPLE DATA FOLLOWS AT END OF STRUCT */
 } xl_heap_insert;
 
-#define SizeOfHeapInsert	(offsetof(xl_heap_insert, all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapInsert	(offsetof(xl_heap_insert, flags) + sizeof(uint8))
 
 /*
  * This is what we need to know about a multi-insert. The record consists of
@@ -120,7 +142,7 @@ typedef struct xl_heap_multi_insert
 {
 	RelFileNode node;
 	BlockNumber blkno;
-	bool		all_visible_cleared;
+	uint8		flags;
 	uint16		ntuples;
 	OffsetNumber offsets[1];
 
@@ -147,13 +169,12 @@ typedef struct xl_heap_update
 	TransactionId old_xmax;		/* xmax of the old tuple */
 	TransactionId new_xmax;		/* xmax of the new tuple */
 	ItemPointerData newtid;		/* new inserted tuple id */
-	uint8		old_infobits_set;		/* infomask bits to set on old tuple */
-	bool		all_visible_cleared;	/* PD_ALL_VISIBLE was cleared */
-	bool		new_all_visible_cleared;		/* same for the page of newtid */
+	uint8		old_infobits_set;	/* infomask bits to set on old tuple */
+	uint8		flags;
 	/* NEW TUPLE xl_heap_header AND TUPLE DATA FOLLOWS AT END OF STRUCT */
 } xl_heap_update;
 
-#define SizeOfHeapUpdate	(offsetof(xl_heap_update, new_all_visible_cleared) + sizeof(bool))
+#define SizeOfHeapUpdate	(offsetof(xl_heap_update, flags) + sizeof(uint8))
 
 /*
  * This is what we need to know about vacuum page cleanup/redirect
@@ -261,6 +282,29 @@ typedef struct xl_heap_visible
 
 #define SizeOfHeapVisible (offsetof(xl_heap_visible, cutoff_xid) + sizeof(TransactionId))
 
+typedef struct xl_heap_new_cid
+{
+	/*
+	 * store toplevel xid so we don't have to merge cids from different
+	 * transactions
+	 */
+	TransactionId top_xid;
+	CommandId cmin;
+	CommandId cmax;
+	/*
+	 * don't really need the combocid since we have the actual values
+	 * right in this struct, but the padding makes it free and its
+	 * useful for debugging.
+	 */
+	CommandId combocid;
+	/*
+	 * Store the relfilenode/ctid pair to facilitate lookups.
+	 */
+	xl_heaptid target;
+} xl_heap_new_cid;
+
+#define SizeOfHeapNewCid (offsetof(xl_heap_new_cid, target) + SizeOfHeapTid)
+
 extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 									   TransactionId *latestRemovedXid);
 
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 1d3e7d8..9632378 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -215,6 +215,7 @@ extern TransactionId GetCurrentTransactionId(void);
 extern TransactionId GetCurrentTransactionIdIfAny(void);
 extern TransactionId GetStableLatestTransactionId(void);
 extern SubTransactionId GetCurrentSubTransactionId(void);
+extern void MarkCurrentTransactionIdLoggedIfAny(void);
 extern bool SubTransactionIsActive(SubTransactionId subxid);
 extern CommandId GetCurrentCommandId(bool used);
 extern TimestampTz GetCurrentTransactionStartTimestamp(void);
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 002862c..7415a26 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -197,7 +197,8 @@ typedef enum WalLevel
 {
 	WAL_LEVEL_MINIMAL = 0,
 	WAL_LEVEL_ARCHIVE,
-	WAL_LEVEL_HOT_STANDBY
+	WAL_LEVEL_HOT_STANDBY,
+	WAL_LEVEL_LOGICAL
 } WalLevel;
 extern int	wal_level;
 
@@ -210,9 +211,12 @@ extern int	wal_level;
  */
 #define XLogIsNeeded() (wal_level >= WAL_LEVEL_ARCHIVE)
 
-/* Do we need to WAL-log information required only for Hot Standby? */
+/* Do we need to WAL-log information required only for Hot Standby and logical replication? */
 #define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_HOT_STANDBY)
 
+/* Do we need to WAL-log information required only for logical replication? */
+#define XLogLogicalInfoActive() (wal_level >= WAL_LEVEL_LOGICAL)
+
 #ifdef WAL_DEBUG
 extern bool XLOG_DEBUG;
 #endif
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 21d5871..30c5751 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -104,6 +104,7 @@ typedef struct RelationData
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
+	Bitmapset  *rd_idattr;		/* included in replica identity index */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
 	LockInfoData rd_lockInfo;	/* lock mgr's info for locking relation */
 	RuleLock   *rd_rules;		/* rewrite rules */
@@ -453,6 +454,26 @@ typedef struct StdRdOptions
  */
 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)
 
+/*
+ * RelationIsAccessibleInLogicalDecoding
+ *		True if we need to log enough information to have access via
+ *		decoding snapshot.
+ */
+#define RelationIsAccessibleInLogicalDecoding(relation) \
+	(wal_level >= WAL_LEVEL_LOGICAL && \
+	 RelationIsAccessibleInLogicalDecodingInternal(relation))
+
+/*
+ * RelationIsLogicallyLogged
+ *		True if we need to log enough information to decode the data
+ *		from the WAL stream.
+ */
+#define RelationIsLogicallyLogged(relation) \
+	(wal_level >= WAL_LEVEL_LOGICAL && \
+	 RelationIsLogicallyLoggedInternal(relation))
+
+extern bool RelationIsAccessibleInLogicalDecodingInternal(Relation relation);
+extern bool RelationIsLogicallyLoggedInternal(Relation relation);
 
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 8ac2549..d7604ec 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -41,7 +41,17 @@ extern List *RelationGetIndexList(Relation relation);
 extern Oid	RelationGetOidIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
-extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs);
+
+typedef enum IndexAttrBitmapKind
+{
+	INDEX_ATTR_BITMAP_ALL,
+	INDEX_ATTR_BITMAP_KEY,
+	INDEX_ATTR_BITMAP_IDENTITY_KEY
+} IndexAttrBitmapKind;
+
+extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
+											 IndexAttrBitmapKind keyAttrs);
+
 extern void RelationGetExclusionInfo(Relation indexRelation,
 						 Oid **operators,
 						 Oid **procs,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b20eb0d..c520037 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -791,6 +791,7 @@ IdentifySystemCmd
 IncrementVarSublevelsUp_context
 Index
 IndexArrayKeyInfo
+IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
 IndexBulkDeleteCallback
@@ -2419,11 +2420,13 @@ xl_heap_cleanup_info
 xl_heap_delete
 xl_heap_freeze
 xl_heap_header
+xl_heap_header_len
 xl_heap_inplace
 xl_heap_insert
 xl_heap_lock
 xl_heap_lock_updated
 xl_heap_multi_insert
+xl_heap_new_cid
 xl_heap_newpage
 xl_heap_update
 xl_heap_visible
#162Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Andres Freund (#158)
Re: logical changeset generation v6.7

Hello, This is rather trivial and superficial comments as not
fully gripping functions of this patchset.

- Some patches have line offset to master. Rebase needed.

Other random comments follows,

===== 0001:

- You assined HeapTupleGetOid(tuple) into relid to read in
several points but no modification. Nevertheless, you left
HeapTupleGetOid not replaced there. I think 'relid' just for
repeated reading has far small merit compared to demerit of
lowering readability. You'd be better to make them uniform in
either way.

===== 0002:

- You are identifying the wal_level with the expr 'wal_level >=
WAL_LEVEL_LOGICAL' but it seems somewhat improper.

- In heap_insert, you added following comment and code,

* Also, if this is a catalog, we need to transmit combocids to
* properly decode, so log that as well.
*/
need_tuple_data = RelationIsLogicallyLogged(relation);
if (RelationIsAccessibleInLogicalDecoding(relation))
log_heap_new_cid(relation, heaptup);

Actually 'is a catalog' is checkied in
RelationIsAcc...Decodeing() but this either of naming or
commnet should be changed for consistent look. (I this the
name of the macro is rather long but gives a vague
illustration of the function..)

- RelationIsAccessibleInLogicalDecoding and
RelationIsLogicallyLogged are identical in code. Together with
the name ambiguity, this is quite confising and cause of
future misuse between these macros, I suppose. Plus the names
seem too long.

- In heap_insert, the information conveyed on rdata[3] seems to
be better to be in rdata[2] because of the scarecity of the
additional information. XLOG_HEAP_CONTAINS_NEW_TUPLE only
seems to be needed. Also is in heap_multi_insert and
heap_upate.

- In heap_multi_insert, need_cids referred only once so might be
better removed.

- In heap_delete, at the point adding replica identity, same to
the aboves, rdata[3] could be moved into rdata[2] making new
type like 'xl_heap_replident'.

- In heapam_xlog.h, the new type xl_heap_header_len is
inadequate in both of naming which is confising and
construction on which the header in xl_heap_header is no
longer be a header and indecisive member name 't_len'..

- In heapam_xlog.h, XLOG_HEAP_CONTAINS_OLD looks incomplete. And
it seems to be used in nowhere in this patchset. It should be
removed.

- log_heap_new_cid() is called at several part just before other
xlogs is being inserted. I suppose this should be built in the
target xlog structures.

- in RecovoerPreparedTransactions(), any commend needed for the
reason calling XLogLogicalInfoActive()..

- In xact.c, the comment for the member 'didLogXid' in
TransactionStateData seems differ from it's meaning. It
becomes true when any WAL record for the current transaction
id just has been written to WAL buffer. So the comment,

/* has xid been included in WAL record? */

would be better be something like (Should need corrected as
I'm not native speaker.)

/* Any WAL record for this transaction has been emitted ? */

and also the member name should be something like
XidIsLogged. (Not so chaned?)

- The name of the function MarkCurrentTransactionIdLoggedIfAny,
although irregular abbreviations are discouraged, seems too
long. Isn't MarkCur(r/rent)XidLoggedIfAny sufficient? Anyway,
the work involving this function seems would be better to be
done in some other way..

- The comment for RelationGetIndexAttrBitmap() should be edited
for attrKind.

- The macro name INDEX_ATTR_BITMAP_KEY should be
INDEX_ATTR_BITMAP_FKEY. And INDEX_ATTR_BITMAP_IDENTITY_KEY
should be INDEX_ATTR_BITMAP_REPLID_KEY or something.

- In rel.h the member name 'rd_idattr' would be better being
'rd_replidattr' or something like that.

===== 0004:

- Could the macro name 'RelationIsUsedAsCatalogTable' be as
simple as IsUserCatalogRelation or something? It's from the
viewpoint of not only simplicity but also similarity to other
macro and/or functions having closer functionality. You
already call the table 'user_catalog_table' in rel.h.

To be continued to next mail.

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#163Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#162)
Re: logical changeset generation v6.7

Hello, this is continued comments.

===== 0004:

....

To be continued to next mail.

===== 0005:

- In heapam.c, it seems to be better replacing t_self only
during logical decoding.

- In GetOldestXmin(), the parameter name 'alreadyLocked' would
be better being simplly 'nolock' since alreadyLocked seems to
me suggesting that it will unlock the lock acquired beforehand.

- Before that, In LogicalDecodingAcquireFreeSlot, lock window
for procarray is extended after GetOldestXmin, but procarray
does not seem to be accessed during the additional period. If
you are holding this lock for the purpose other than accessing
procArray, it'd be better to provide its own lock object.

LWLockAcquire(ProcArrayLock, LW_SHARED);
slot->effective_xmin = GetOldestXmin(true, true, true);
slot->xmin = slot->effective_xmin;

if (!TransactionIdIsValid(LogicalDecodingCtl->xmin) ||
NormalTransactionIdPrecedes(slot->effective_xmin, LogicalDecodingCtl->xmin))
LogicalDecodingCtl->xmin = slot->effective_xmin;
LWLockRelease(ProcArrayLock);

- In dropdb in dbcommands.c, ereport is invoked referring the
result of LogicalDecodingCountDBSlots. But it seems better to
me issueing this exception within LogicalDecodingCountDBSlots
even if goto is required.

- In LogStandbySnapshot in standby.c, two complementary
conditions are imposed on two same unlocks. It might be
somewhat paranoic but it is safer being like follows,

| XLogRecPtr recptr = InvalidXLogRecPtr;
| ....
|
| /* LogCurrentRunningXacts shoud be done before unlock when logical decoding*/
| if (wal_level >= WAL_LEVEL_LOGICAL)
| recptr = LogCurrentRunningXacts(running);
|
| LWLockRelease(ProcArrayLock);
|
| if (recptr == InvalidXLogRecPtr)
| recptr = LogCurrentRunningXacts(running);

- In tqual.c, in Setup/RevertFrom DecodingSnapshots, the name
CatalogSnapshotData looks lacking unity with other
Snapshot*Data's.

===== 0007:

- In heapam.c, the new global variable 'RecentGlobalDataXmin' is
quite similar to 'RecentGlobalXmin' and does not represents
what it is. The name should be
changed. RecentGlobalNonCatalogXmin would be more preferable..

- Althgough simplly from my teste, the following part in
heapam.c

if (IsSystemRelation(scan->rs_rd)
|| RelationIsAccessibleInLogicalDecoding(scan->rs_rd))
heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
else
heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalDataXmin);

would be readable to be like,

if (IsSystemRelation(scan->rs_rd)
|| RelationIsAccessibleInLogicalDecoding(scan->rs_rd))
xmin = RecentGlobalXmin
else
xmin = RecentGlobalDataXmin
heap_page_prune_opt(scan->rs_rd, buffer, xmin);

index_fetch_heap in indexam.c has similar part to this, and
you coded in latter style in IndexBuildHeapScan in index.c.

- In procarray.c, you should add documentation for new parameter
'systable' for GetOldestXmin. And the name 'systable' seems
somewhat confusing, since its full-splled meaning is
'including systables'. This name should be changed to
'include_systable' or 'only_usertable' with inverting or
something..

0008 and after to come later..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#164Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#161)
Re: logical changeset generation v6.7

On 2013-11-29 01:16:39 -0500, Robert Haas wrote:

On Thu, Nov 14, 2013 at 8:46 AM, Andres Freund <andres@2ndquadrant.com> wrote:

[ new patches ]

Here's an updated version of patch #2. I didn't really like the
approach you took in the documentation, so I revised it.

Fair enough.

Apart from that, I spent a lot of time looking at
HeapSatisfiesHOTandKeyUpdate. I'm not very happy with your changes.
The idea seems to be that we'll iterate through all of the HOT columns
regardless, but that might be very inefficient. Suppose there are 100
HOT columns, the last one is the only key column, and only the first
one has been modified. Once we look at #1 and determine that it's not
HOT, we should zoom forward and skip over the next 98, and only look
at the last one; your version does not behave like that.

Well, the hot bitmap will only contains indexed columns, so for that's
only going to happen if there's actually indexes over all those
columns. And in that case it seems unlikely that the performance
of that routine matters.
That said, keeping the old performance characteristics seems like a good
idea to me. Not sure anymore why I changed it that way.

I've taken a crack at rewriting
this logic, and the result looks cleaner and simpler to me, but I
haven't tested it beyond the fact that it passes make check. See what
you think.

Hm. I think it actually will not abort early in call cases either, but
that looks fixable. Imagine what happens if id_attrs or key_attrs is
empty, ISTM that we'll check all hot columns in that case.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#165Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#160)
Re: logical changeset generation v6.7

Hi,

On 2013-11-28 21:15:18 -0500, Robert Haas wrote:

OK, I've committed the patch to adjust the definition of
IsSystemRelation()/IsSystemClass() and add
IsCatalogRelation()/IsCatalogClass().

Thanks for taking care of this!

I kibitzed your decision about
which function to use in a few places - specifically, I made all of
the places that cared about allow_system_table_mods uses the IsSystem
functions, and all the places that cared about invalidation messages
use the IsCatalog functions. I don't think any of these changes are
more cosmetic, but I think it may reduce the chance of errors or
inconsistencies in the face of future changes.

Agreed.

Do you think we need to do anything about the
ERROR: cannot remove dependency on schema pg_catalog because it is a system object
thingy? Imo the current state is much more consistent than the earlier
one, but that's still a quite surprising leftover...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#166Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#165)
Re: logical changeset generation v6.7

On Tue, Dec 3, 2013 at 8:24 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-11-28 21:15:18 -0500, Robert Haas wrote:

OK, I've committed the patch to adjust the definition of
IsSystemRelation()/IsSystemClass() and add
IsCatalogRelation()/IsCatalogClass().

Thanks for taking care of this!

I kibitzed your decision about
which function to use in a few places - specifically, I made all of
the places that cared about allow_system_table_mods uses the IsSystem
functions, and all the places that cared about invalidation messages
use the IsCatalog functions. I don't think any of these changes are
more cosmetic, but I think it may reduce the chance of errors or
inconsistencies in the face of future changes.

Agreed.

Do you think we need to do anything about the
ERROR: cannot remove dependency on schema pg_catalog because it is a system object
thingy? Imo the current state is much more consistent than the earlier
one, but that's still a quite surprising leftover...

I don't feel obliged to change it, but I also don't see a reason not
to clean it up.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#167Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#164)
Re: logical changeset generation v6.7

On Tue, Dec 3, 2013 at 8:18 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I've taken a crack at rewriting
this logic, and the result looks cleaner and simpler to me, but I
haven't tested it beyond the fact that it passes make check. See what
you think.

Hm. I think it actually will not abort early in call cases either, but
that looks fixable. Imagine what happens if id_attrs or key_attrs is
empty, ISTM that we'll check all hot columns in that case.

Yeah, you're right. I think the current logic will terminate when all
flags are set to false or all attribute numbers have been checked, but
it doesn't know that if HOT's been disproven then we needn't consider
further HOT columns. I think the way to fix that is to tweak this
part:

+               if (next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
                        check_now = next_hot_attnum;
+               else if (next_key_attnum > FirstLowInvalidHeapAttributeNumber)
+                       check_now = next_key_attnum;
+               else if (next_id_attnum > FirstLowInvalidHeapAttributeNumber)
+                       check_now = next_id_attnum;
                else
+                       break;

What I think we ought to do there is change each of those criteria to
say if (hot_result && next_hot_attnum >
FirstLowInvalidHeapAttributeNumber) and similarly for the other two.
That way we consider each set a valid source of attribute numbers only
until the result flag for that set flips false.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#168Andres Freund
andres@2ndquadrant.com
In reply to: Kyotaro HORIGUCHI (#162)
Re: logical changeset generation v6.7

Hi,

On 2013-12-03 17:13:05 +0900, Kyotaro HORIGUCHI wrote:

- Some patches have line offset to master. Rebase needed.

Will send the rebased version as soon as I've addressed your comments.

===== 0001:

- You assined HeapTupleGetOid(tuple) into relid to read in
several points but no modification. Nevertheless, you left
HeapTupleGetOid not replaced there. I think 'relid' just for
repeated reading has far small merit compared to demerit of
lowering readability. You'd be better to make them uniform in
either way.

It's primarily to get the line lengths halfway under control.

===== 0002:

- You are identifying the wal_level with the expr 'wal_level >=
WAL_LEVEL_LOGICAL' but it seems somewhat improper.

Hm. Why?

- RelationIsAccessibleInLogicalDecoding and
RelationIsLogicallyLogged are identical in code. Together with
the name ambiguity, this is quite confising and cause of
future misuse between these macros, I suppose. Plus the names
seem too long.

Hm, don't think they are equivalent, rather the contrary. Note one
returns false if IsCatalogRelation(), the other true.

- In heap_insert, the information conveyed on rdata[3] seems to
be better to be in rdata[2] because of the scarecity of the
additional information. XLOG_HEAP_CONTAINS_NEW_TUPLE only
seems to be needed. Also is in heap_multi_insert and
heap_upate.

Could you explain a bit more what you mean by that? The reason it's a
separate rdata entry is that otherwise a full page write will remove the
information.

- In heap_multi_insert, need_cids referred only once so might be
better removed.

It's accessed in a loop over potentially quite some items, that's why I
moved it into an extra variable.

- In heap_delete, at the point adding replica identity, same to
the aboves, rdata[3] could be moved into rdata[2] making new
type like 'xl_heap_replident'.

Hm. I don't think that'd be a good idea, because we'd then need special
case decoding code for deletes because the wal format would be different
for inserts/updates and deletes.

- In heapam_xlog.h, the new type xl_heap_header_len is
inadequate in both of naming which is confising and
construction on which the header in xl_heap_header is no
longer be a header and indecisive member name 't_len'..

The "header" bit in the name refers to the fact that it's containing
information about the a HeapTuple's header, not that it's a header
itself. Do you have a better suggestion than xl_heap_header_len?

- In heapam_xlog.h, XLOG_HEAP_CONTAINS_OLD looks incomplete. And
it seems to be used in nowhere in this patchset. It should be
removed.

Not sure what you mean with incomplete? It contains the both possible
variants for an old contained tuple. The macro is used in the decoding,
but I don't think things get clearer if we revise the macros in that
later patch.

- log_heap_new_cid() is called at several part just before other
xlogs is being inserted. I suppose this should be built in the
target xlog structures.

Proportionally it will only be logged in a absolute minority of the
cases (since normally the catalog will only seldomly be updated in
comparison to a user's tables), so it doesn't seem like a good idea to
complicate the already *horribly* complicated wal format for heap_*.

- in RecovoerPreparedTransactions(), any commend needed for the
reason calling XLogLogicalInfoActive()..

It's pretty much the "Test here must match one used in
AssignTransactionId()" comment. We only want to allow overwriting if
AssignTransactionId() might already have done the SubTransSetParent()
calls.

- In xact.c, the comment for the member 'didLogXid' in
TransactionStateData seems differ from it's meaning. It
becomes true when any WAL record for the current transaction
id just has been written to WAL buffer. So the comment,

/* has xid been included in WAL record? */

would be better be something like (Should need corrected as
I'm not native speaker.)

/* Any WAL record for this transaction has been emitted ? */

I don't think that'd be an improvement, transaction is a bit ambigiuous
there because it might be the toplevel or subtransaction.

and also the member name should be something like
XidIsLogged. (Not so chaned?)

Hm.

- The name of the function MarkCurrentTransactionIdLoggedIfAny,
although irregular abbreviations are discouraged, seems too
long. Isn't MarkCur(r/rent)XidLoggedIfAny sufficient?

If you look at the other names in xact.h that doesn't seem to fit too
well in the naming pattern.

Anyway,
the work involving this function seems would be better to be
done in some other way..

Why? How?

- The comment for RelationGetIndexAttrBitmap() should be edited
for attrKind.

Good point.

- The macro name INDEX_ATTR_BITMAP_KEY should be
INDEX_ATTR_BITMAP_FKEY. And INDEX_ATTR_BITMAP_IDENTITY_KEY
should be INDEX_ATTR_BITMAP_REPLID_KEY or something.

But INDEX_ATTR_BITMAP_KEY isn't just about foreign keys... But I agree
that INDEX_ATTR_BITMAP_IDENTITY_KEY should be renamed.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#169Andres Freund
andres@2ndquadrant.com
In reply to: Kyotaro HORIGUCHI (#163)
Re: logical changeset generation v6.7

On 2013-12-03 19:15:53 +0900, Kyotaro HORIGUCHI wrote:

- In heapam.c, it seems to be better replacing t_self only
during logical decoding.

I don't see what'd be gained by that except make the test matrix bigger
at no gain.

- Before that, In LogicalDecodingAcquireFreeSlot, lock window
for procarray is extended after GetOldestXmin, but procarray
does not seem to be accessed during the additional period. If
you are holding this lock for the purpose other than accessing
procArray, it'd be better to provide its own lock object.

The comment above the part explains the reason:

/*
* Acquire the current global xmin value and directly set the logical xmin
* before releasing the lock if necessary. We do this so wal decoding is
* guaranteed to have all catalog rows produced by xacts with an xid >
* walsnd->xmin available.
*
* We can't use ComputeLogicalXmin here as that acquires ProcArrayLock
* separately which would open a short window for the global xmin to
* advance above walsnd->xmin.
*/

- In dropdb in dbcommands.c, ereport is invoked referring the
result of LogicalDecodingCountDBSlots. But it seems better to
me issueing this exception within LogicalDecodingCountDBSlots
even if goto is required.

What if LogicalDecodingCountDBSlots() is needed in other places? That
seems like a layering violation to me.

- In LogStandbySnapshot in standby.c, two complementary
conditions are imposed on two same unlocks. It might be
somewhat paranoic but it is safer being like follows,

I don't see an advantage in that.

- In tqual.c, in Setup/RevertFrom DecodingSnapshots, the name
CatalogSnapshotData looks lacking unity with other
Snapshot*Data's.

That part needs a bit of work, agreed.

===== 0007:

- In heapam.c, the new global variable 'RecentGlobalDataXmin' is
quite similar to 'RecentGlobalXmin' and does not represents
what it is. The name should be
changed. RecentGlobalNonCatalogXmin would be more preferable..

Hm. It's a mighty long name... but it indeed is clearer.

- Althgough simplly from my teste, the following part in
heapam.c

if (IsSystemRelation(scan->rs_rd)
|| RelationIsAccessibleInLogicalDecoding(scan->rs_rd))
heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
else
heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalDataXmin);

would be readable to be like,

if (IsSystemRelation(scan->rs_rd)
|| RelationIsAccessibleInLogicalDecoding(scan->rs_rd))
xmin = RecentGlobalXmin
else
xmin = RecentGlobalDataXmin
heap_page_prune_opt(scan->rs_rd, buffer, xmin);

Well, it requires introducing a new variable (which better not be named
xmin, but OldestXmin or similar). But I don't really care.

index_fetch_heap in indexam.c has similar part to this, and
you coded in latter style in IndexBuildHeapScan in index.c.

It's different there, because we do an explicit GetOldestXmin() call
there which we surely don't want to do twice.

0008 and after to come later..

Thanks for your review!

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#170Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#163)
Re: logical changeset generation v6.7

Hello, this is cont'd comments.

0008 and after to come later..

I had nothing to comment for patch 0008.

===== 0009:

- In repl_scanner.l, you omitted double-doublequote handling for
replication but it should be implemented. Zero-length
identifier check might be needed depending on the upper-layer.

- In walsender.c, the log messages "Initiating logical rep.."
and "Starting logical replication.." should be INFO or LOG in
loglevel, not WARNING. And 'rep' in the former message would
be better not abbreviated since not done so in the latter.

- In walsender.c, StartLogicalReplication seems trying to abort
itself for timeline change. But timeline changes in 9.3+ don't
need such an aid. You'd better consult StartReplication in
current master for detail. There might be other defferences.

- In walsender.c, the typedef name WalSndSendData doesn't seem
to be a function pointer. I suppose passing bare function
pointer to WanSndLoop and WalSndDone is not a good deed. It'd
be better to wrap it in any struct for callback, say,
LogicalDecodingContext. It'd be even better if it could be a
common struct with 'physycal' replication.

- In walsender.c, I wonder if the differences are necessary
between logical and physical replication in fetching latest
WALs, construction of WAL sending loop and so on .. Logical
walsender seems to be implimentated in somewhat ad-hoc way on
the whole. I belive it could be more commonize in the base
structure.

- In procarray.c, the added two includes which is not
accompanied by any other modification are needless. make emits
no error or warning without them.

...Time's up. It'll be continued for later from 0010..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#171Andres Freund
andres@2ndquadrant.com
In reply to: Kyotaro HORIGUCHI (#170)
Re: logical changeset generation v6.7

On 2013-12-04 17:31:50 +0900, Kyotaro HORIGUCHI wrote:

===== 0009:

- In repl_scanner.l, you omitted double-doublequote handling for
replication but it should be implemented. Zero-length
identifier check might be needed depending on the upper-layer.

I am not sure what you mean here. IDENT can be double quoted, and so can
the option names?

- In walsender.c, the log messages "Initiating logical rep.."
and "Starting logical replication.." should be INFO or LOG in
loglevel, not WARNING. And 'rep' in the former message would
be better not abbreviated since not done so in the latter.

Agreed.

- In walsender.c, StartLogicalReplication seems trying to abort
itself for timeline change. But timeline changes in 9.3+ don't
need such an aid. You'd better consult StartReplication in
current master for detail. There might be other defferences.

Timeline increases currently need work, yes, that error messgage is the
smallest part...

- In walsender.c, the typedef name WalSndSendData doesn't seem
to be a function pointer. I suppose passing bare function
pointer to WanSndLoop and WalSndDone is not a good deed. It'd
be better to wrap it in any struct for callback, say,
LogicalDecodingContext. It'd be even better if it could be a
common struct with 'physycal' replication.

I don't see that as being realistic/advantageous. Wrapping a function
pointer in a struct doesn't improve anything in itself.

I was thinking we might want to just decouple the entire event loop and
not reuse that code, but that's ugly as well.

- In walsender.c, I wonder if the differences are necessary
between logical and physical replication in fetching latest
WALs, construction of WAL sending loop and so on .. Logical
walsender seems to be implimentated in somewhat ad-hoc way on
the whole. I belive it could be more commonize in the base
structure.

That's because the xlogreader.h interface - over my loud protests -
doesn't support chunk-wise reading of the WAL stream and neccessicates
blocking inside the reader callback. So the event loop needs to be in
several individual functions (WalSndLoop, WalSndWaitForWal,
WalSndWriteData) instead of once in WalSndLoop…

- In procarray.c, the added two includes which is not
accompanied by any other modification are needless. make emits
no error or warning without them.

Right. Will remove.

Thanks,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#172Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#167)
12 attachment(s)
Re: logical changeset generation v6.8

On 2013-12-03 15:19:26 -0500, Robert Haas wrote:

Yeah, you're right. I think the current logic will terminate when all
flags are set to false or all attribute numbers have been checked, but
it doesn't know that if HOT's been disproven then we needn't consider
further HOT columns. I think the way to fix that is to tweak this
part:

+               if (next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
check_now = next_hot_attnum;
+               else if (next_key_attnum > FirstLowInvalidHeapAttributeNumber)
+                       check_now = next_key_attnum;
+               else if (next_id_attnum > FirstLowInvalidHeapAttributeNumber)
+                       check_now = next_id_attnum;
else
+                       break;

What I think we ought to do there is change each of those criteria to
say if (hot_result && next_hot_attnum >
FirstLowInvalidHeapAttributeNumber) and similarly for the other two.
That way we consider each set a valid source of attribute numbers only
until the result flag for that set flips false.

That seems to work well, yes.

Updated & rebased series attached.

* Rebased since the former patch 01 has been applied
* Lots of smaller changes in the wal_level=logical patch
* Use Robert's version of wal_level=logical, with the above fixes
* Use only macros for RelationIsAccessibleInLogicalDecoding/LogicallyLogged
* Moved a mit more logic into ExtractReplicaIdentity
* some comment copy-editing
* Bug noted by Euler fixed, testcase added
* Some copy editing in later patches, nothing significant.

I've primarily sent this, because I don't know of further required
changes in 0001-0003. I am trying reviewing the other patches in detail
atm.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch.gzapplication/x-patch-gzipDownload
�&P�R0001-wal_decoding-Add-wal_level-logical-and-log-data-requ.patch�\is�F�,��I���$��(Y�F���BK*�^;�r�@`Hb�+8D)��������lX��`�g������0W�k��>����h��~�����n`�z�A?:�|��z�{�:��{�s���c����vg�
��3��y��|9;���'��|�On5
~�{����}�F����A��c��q����v��������zx�~�=������V����5kfp�5Lg	������[f�KS�,��gfh���|�Gd��`�O��Jvw��.3\���oi[�����&~f���}v���]ga.7��K�V�r��4����e�Oy����P�����:���k������o�����?���D�o/}�:���V�;�,�&.�u*�B_s�n�k�[iW��Zy������i��;���(�rlT�����m�t��7p"�-���
~�=��%���(�+�yn�Zr_��.�K�V��W��s�>d��A��T�6a���%��V}�	��,���������ka��.�^��3���z�f�hV�*��	���$��,/V�Q���r�,`�
)�����%bbJdA��Z3t]h�-����>y����i�AXf��v���d���,F����M�	j{u��,.�4�vw
s�`��������y��]�vt48���fS�����Z�}��
zwr6���'������5�5�����~�r��d��-���h���S���.��6��x6a�����:x��g'�>_0�t0�����l$
�H~<�m�����
j�9�"]v(�}�:��I���`wI���p���'x�W�=�
�������J-[7l��?�f�����!���M?Xq�b�P*
8xPj!V�����*��dl�>��sZ�Nwp�k+�-@����,�[�Z�X��?~����������{�+a�`6����M�a��u�{�5�9��ab��U(��k���o�|!�����M!��`��,��0-`k�^3V���f�%�XK����O4�s�&�<t����;X�QJ��%���j���m��r�s	������xrqI�J��}0�����d�c=���;�D'�F*`����-#������
F�is:�����%�B&�:��t*C�W>������s�l���Q���T��SS8�G��q�CBn �5����;����6p�8���L
,�~��a��T�:��b�M��5Zd��A�"��
�b��,Z�������.Z3��v*"
���aC���}-X����
A
9@���(

w�4K]T3�y�%C�����C�����|�/��#O4��#'��s�h�d���o��]tS��	�j���+�c
�+$
���q DP�6���Nfx���f��o��u�VG����a��+�L�o�5�	�"�s�0N�RUq�p	�J�����h-�
XF$�?�E��&/]�d^-�q�,�}�)�&�o����`�k��Y_$@t�u$��aT���vbs{���	���7��ao��!�T(6����pa�u���?�~�h	���<i�^dj&�0b�i����)�w.t��&�i�f*��7ZfqKb���tA����@2�,1�����$������4��+����Hu	��# �1-c^��1��j�r�=�i��6���,�v4��	�Yr;*���Y�[.U&�C����I�^l��e�&�r�'#0v�������Y�Y��<+����5P6`�4�B�����s���A��Iw���	��6�q��5��E����_��\�
�.�?�{~�IXe�x�EOE
���t��)D�M6a��9�+��P�>�<���`G�k$-hRd����)���T��9��hd��j���D��d(��l�2�2	��\��'LE�H�h{��n�$ �!��+�P:���1U�����Q�1�d�m�q�'���L�+�b��Ne�;;��1��]�z�cHi/X1.4�"
D[>�?��5������imZDt�c�~p���ud/`���|�-`gX_��������9&=�]�N��gl|qy=OcW&��V�8���%L������2q������R�F��6w�B���z����H�@8�u�L����:%������#v�G���d�AI���H�
����A\\k�!�d����N��G�}hJ ���s*��]Z�CUg#���cC���i5=Ma���>��
pV��� ��D�2n����(h�x��#V���3�V�K��x�~X��O��9T���o�JV"��b��F�&����m�DT�))JT���c4��G�}�o
������<i�`�6�)�������t-�'���h����?�!������6�������s!}�Q�J���� 0q�K�'��b����
�b^���3"_�0�i��h]��kM����H��tU���f2bDm�02����=�l��a�@���]���7��
�,|,�y+�T*�)������Lj��XYES������q�e������g�����a��l.�����D��JD8=�ci4����c$�M��m��]��3YA���8�;0����uE ���.��?�4o��4�e������@�}��n��������mH/�g4���������w�G�Z�<Z�n�=����22hE�k`��64������<{0)bg��a�����vV��~P�4������e�g�������p��R�_n�`�_������J�����m�C���x������������l?����d�p�����b�A��Q�>N���5����c�_Mn5�����K�pI�E�p����K-"���"����&nG�^��[gY=�a�B��R$'���F�����i3��U�+0�!l����]�������������k`B���6��QO�'�.�c����U�S�RN@��s[3*���^����6�� ����I�_���N�����KFW)�5��a=L�������w��R�_k_��v���@^���%��	l�9$F�=Z3E{����Q��G�������;���n#�{��<I����������V[��~����!����j���R�vvp)��'`}�_.�ng�����)u�����P��'�L?�>�������5��Y�Ud'�V��k��S�%�$u,"�xi����r,[���W${x��D>�d=����O��G$F�eH=����%��uF���L-��0����{M!"����qt!f`���8�L��;����=wa��RH$�@,�[2���j6��dj��_9���n6&�����������0ki�3b#���)w!�!���8(f��x[b-z�k�d��a���a	���-��V�����M&�����&��������;fm"�	��%����$���1�{������3i�+������hnJK������=�J��@����!
�����{��u����8��6��[vT�f��B@A`�������a�������f��du��8+�CJ����g;�1��j�N��SDv��a�������XD2��`�@x����3l~���xA�Z�[������ '���X_X����t���(&UT�������j�w�������&���0�$����RK�Q���T�KP�u {N�Xjx\�q*v!�9�aO>����:�������N*p���3��_�L�I���C�:P��v�<����-[�����3�3V#Q��ou�<������9���8�~��\���&;&!j���[��A���0� ��%�Zr�0�9�1��O����)�2�B���E�W.��J�A�6��xV�+����|o �fH.�{M���!��vF]q��[��h
�����
��5�]k8�=��-�B[���P�/�%��w��������/)\K�����TW}�X��r��8���q��bs����_��F���=�u��.t{@DG����oe�}r��ycII!0%vk���������9��4����n�Tq�J��V�E��'��P��
��)duv���v��l����k�fF]v�����D#�RI��!���
F�����<��KR���j?l_R��NY.�IBE��J+�w���d:N�l�{1fb�"	a�Ozk�7~��F�L��w6�D7�:Ytd�{��b�(�jr�S��2v�	�����2��,�8}������um��q����q�����_$���^�U�	�?h���6�N�����Fe�r8�
�z��6(qz��;P���W���>b"	4��4C}@�qL���c�*
sbn8���^��l�O[�C�]��dIa�dj�6�\�d�xh.��2x-�F^���nr���������;l��z�������\rC�%-Y((���,��S��r�B��p�����=��j9ck�K��?'nX��%�����m�%$:�,���Kpc��m�%�;y���H�LZRi���
,i�(�d|�O�0ab����<Q�/$���|�H��N����`oDMa;�N]�4���6n��n0��a��PI��P��6�}+Ke�`�����2I�����@!�f@���F�*��7;;�{����Z��9@f��o������X��z���Reb��_h��o��%Y���'K��	}�s�rH+���!M���,z��\"WH83����=�B�)*u������V�a8o<o7�3��59N&�e�G�	�;x@�ko��g��sytv������	q�LfZ����$����������<�z��]�H0�g�����U�/������d�q
�p4��=��Pr��Es��J5}��ywS�\���R6O��j�7_�� yFZ���%Z���Sy�d3|S�6y+�N�m�Wo��6����
L�3j(�G�G��a�</�j�A}�+V	G�
���@�t��~e����R+1����������G3v|S*��hVr�.���y�LW�BJ(Uz���Jx�IU)<����N�}lQs,�8&5�RC�m}��tI���n�[������)�o��f
.�7�(
0�>'����c���������L.Tb����D���������T�8���X���������(�
}q�5���h0��	[@�[�[�$U��l��:��\��.qV�j�sN���r?�����I�{���T�YB������k����T*a�.���y���a$���<<�|`�a�)��;���(J�Ar�V}�CA�����{�.��������o�;����2�Rpas��7��*l�����
�PG*�����R��5�����:e�a����S:i�(�(-�XL�X0<���9�0l�#4�Q�!���[����+pl����+�7gJ�cZ��<���tx�F�����[XQ��V��q���mk
����1���|,q�l=y�����pe�����b����"u!�q�����O����n�n<}/H�z� ���IU�[jR���I���dw ��e�xC����A����w�Y��O�)� �k�F�������X�&-�K�"#�5��"�	��iE6~�2\��p_�!�R�azx�+
 ��|�|�L|H|������-7fT���yE����7����`B������W�w(�����#�Y-�F�2�o3���<r���Dr����y]>��`V���z��\q���+d�*�,��{���W_�����I�@k.�&&D��v�����o)���Cq��5���o�%���i�����nO��(�
q��+V�'��.*�T��Z���x_����w{�bB�H
Y6���k�nG���_�8g�R�����N��m'�Mbgl��9�sth����D�(�����o�� 	���w���|��E�B�P�u��O��D������d	x��iH�X���0�z��RI�Q/)������H\)�������6����{<���J7����nac��*�1Q�Lbi���������<��r<0����-��-I�'�)��1�N�UZ�+�s��R�
�����u~���T�������z+1�����;M���+w�Y�=�X������� ��E~:���"���L�_�4	�����X�\,��t��):/�b���S�RlE�����z�'\�����#�Z�����X�@xN�W
��������=k����������n�cF�Q�)E)����)m�{��M���E|m�Q�hkD��+0DG����d=)=t��(��;�	K`��l�b|���c���Ps9c�j|��|����6��I�@'�,��P�4�9�K5skbYC���
:������)�#�+Cy�%B��V��}�Wa��^#Hm�m)�Z8�}b��I�h�:��*	V�U@�!�
��.������~�)����d�+����s��0�=�G�B�h��o.����*,iX�WQ�i3�$����//��&�������O�	�a�5��M��/�����v7�]�	_*m���QH[�D!����QH�*
�H��B��p�e�:M��-���'�W�����?D*���Y�Z�8UR�*��!��i�J�j/�Dyv�x�A�����>n����3Pb���M�������(M��U���[��5�e�=�*�v����td���$@�7��x�qbT5>������M�,EtO�s��2������F����q?^\A���>s�-�M�,mI���/��H���F'�TI��B�-���:�m�Q�R�]����+�8�@�7E���<�����#D����'aG��a���T�����q����-`����=�npbDL�Is��P&��/�\]o�
QK��JZ��e�
�o5���[E��\����(��@�P�g�����"l�itG�VTBu��N�R2�unDL+s��YF'KO	�T�r����1�S$
b4]��-�+%a[~����Z�Z-'[�]$[jH���8�m�[<Z=�H��/���*L_t����*�����B8�����#�4^/��6#G���J�d����VI�yp,�od{��&#�f_�!IEA#�����K|����N��Z"����jn��-V��,�ox]�l�%Q�m8M���t_�c"r/N �"@��`����W���������������X���-{�-I���(��=����{�F�E��M�[�@���W��z3s���4������6���u�mzix��P4�Ry4B���[s�5��Qr-=2}L�6�Jl�k�B}V�;�O�M����_���Cc�3���}�I����XCqI���!�F�Z�y��*e�yx"���O^�`�.7y�'S�TG�������0��"g$R!��� ��-��(k�������c���������Y�*"S�����x����R�,W��gK�7>2l�848�
��[�x������R�W�����dl�e#�R?��ob�n�0T����G�!v��T��3���R����o�f*l"��&�����~�1�o�-v!��������H,:�Fo*K�F�"B���n9�����!�xo6%�	��br��~�<Q�8����:!�%:�.N��h�V����������c�~�H���`���T[1w����Qv���{����>�����*��!<A��d�`Rb�)&��N�iE�\���,0�"�Q��U��r�So�c�Y�*�����AE������������%���TLD��<f�����,���"���0�$���tP:���M��w'���4L��J�b}���9OT?s����b������������|pP�i��M��F���<D���W0hT� �������*Kk���-,Xn)��l�>��\��L�5{�\v���=$�Bg���k������2�� ����������-
��az]���T� �6Z��^,��A��|�����AQ��2�(4�����6��������N���o(�Al+���xEoyH��������$E|}����M�v��7'����Uq"����M��fo�rb�����-'�[@j�L��M���sE�<�))_���Ln���.m{��]:��;����#v��v�n9�����?:<�0����t7c������Aq�
�R@���(����Tp�x�4�WlraL���g�n����K���-l��)w3�Q%Y3�*QY�t�0�dLS+#Q�dLS;O��A%Qp�2����6jg��1������?;T����ju����P��aZ�����JE#���%o�M���QO
�K�M$�=y���P�r���Og�N�g��&�z����&�A)�����s��RI��rA�&��G��|UpX�"���n���^K��`p�}$�T�e�=�M�h�X��z�IK�.����E�������4�"oa�P�������M�������g�`Q�a�U��bH4(���u��n�6g��.�a����r8�������>��'��X�x��=�gQZ����@x�X)�E~�T"g��GZ����(l	��n�:��2��������S����=��Cq
�#>9��?�!�V�3��#�FH��G�� �Y��a�;g�L�7����L
�� g�e������@�Y'��(����A�z�*�N����i�
�u��v:��Z�p���*��eX��Z��OW�J3��F��F�!��K� �Wq����3D�,;���A^Bj������I7�������N��/�|$�"6�/��hb*%�V N_PK�QY��=M��G$��$t�����"��=`�*��/D����8Y�K, ��D��-�j1���O�����>���Q�!�F1�;�T��S��(Q�����U�'y�T�*���(�=���Z�iC��C��V	��#t3��z���6ZE���ipwE�7k]$c�-���`w�S�=���g��������"a�p�!k��}�u���+��[���WPz4��r�=N"�����U���S�b���X�(u�44�T�x��E��u�l��9��k��.�A<���m�y���"���_�laTJ2F6��A!PO��T���~�	���_��q�2��{l�d����������0��$WI��8����O�O�`YL��7����������z����� %�fE7����D�g�����zhuu�J�Z����� ���@!&�r�)��j-�i�|��XJ-d��M��2F�����iv�0���A��\����_
��l������>�0wKG�<_b�F���o��d�)���{��eho1���!�_��Mc�=���{������9{~W�q��`�dZL,�� ��S^�7�#����<�bU��NVVvj�Q�E-0|n������Zeo�P_���:��m&��������������r{���+X���J��L�h�fxh���9�R9O�I�E=5���y����fg��q�%��$6��IHV��%�������f2���s���h���0���lH� `�)$��1D�"�E�&����v��C�a_�F�CQ��O��]��������|�7g��#z7g��	��p�j,c$��F�I�_���GO�����/���I$����i���0�It6�U�ht���{gW�`F7|�lZ�h�,7���'}
����`��)��NQ�@Ux��v*^�����"l�f�j�%GR2JP���8����~���(i�R�4
D����d��Kj�5�?�G���;j�hL�����F
�b��W��x���,G�+�=���8�$7�L��7�)���,�q�����!,sO>���+��L�q8	u *-"\|��D���[.8/t�����7���AG�(k��BZ�q�z�3�4�����4!����^|��F� �E��87Vs��\�$�.*E�mK���0j�����V��_m��~�2�1���������]���aH>�n���������:����NO��o���W
�-2��%�aA(+�������}��Q�������&|s+js_�V�QE"O�'+���(����}�'��D�y�x��C��<x�
��F�9�U���!�|�y�w��n�h����7
�.2�>I�Z>����"��(���Tp���x���a������/ct�	t��e��!�q0�*F����Hg�R���1��`V_�/A`�]�����x\{-�n;=����o1��b�k�i������KC����r�����(���
�>}�^��!t�����'8��-mi�T��5�����i���_'l�Z�7�
��|1��pq\�?���6�S����D.Kq��	���~E�Q��V��7h.*u��p�;I��k���9w��
�w��g�0������2:�'�f���&Cp���"���_�ud�����-�oyx��/�%X�����'c��`6`��7a�;����C�K~���GJ^���@*��<D�W�v,LyF,z�d:�j��+�I����>x������8!��
����,�g��(������?>����f`L������az���>����7�B��s����`*��2��5���
����>U�}�m0�`�����5��s��D��F�!��ZX6�FCa��z�y�l�s!M�`��"�[�'����������&�
:����h�����.��t��/n�N�<�d���������n����<������(M���O���'�,����6���+t2����b�Nv������K�e0�5qM�=.��.l�#$���[�����^�eT�b�Ci��QX��E8
���}��>"N�-c��f!#=,P������t�r��l���|9C��7��hq�b�9��}���
3+&C�7��#`�������FFf"-���it7�0�QY���a���>"P��T����������?�L��|3�;�\�������T������#���v�Zm�
��a���j�Vk�R�����R���{Q~�=b���m#=X�p1�?A�&��R�&��>#Y���U��'���8d����C�����I{�(�\��C(�s�ior;�9 ��yR�4�p������G;R��'w,kt9��}�x=9*�K�A5�H�HJ�!��e�B��������?^���_�����O�>����@|q��%����o}��V�:v�W����k5�Z|�������&2��1��!{����F���N�_��7�ox_�V�;0�����7���'�'oKWj_�T��
n��A�����/'�'�,���wu�(���e���������
 �;���52�j�oGk�7�_�XVM1�����.��������S�i�����j���5����h)w~eqz[�nuiz����t0^�z�Q�T���%O�(=�"���'��dpv��'�y8�S�/�{{��������J�>%��� �[��t�?f�$��"���^T2��i^J1n���}=
f%D4�����b2%���]�\5C�^2)�J^����p�&����8��a��u�Cx�4^\1��:������yxp���Q����O�.����Ha�V��U*Z9�B�x@T��bQ�)]
�9{m���%	����g��2�|�Z���j��������GZY�t�	U��"�Cz�� �����,�,F�A�>_��h:���>�?a�~l~��!5 ��m�X8,�� ����76eIW��{��v#�K
���B~O�����
bx"<E��������P&�c8�:��I�	�$��d��,����KF�{��k:�&q���������YW1u�����]��@v�.4`~�]�V	�dNh�(�z+�!U:3��I��#�m�������_�~(:�g�G)��{�r�N�9��NF�{��H�pU`P$U����J�Y����$1A>����C*�)�'����A����F'���E���:��e� Y��.�����fZ4��0
��C|A��7�O�XsU�
x��7�{�te�pL`�({d���T�����*��A��I��j��k�I�9���t��B��~\y!��B��$�!Z�����jr����,9����-�*	��M��]��a��`�!��M���9�qf�����j�^���m4�� N���v�s1!���R.{��&g%#O�|�mQ
)K�7����L#V������^�T����A�*�] 8uJ��1G��a��z�rK�[}�Jg��tN��h�=$8-s�A[x��',$��K
��p��gL8n�eM�GA2�B���x�d��a��"���X�d1!���:��!i�b����S��^��8�9_�S�m��neAeoM��r� �L�Dg���I�q�Q��L��@U=��s��G������X�������2�!}��W}�fzf�R�`�fN&l�����C���?�<;?�'��ND����k7�����&;��_t������Db���l��wnz���`�������-e��sKe�W�%�*���l�P�su�����Gb�������na�=����`�mqL��:B0���aX���:p.yx���������0����f��)K�N��B��v9�
�	��u�	b���64[d����E�0:}?�b4=1�������&��,�Cwg����i�*�W4�����a�a���	�zbbn=�@=�H|<3*��|1�?O��R��	��Jz�s�&��,�|^d|��[*������	6��..N^��=>�LeW�J�W����1�[���3�i��,=X���ov]��^��->��UN'M9�t%����Vk��@1��<������"1����;��s>3N��	���A�>�"��*�_>F`�2�FX����U'v*�N��^�YV����nu����m��8�s+�O�\����W�i�I�]��T�8���Y�.�����Ja�c�d�:>=Z�w���!@�+�@Y����!(�w�	���~��-��P���s�f��a����d��H��da)��$�.������:�B�wN��;��H���l/�o;
h?A4?fB�}FL�r��I��y7`>"5^��������9/��{�������2��@�x�k$_u�XT�	�`@@���G���b1�O�/}��Fvi2\��b�K6���u��0
���0���������-�7���	��\^`\S��'����8��*��a�r����S3��s�����q����9�����)�����yrqry�2vy,��yjj����)[�x2��kQ���O�m��Jr�������}�#� ��OO�?�yg��LKi���.9�M�!�����v��5-E��;1����@���#����^-9����v�����(����;?��K������^��y�^��6���������;���|��	�K����N������z*�j��m7Z�j����Vc/�����O�%���l�&��O����U�PY\�<Ec �;���s������������E#�)�<��
��O?�:c0��i\�����MB���5��Yf�U�����x��%��4OB0f���PSZ��F�N��
F#�=���l�Y����&�L��d"	?�3L�����c>�<
��\����E�r�;���*��4�7GV�I�0��������q8��%m�TX>OX��b�U�#
��I���sJ%/'9�%����w��K����|W�Jme�(B�D��4g�����X���6�Z~��Vk��_�-Qy�d,�$JY����W���w��[?�(fxv=P������xfip�lx���-��'���y`���*	�w/���W����S�<�"�����z�����~��;VmK�����	��0����j�k��_�{��-�q���Ek���g����A�����"�]���:"��,��{It�{�B%4�.��wg�*��Sw!�Lkp��{�L�WWW{K�?9�dWiNA��{�l%�w�AI}�� �%�)��VN��{*P�T��
|A��]����*���Jm������N{{2�v�]T��;��8���9�=��;�0u���!���D��*1��G�(@�a�i	_C��������T�!����$c��WA���z��$��~R?3��0G$���|�v�C�U�h��L2#,'@��525��5'���&���T��e�����.�dx4;�� �p5�0���,7z/�M����!����!x^z���*i���u��d�wp.)�%��vi���"(;Z�����e��=�1�s�'�Q)�bIRT���k
+X������b+pT�g]�dic��C����`q���}yp�tD�l,1��^!,��p���rv��"|�
�����C����8\�L;t�����N�2��5��OIj+���>����f����M�L��?�K��������dw�G��
����\�)���m�ag�n%��`��j�~E��1"\�����y�=�>�,�����V/I�K�e4��
�$��cf]����f�u\�h#����B1�[��Hc�,��l��q�����!�}���\�c�+�
c��1�nX�����g�*B��z<�A�P�2IaA^�4����ut#�e|O����Ru�{"gg��R�!�������O��������u�Tk�m�
'N�rBg�0�[v	X��'o��9UN�UR�$�<�%}=s/%���P��(f��'E��C�������X����'E�1^P�f�jHV;���"�S����C	*?Dn'�1�����[�z��L��&��8���3+��G��M�u2�*Bsq"� Kyz=��>��oy�w�3]���,���}>�!��.���{�Z��=&��k�ub����2*�#��3�����f�1����d��"Q����;�S��{�g�`>(��|F��:�##^����8�����m�����-�$I���?��F����C���cR��V�����U���z]�V�$��"�j �+���0���t���**�j��6t����x����n�Z����e���-�����[���?���=��{�|l9z<	���~�����K"�:5n���U�PhF
�Fz��+?�,��P�G���{0L�,�������g7�h���MD<�pv���Y���;��si�q�}��CJ�)���q#�?�[fuI���0h��~��t���=�F[�L�Q�BS�����#�	������F.��g����QV���+�	 x��dG\�	�J,u�/\��_�A� ��9�y��������x`��?�s���"2W��U�9�U��&���av��6b&ii1��1��hpl�[����U���9K�}����rzq|~�������pb(���J����N|[�f�p�X��bk��zq�w���1"���mBi���X"�W<��V����H���Aq04�=x�� 'h��Q/�jd3�p��K��������|U\�U������`R���?�El,����!���1A�JCm��cA���U���`]�}���%[����cP,�X�3��?X�����zdb����'*�����u~�9�n9x_b`$��������2H�/LmBR��J����*�z�8��I>i|�u%�Z����
�Z���/AM�!9��[M���Uv�kEfb���Pn�0������E{�?Qj&�#s����s2�Pb��N;�
�����)7����h����(~������ ����������������A������5�����6���E�cEy�(/�������E��"B���)��sU��z]�9���-�9�2����jH����������w<��&�I
�����h��yS�&���V.K�E�jQt0)H~Z�Y�����~�gs�D����9l
���i���>�X5��~��<{�����wp��yg/�������%����� W��s�����
��-kL���u*�h�8�d���]�t�#V.��B�r&��id���s�(/E��_:�������5�E�?%��������0�.9�1���gr>��f��������!��FU~�l5����Y4g6��f�Y�.o����S��oew`�,��h����]E��w'��\L��B{���F�������u?����X���p{�&XH�q�&``�c�r�r^v��������6`d5��n���Ew<Fg1��C!i���r�B>�m`�����V��yD�]���2dn��^�qx�U��b`�A�Q=��pL�~=��P�+��J����+��\��W��&#~��/>ekU�s%�
t26a�Wi6Ay.�|�m�(;����F��"�����P�1�4�}?�D`]qbj��6�jA�\Lt��y���&�9c�Q��k����v���ow�Q�iqk�~K�K���dJs ����(�V�&���%��n{� ��t����g��#���	������l�7t������TE'
N���/n'	?�|F��;�U�{�f������H�\�<�U_'��C;��W��r���Q%�y`I�\2�t���U�k��T��-k�$�W�UN��XH3��X�Dm��LOM<j�^����On/��T�S�TwW��`�X�5z���Z���m��Y-~+<������e�!q�*��TY�������a�Ml9�@�_������&r�_%�o��3x�����l�k�������)L&�q@E�'�)��
E���W�	��
X=G���C�
���5��;G�3�s����:��\����,��VW�X���':��s�5���~���d��E<��/��P�#P|H���j����;L�c�C��"�d���Y��Y������������
�VW�����[�Ik-��O����I����3���\'&����QlQ>!u��'!%g�a�i�@�0�5`Na_��T#r�����-���?�0V��f {��[��OS>4�pT��k�0�(�0A�3J�="O5�����"(��D�A#S�&�\�~�I�E�������� ?25����3f��y����l���|�r��������	SZ��z|�.�D��ac^!!3�-��V��]��1�.��d*+�H�T�R	��E HF;�(��D�b�x�����s�s�����0�7�\9�����7���%A�	=�����AiX�U������2���E��)7��(�L���G�����m���V;����r1�2�b��s��K-
z���f���LZ��Zp�@?2E�H60����8�R�g�O���`��\df��xlRC8�c^r?��g?7��w��GK�������/F��"p]������`F��
�v�1�	X�2Q�����I����vk�Zv;�V�@}�6�d�REHmQ4%��>�p��3ho9��V%L����q��B?>��2���vE�Z��o���;ac;p��Zq��������~`����x��?2�-tF$��f�EN��g���t��w)~i�>}�"�Td!���,���56���wo����|W�"���t����i��+���Z��]kK1�mi1��f2��g��_��>��=�n~�gp�e&���������f3��u@����K���;?-��/��s
Y���?�E�.J��V}a��F����(���>����TW�����r�Zr��YwI�clg����h�%�R����+�R��i��������H{iA ���^�]�
�!&����o�W�`m
0002-wal_decoding-Log-xl_running_xact-s-at-a-higher-frequ.patch.gzapplication/x-patch-gzipDownload
0003-wal_decoding-Add-option-to-use-user-defined-tables-a.patch.gzapplication/x-patch-gzipDownload
0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch.gzapplication/x-patch-gzipDownload
�&P�R0004-wal_decoding-Introduce-wal-decoding-via-catalog-time.patch�[yw�8��[�����eK��I&�s��sl�������A$(�M�
AZV����~������n[&Q�
uBo�d*T����~0���������������ht(����>���������;��Do��|ChN�y��J�7��c_�S��/d,S�UE]_=o���:?+�#��+�xo z������������5������N��~:��x'��w{��s
}�%~�O�e����{
��{.�B)<��(7ENU��;5�7�P��i���J	2��>mn���JR_��<T�TLieD�'�7��Z��("PBk�eak@�R��VLE��J���W��&2+
�:��QF>p���v�
���$#��Kf"�g �4�MO�b����gI�b�c��$U�'T� ���+�^��C�"+������@M�HOdJ[��Q*��H�l�gb�DV�y�M�qE#�����"La��#EK����?%�)�d�Za�:H%�/��<�N�D�������V��e%�Q2"���aL��'�x�.o�W�^^�_
?��tEn.?~3���D��QDz�H���T��d(�L�TSE\��-a7����A�f	� tX�P�AH�3>lB��x��gI�������#��o�?o���-���������&��rJZ�L�����GLF�,$~��,cdj��������-Tt�h����#U���������d����5��d��r>���Z����R�q��Ji�&<����H"�T�t�d+�&@�?K:�8^2^���h�4���T���_�/:O�(��7��%U��S!��S�����������$��dB���Y��1��M��w�'s�eL2���=X�gu��-N��d�v������oz��i�e1Y��d��L��R7����6Wa�[�0=z������2i��&��a&����;�*n�'/"U$>�/9L�:��!~�c���H!��8����W�%�Ss����8�^�I��p����Uz����q�Gw2M�;�=y����NS���� Q�JZ��(9�r��������Dlo�L��D���N����������1�1�1|��3��
t�}Nw��<VXd�P,�>|7$��_�gI��T/�M�0��_���e~�L���.��h�U����������?�����R�y'�<_c�,�8����V!��
�n��!p��b�� 9ZVM��2�n?��.���I��� �=<�08>����v�M�O��������"��;��
N�c�����8]>��,�R�T��n8����h���B"|�4��%p@�Z^Tgd��e@k?�D��Ft��7!h�5�n0�*h�"��
��U!�iH�o�{�]�zwR�t�����A������s�k�"�f� ����o�*�f_��Bh������.r1dm�����l���U5����]�d���7�V#�Z���&PaV�������^��V��C�l-U&z�L�zV��[����C���-���������M���"j2��I�+��<z�����A�J?����������2���P��e�4����!�y�x�����d���Q��.�j��6�/5g����H�S��>D����h������	��w�\S�D�iN5xE�
�"e�Z����H��l7[�������\Gt���d!��s~G��N��z�T 
om�;����"e�P-���z~�����w��������]��C�E��G�m��C(�R��QIs6�?�3��p�?e=�w�.����]r��[q������S�)w�����&Wb"8<y#��'�������G�i���/�N��`����`>
z��I�
���71�.��J�%"����g�Rj?t�K^*D�)
���_a Z���������n4�[B�,7��T�a��K�pp'oF*&/��#��r`��v���?Z��
��;�
������^"�|��#�����&A���Ys���@��S-�B�(��)��k�u��,��P�m��v�
n%�g��c�����P��2�6%��
J�����(��MB{JC��U���-f���d:��(����(�`�f�3T�6R��t�d������@�N�+R�-n�{'4(�yb%�����r>���g3*${<:>>�PFwt|���8k������U2���OY*"M���Y[)�6��Va!]���eD���7�I��������������|y����''�CkpC�5���%e�hQv
�2��S��������EG<)�p*��6N��Hf�����I���u��q��^�|�k��o������nw_���8�R������e/�������oJ"^�h
V�_��O\���<i��v���z�Q�f�����<������OPJ���FY��UNLe�;[U�[P���-(*��/�Y�Hn[���������w�
��W��
�K[w0�NGk����_����-L��-���?�}I>i�s�����$
�$F����5�?`��LH\��Hj�x!�x��v8k�N�JY8]aFi��/����3�)i5&����6�
:���DmR,�X�b#f��H'M�X�"a#��$���Gg&�%y�)�a�UW�j^���o�i����������3z(�<M)i�	����*�M����?0H��3�Di��rCv�g����e�p��0��j	��9�K�'�$>�������_&�0KN�L@	����r��,��_4n����/��I�C��������<�C4O�a���NZ��6M��h��Z�*�U
�IJD�
�<e����'1N�2�YF���Ig�F+��\�^�oo7V0#������{yO�*���b�p���K��C4W7���S�$]P�������gF=O��Dd����C)�t����[����Z���bv;������9���uvSpI�;�	�6��x*3oB�9wt��* �3*�*���-��n���h�a=k�T�}lKi��Iz�>	J��q(�\�'o���3��������d�vx�AO�GY��iU��a�{�1�o�j���*�����	�6f���Q���C�R�;S���If���������^���xB"Y�|-o������i4pYM�����2%G27�'��a�0��w!_��J^K��	!	$l�N�e��L�b���V
(�����h�~��F��
AqH�����[z4�!������������!�-�C���y�H>J_0�����Q��!���{�)In�����
�\���1e��an�a�@����qX�P���K�@R9r�o�S�z�x!�gE�����_"_5�
�K����U1eW]��F����g���J����3�����6|�D����pYI�^bg�$WI���5R:H����,��W�:����|��i�m��>mB�����B�.#��
_Qd�	EfP���'�C�F	�������F*����z�>q�U��u����=���XxO�	�^�#�(��h�
i����*�Y���J����0�H��;���TD�in?��i��>zR��Z�KJtjK5�)a�q
�����C����������{�0
Ed0,����#�Gv��k�'�(����Z:�����������;��:�@�H�:~o�z�������3�B����^^]�����[3����F����#~Ero���E����<9��9�\N����&7I{��J��ZqP
���G��`��`�����x<��n���\��2��"�6���^����-��n�4���*��61���P�h�����&�f8U!o���E��?����_��e�e}T5�v�<��j�l��@d�(��UY����e�e�b1���\��|��p4; D#J�:����f^H��}o1��<r����+�s���u�
�����D�2=<:������sp��R�n�n�@h����{j,
z72�+�F9�;�?�WKI��g��w��gH�O`{+M���6r��.A����]o*��w��K��}�A�$7_5!������g�'p�j���+��i����YOw�����T���t������-�����rZ~[<{.Z�KG`3m��M���1�������������p�uD��Nc�x�TV���Ah�����H|�����$
�Z����q���������r��.{ ���~Z��
R%�w�
up�%GIj.����������}��a4���c>�eh���t���M�`~�������_���1��"WAz����'8����z���*�����uT�F�(#�/��V�r*�b�\�G���p2WO��c�1y������N�����H0�JHh�cb���U���E*�d�Z*[�����F���j�.5���H�$nSS]D<��Ka�Q��=���5N0��}�"X������
�H$��T���$7J��u'3uO �	g�,HW�~"����si�d��E�;��i�9���M�����$��/��K����*�,eD�9�c�V�p)�8�d|�R��|D�3�%Y7�}v[ d���;!t�+g�����%������a��rg3Ul���Z����cW�}]?Q�Iz[�	2� d:����9:�t���7��[���*X�)�*r4���Bd�/��}��:����.+EC��2�RD�$��-��-
��d#*��E���6&@�L�o�d�/E��^��`7)
(&� �	xL8h��d����T�$3��lOa �����U����H��f�Q�s����(����3&���E8��Ra'����e3���5Kt�.����$����x�%���L�� ��0{����)�UY������Foa�be��Q�4.�y�-���rJ���d��w��5�'��������V��'4�<^�r7�0�2FU�!�q[;&H��C�&[t���Bd#�	�U&��<.�q���l��ax��N�p`[&{ ��h����R����RU�������H��0��Ijl�.L��p?�al�6pH6�#m���I���%�W�����3��2�����-U���d�m�X�%�z/�� I;O���r�+IcR���l:�jJ�[���"GC��}��LM��t�h��y�FR��i��
c\���/�b�A�Dyz�rW:�[�m�FyY!�pG�����y��*wT�j�[����H�������i��;�~wqsEe������@�6���*����M<CE�j�K}�n\�����~e��Zih�
rsah�6�r�,?�\��_dj�?]]�u
���<.����h���,����^���M�u�`?�KW��C��^���������R���	��h�8OVgi��9W��O
��d�M�F�
��CK&��;T�T5�B�*����`��k��]N#/'0I����]���|�Q��Qq�b�#������_x;�i��m�ub��Xmi���z��2�0�V��|��� ���(	��K�NDG�u����gm��������.��Z���V�NX� �>��j��!��+�!>n:��^!w������2�.}����^���c/�����Y��]�{���Rl�<�������7��������,j�������/7����k~����;&k&^E������fN��Q�+.i
=�1����kx}s~��5�r_�x��3���J��S�kMs�C�*+�����~�y��:�)��^�#�
��jZ'�������{Ws�����?��u�����{�it����l�����^ D�S��8;b���0������m$Y����W��k[�������1`�W������^JJA�%�Z)��z�;k,��Z��z��wn���g��Z
�����'�����V����K�
���D�[��O~��A������:�����MOhs�UI�7�DFO����]�u2
�\�.�AB�j1 ��!��E�������N4��K�k?�n�t��v�����f������������2��S�#s��P`���w��*}��O�L4�nE����G�R��3����i/,Q^����7n����^n��{���{`��z��8�d�LJ��4)R�	���������`���z��pd�9��f������Wv~�[��]����<B$����!L:���<��U�����)���
zwx��}�����?���L����gS���-U�R�+ppK4�����]`��x
�f#z@�4���X���������!�����f2�U��L��4�_7A��uA��;���\@��]��@����!�?~Q~D��x�!�HQ$���$������a$������,5����st�~B�'8>����VK�%7~+��Fp��'s�����1i��/Z�x��"�����1�w���t���N��#	��I'��tlN���VH�@~��$U����{��YL� ��g3)��P�F��qZ�9�25�������*i���4X�P:��bXu�BH��h�d��FP{�YG�hI;��W���!-���d����[�L��c���A^Dx8`''���~�y�N�������Y0��Nrx~~z��f-�L0�wx������>��������ys9�4���{�n��u�p�������*�IG0cc�w�:��Zt�LT�D`�?�c<w��zf!�B���\��Z;��*�8�	>)Tl1��S;��B9�x@aD�Z�����r�NR8����FD_���������.�����Wf���TP�i���4\���R9v��`a��@�+�"���Y����.�h����������� �����x��u"���;�lSU�m�M��)�#����E�/��������N#��J������d�e�u1�PH?/��tI<��9��?�7�CQ���c�O� `;�u�s�>�������D�Z�f0��7`b��a�����P���X^YV�D�����U�[��4`m����l6%��.���Z@��j� N��������<������V��H���A�tf-bC�����9������?�������{9��,����m�H�{l��V@�����yc��
&�{�}�A������_��}<{s�w��G�Ve��m��d�)����Fd�&sm�/����S��YD5:���O���]��1J�4+�8�M.P�>��-Q3����	=h�}��A���L.���O�ar:NF�yx���i���]D�F����������������go���N������j��o��m���e�C���&Z�)��>����q?��qO�G��R�]D+����H
�,���(l6�-�U]
�Z%�[+�YT
�ntN���(�����tz����k����|��#F�e�����D����yvjI�x������`g����rJ|����
N �/�?���V ��aB��-�p2���)y�%G8�B�|B_��YL�� m�`V���/T]�o�s���\ �"�_*��o����Q��6wKxH.<����	����;	��{3�o	<	}Zp����j7d���s��{2�P�`����f4??����G�{���G�����k@����6`�8)�qRl�����=�O0vsP���GD���g�F/��~Cb��4�>��8^����7u#N{hR49��!���D���'��G�3��U��Ms��P��L��5��D�"R
u��G�G�����O�m<����bT�0�?�����^kX�q����U�d|���6^XE6�UlP<��W�-&s/g!fM!7��0bj���P�u6���H�'Bz*e\�1ch���V�F�2;��e@�0(Yg�afl�����)�"���V
�QR��mt�]#Q!���%|?���im� �i�-�6�D��R4rSn�	�N�4��3U���C���� '���2A��8.F���o`��F�<�u�������F�b�G���E���
�;���^�SJ
������d,�n/�'�e<�I����Dfv��>�����K�[��a8@k
v��zIZD����g�k�A�K��_c'���YL��D����?�Jtc8���jB
���+���1x(��c_/=�#��h���a���=��b�Z��O���!">(�6Lv��"3���B�9����>�b���j8���2"�T*����'�r�>2^^���m�ZEC�uw��hP�l����������k�++���fu������
���|�%�s���JwI;D��O��zs���s�Bx2�"i:�`H\�]p�����A��Q	UZ�`�;_�Tz�H�pG�
f������i6��F����zz����K[}�%5�������1>~�y����9��Yy�Q��P����5����'�'�s�@g'RU]����	j�w�S��Z��?�[�4)�����]���;����)������xx��62�;����{�s4b��(p�kSX�s�|��}���r]j_kn�����HalA�%`��dT��GA�[���x�Qf �&J
(���/���v�YMGc��u�n�$��,%��u�k;���3DET��I6������pp���_�Z��SsW�%��$����h�l0���TF�1�p��vEz��R;�a�A������:��9�pX(Hs�*(�����Or����OYB��2��KQW;����Y�#gz�N�iV�@�����\=0�*)=K�l�9g����{��x��lM9������������_�t�tpt�^��v/��-���1+�K��f��J
[�|�0#2g:�
5(�6��V��
M�r��+X�\Z�����,MY88�c����Q��7B�������w�E�H>���Xd�*]��`RE����D�����c��Yc>�����41�C/:�`5�[M�� %R�G��0z�0
i���z8�2��4��R���tG�U^��p����g�Nj%�t�s���'�X��w��e�v#�W�B��l��Y?����E��-G����D6��A�.��rs�x�&������^3p`,��:ED����1Ss����q���@@��o4��7�Ke�s&-=�������v���MM�41�_��B�t�� ���A��)��8:)�)(G���	���X����Q~E����������������N'���A�	��3�
�����lb�0���;���>ph~Y��|#�����"�����$OJ��������d,��gz��^8�`��M��+�K�;�?�_������n��aB\�Kqb���+:Wp
��$b���GO��yj1���%��h���TK
�����1�cF=���Irwo�QeP��F<�� �bKb�i)��;�!m���]%@o�MW������YB�}��n��LB��������3�F��(`�	����2�r��%.B���(�8�i�CbM�^Z4Fj3��h�-(�yT$X�����o�+_c��@5(�|�r��	���I��V�*��n��
�����<�m7�?�q���;a�CMU�*�&(�g�����l[�l|����	�h�������|�=&��7�q��Y����
O���(>��������G2�E��	?���l�5BZ
G=�]��F��pQ^�oHXE4��������g�����88�!W��D��$�y������Yo�O��Zp�g�h����<���fs�����b$������h
�T�K�9�zJ�\O	���J��A��q����b���;�3JbjXUm7��A�d���"���0��:���"���q���0�*���Nu�3U���>�� +p�;�d��]B�z�������;������E="�f:�|	�6��8�b�(1�A��zg7�J�iB�r�q���mVX0����������O���hb���0���R-(��;=C=�����^��f���]M�j�is�Q�l�?G9��Is�����*�����C-��S���E����o
�����e�A�
�7��1�N��H�Z�_]����<`	�|�H�����3Nn�Fg^%m�V��ks�7����(���
���sx���"��;'�U6!Q���Q|a$��f!��jr���8g�
a'hHH�F":�j�!. ���'�������#";�of����YbA��ap���f��F���q�u2�R��Q���1'��[-`��A87��k�V9k"�q	X2pr�2���d-���b7<0�N"^^��FO����8�a��_��\����]��������?u:����9'�������M;��tc^�u�0��m6m1V�U�^���{�9�G�rkg�]�6�����' m|�t����_-��/��'I�������#=�@!��(-���Ji�����%��nw�1�>��g�&����~��������\���ec�����G���Hf���hGI��b�4�`�=�����99S��gkL��BJ(�
/X�1��o��&~��o6�����B�(5TM$�GI`�!���a>��������h��s��V*�k�S%�o��Vk���$��o���wr����k5�t��KJ�b���C��������[�w�D<���w�Y�2l���i��x�lk{-S<��*�?�/��Q��f����>?}gF{��G�&�B��}9.�{��"�N�N��gt��D5��.[�)6/���z�u��H���UM���������o:���?|����9u�M���W;���A�.�]:��\���8<"�(�x<�G��p����0J����]N�T�%����,B$b�#���B�P�
T"�j��2�p:n���&�C8#(���M3����p���i��N�G�����M;s�#�\�t�=K�U�+��< ����>�~�m6�|�C���Yw	��m�!(��U?�X��S7���7V������Ir�����)���o:�����-LYc�_DGy�.)����0G��@?.�)���4�VL������F��P�?r�������c���}��D����� �f|����(y<����v����=}��������9�=G�T6��]�.��D���d�9$`�@*��U���zc�7F��!]��o���&����!F�T��������u;�����yk�R���#�� 3�;�0���n#<Gt:6"���g�!��s�O��D��L���0���`�x�x?o
1��M}�;�]�h<�^}���]�?��-�����)a�8�W���xm��i�k�Nx����FRRh�N����:4'�f'���`:�~�a������fh��l  ��)���;Fn�'	P|]�����"<_6M^�H��u��5��~��v}a�^�?=8l���@�i����K�RF�`�475;�2&v�:Dx��&{	��m\4��=z���
Ah<
�H[A�v�)���M\
��]c�G<boFQ�������qi�;��<	�XvMi3���1(���M�,��?�$S����p��zI��ph'�o������pd�q�:����7�9�7i�=H�)�B��	��6p�O��MFH�]������1��l�E��w��;�wD�����]��6�x�n��ZR6�e�"Kk�(��o��}O�7�IXN����x��r�?<�m������u`V����A�;����S 1#�q��]�����@���'^6��J�ia��I��u��z��2
0��2�R�����\��k�&��C�%3�K>[����@X�^7�Q�5#�D0��Q����L��	����=���Z���!B���{��%�; =Iv�&O���y�F��/���&��wlF������^��iE�_D�G�S��G������b���d����R��%��|
;5���Q����}9���������������y������B���M�q[zUb3�]���sGQ�m4�>Q��c
QA���@�.,����e����:�'z�)��20�d�����7]Z��^�yk4��Fs?�k���r���
����������"�]@����G��T�8:8<�8z}���������6�������`.�>��/��fsK�o��O����v�y9�:D��W�u���;�~��������$�>���e|����fZigH��r��G���~�M��;��!gg|��6m��Xh�D��b���p���p�*�L(��y�EX�K:�W�[��<E�i<J8�b�L9���p�
����IJ�1��:�z��
���T���T��8��TJ��[�z�D��#2�1Y>��"���>)vIs:K�u�<��x�I
I��*/��n��I���LR��/����(C�=1��4=���1����.5����hm�GQ�rK1�90�m\";Ug���>��rs���h����|����OP����Goh�G8Y�x��x3�f�-=�*����I���QJ����xe�����b&�J�K�m����-�>���W��EcK�W�3���8Q2s*���`V����~DwqEqz��3�������
_^���%�p������G'��7��[���r�Vft;p:.�Qwz�����4�j
T_��'_�=��bs���s�Rs��0��]"��?��qW���E~��00�WkN�3~��H���#�0�7g�Ne@��"��r8���h�m2l�w�H2���EL�Gp-*y��3
����Zd����A,%8�����a����������*�����7��&��dl��0cb���)������`��o�����g��+FZ\�����/��jRy+[�L�����~d*.Q�A��r��O0��A`aOy�*t���H��O��)yd���W�+�L��s�@��9w�L�yT�8����������r�����F��8(q��(�"���j�s�{������O�P�f���^l���"s�GHn^��I-V����4�?��y�f�E^�����]�b����4x'Q�p�����$&��=4o�/�A��������W)�oO�%�P�_�=��|=�����NLI*�E�99w���!\����J��(��B%cA�e�����V�\��2:H���,�^;x��:,^�xu�:�/^}�����]{�q~x��N�^}uq~Xj��C����/�J_A<���3�A�^������v/��p&>/'��F&���C��N{�p�����H�D�����0�/�a��T�d�e�K#�
��6���R���\WL��1�S(���>�������I�~���I�f���#H�V��T����A��5��������2�����{�������E�������������x=��[��L�t�s������@�Om���;qzr|tr����G��ON�N�/l��H	�6�~@�f��cq��9�T��/2���K�1p|�M�
������0'H�������G�o_�����g�p�l�|�����9���a���a�����M�R����C���gG����6��U4�#H�l�APn7%����)��6%�,�-��E��E�����+�ojy�l��g$�9[���,D��
d3b:w��@���7��m����zt|�~�����?[oO/J�}Avb��Vs����#�<I���.����58P[%A������>4G1 ��y\�h���O{�]��`���|�V�U=zP�<������	x ^����f�R��alK?e����q��G�>��O��0L�����IJb��H>��/�H���k6���l���C���m/-��d� 1����m������D���:�@9w%���6���p����-7�]5�MSFo�7gPA�sI��������L�%����&�s?��&Z��o�o.����a6����8����n����gn���yx����mJf��Y�6�sjj�n���Z����3H�� T��U�m>/���g�w�bL?�&��*�Fw���b�ekq��}��a�����UH
K�R�bD��;�E�Z�V��������M�����\q��rPk:w���h�juyFd�L��F�I\T]"����_Lc(��R�0� Wg�>~,�2	��G�����K��-��fc��j_5��Z��t.���'��"y�3d�������"����x�Ap0h�����)OZU��&���O}��Nv���u���g(n��l����`��O�����\��l������CE5<���,��D�bln�d�#�$��`����@��i����k���I{RLQ���Y>u��1�C��)�6���o�<�H��
~
W�f�"dc�#5oO8��5.5����>���A��LM��@����f�f�e��_pO�����������4����Bw5�����_
�_i��w�7�2�J��>=�
[v�������Mk	�Y��o�U��� |c��c����wuW�q����rON?���Jr)eua�.�%s��$�yji~x�\���9������,RK0�uu�1])��u�,��-�Q�Q:�HD/�iDAa)s9h���\�T��DU2��K��1$��<B�@^��G�P��&U�@��?��^��E�~v�wqX��,��
��.���2�����>���|�%{��!��D���w����|!oHv\�7����
�m���6��z�RkC��@�����X���	>	�����!���w�voC���[���5fW"���������%�j����t�����#%�S�<duF��q��!�XM�
����0oFG
�������*5Px<|������[XTS��I�d}��.l�����'^F_�`�){�&�����~i&�9�	��1�Q?�����8�q~,�?w��x_U����5D����0�Z�v�x&�!�z���IK@5�P����!�\��"�nF���{��[�t�_���!���R(���=s�3������^.����P�`.��o��I�5�jC�@���1��)\=��t�fsC���B}������+��x�j�a.��5��),	�HG������pJl;_�z����������Z�"P������M��iE
Z���Q-(��dx�4q��thm����O��+4��kO���~��6�['b���+�.5���[��{t��z��I��'��.���;Qk��4��3` �E
}D{��g�sGnJg@��i2������`��B��J�\��+����s��Z��	�f���t��zj+�I�7���>
����In��l���&��!b���
�Z�s���j�`�eN5g������SL����J�KB��I����
��i�k����h�T��mw��O	B[�9S�uJB%��@�3x*�����,Z�-������v������3�x-����)�6��%�Ya^�d�x:�p���(�8fl�|�%s�����zf�� M�J-�"�����S���WD��<�L��/��n����Xo���@��������y�z}����.B8Q
����9X��4����]s����VS�r[J��JTS1��
��a�^��=%fR�,ln�Y���N��HJ�PY��Kn��B�^�������0������+-jUM�����3M:!����(M�R�uJ�����t���Y2��Y��?�aK��Q���R�xxz~px�~���k�)��/h�x}q:F����?(��a��x��j��������z������=�,�/��[�wAa_��{	�|����5&`��W�6E[��-2�ry�4��4g�D1sFRg����q��������zX����>KfI`u���@���|����b] /����F&j/�Q�|�\�f��l	b�aP���e���8A�I:D��Sr;���=�n5�"6?�T���!->����#vKg�b��������>�v����x��sV����|�2+����
<�I�|�S�{�;^��;NF�'��,A�W2�)u����|�JIc�9������K�xjJ����(})�7@�������u��:=>0K�`�w�3~`[+���s�'�/Y�p!a/X��3���d�+X�r���	���s����N�}���Xm��������������*��JS�$?�������p��gV�p�?$��CU����+���]T�����S`�L����B1��,�?CKe��a_�������`'��(z�!l,��������vtm�`�����U0��_������)7Wv�w
�R5�A��Z�YD�:�d�tK�Zq����ql:�����/�}��=^��{�
�r{W��$11��S�g��NK�
����mf>V��^�(�7�L���p �n�X�@XtX�DY�����r��c��$�&��B@��n��}�d�����)V��&��U_��=�f��0������WVyG�
t����~�����$����JqM`4
=���I�D�:upWT����4�5}+���t�����O�� �����i2<�B��d*>��zZ$����'�����$��H=j�==��;>zsB���LTwN`h�n����.dv�EF��;n����@��l��^�&R��~�*f�U(}�:b�0��{JQg2U�R����V'�Q3�O��dT,V<v�����og���0m��N:��x�������f��4Rw�Fva5#���K��h6
:6\���4����l����(�Q�����7��^y~��k�����z�J�}yN�����Jv�0��P8�bdd��H
�[��!;k����Q��p� �����=i��(�Xh�B��G����<4&�13��!.�C�2j
vA+�g(����5�&��W�q��@����-#2'�w�#qA����D���_������?����7���%�l��_voIrK_��s\�����/�*�d��3���#������.`m
�8�"A5����-;��eIX���|���Lu>K]��Jg�l^>C��[�9�>�\�6��9�g6
��Sm:M�����
�g���~�t���	V�~�E�~�������3��
��q�"E�E@d�D�xD�����������'���g�4�-|�
���(�����K�b~�oq���A�X�����
:K�����e��t����O7�L�����2��l��-�	�������P.����%�l�I'�_�M���f{I_a��,$s::��
z`?��Np4�+��[��3�F��G�1�g��Di�9Wo����O@���?���w��K���,lH:��\.{9������c9..��
>�<r�7�%�Q���z
w�$1�!�yL���9�����JGI�W���R<��L�7��9�*_i�����9����.�j�N���a�5}�3���:HnK��v�~��
�����=�wm�fg�������e�R�GQ�aW|����p���5��u�Z_��\�x�2DG������l�d:���|�E�����2M�-GRA�?8���u>zw��o�/��$�BA��a����)�����9A���;���1\��H�����X��+��O�`@���d��I�������r`�p������c������Wk�
N���pIg����.)���?������F�cO��8��$.���s�&��)���1�9:3�bCW��N����z�H
#9��k��#c�G�[��^p�����/���v[��e��q{����q���?k�U�A�����
!0�����K�D{�xc]�-^d��lR9dTv��l������Tv���������AT1������-:�k*���<����C�RP����{|A��^�+�������?��j ��3�l�{�J�����4���������.&���+o��5(�Wn�������_S�)�����4�% �Ry���Z�tt�u?q5Z~������4��c�|*g$7�*����~��C��F���?Y7S�l{� 
�`����P���`j�(�:������%�4�I�M�4�/�I}4S<�p�=(��o��r{���:��_��u�����������#�����!_�����sj���Sr
!��\z���Nq�_��>��L��.�/�����	f���&)�xSr6I�p�������`�5�E^E<4J;�9�8b��; T��87c�s��!,�M����J�����mBW�����^<��)ghy#���3lBb��������k�?��W�6	��G)FJ?����/`Aw4��#����n����bi��Z�r���naMN�G�[GO:���q����7�����5�2�uJ�o��d�a!������l��pN��`�>���
\nk����l���(��{p�����F�q�MI����0���������m�����u���/#��a�j��o�8�iI��P��2�$[R�����R���5����%��s���>8�5�gl�\���)��T��� +�N���]��:�eu�P]1�����Ypk�VN��z9�YX\��8���&��i�)��>�9U���,���a�
Q���'�9�F�%p9Mx�R�<N&&�;L&^�������y��c��w>�Bg�w���_7����o�Sz�N(�,���}C��I��
ejS���sf�r�N�<�rl2Di�k�,'7��#��[�?/3Ew��Jg��������)F\=�6_p�1���=��=*����)��KkN�C������	LC��d�o���X���V]�/:������9:�����K��s�����J/:7����O���w?�������3����!-����C_�
7����t�������S�4_hh�v��&�/�5�t|v��������&J��"�d���C��Q�����|$��!f��j��F��B�9���c#��k������|���w`���(obg���x[��'4���#��
`b6���n�\�J��	�s
����f^�c��|�$/�%��\qC�c��1��d	+K�=Sg6#GbSF/C+��q�WZ��a��_D�?����pr"n��K{������f�)z�I��	/��c�c��W���$�Jx�a�P�F`\����(T��tb�&����D|C�G���Q��T1���3�F�TY$X��3F��W��f���CI����p��s��Q����CO|2��(����B�Z�Ew��@�g���?��>W2TT[���/@��J��H�]$
4�3��M��Y�����2������������e���	�?����l���o>w4G}�;��f$�%��!-q�G��=	��g�z����Q��/�z����@�@e��}�v��#u���m�
a�T�H;���R�'�&z�<(g1�!�'���hM���Qq�d�b����9^��������C���d���s�Gk���x@j�K�Sn~�|���xP��q��!u��5����:��$����~��4��9���7�f��+;)we�f]9��������=��N3e�ah^lD���Z����
�j��?u�0@y������J(e��0�����Qm�$prE�(=�2�<�g?�fz'u����W,sY������5^bm�[bYT�7��df���I��*����[���p%a��1�#.>9x�����Yb�@��@B�l��9�W��!����+/�	�]���R�&�&^h���1�d{�f;\��������:���|���b��Ip7�}|
��`Zu�{����1<�vJ>�=����o���Pw�������;Ok9K$�.�
K���U��e�HW������re)�c�
�'�T3�-�@��'�3b�l������I�L�6�������Yq��k�eIU
���1���z��2���z�~TO���
��v����$��F"	Mazh2��N2.�����8/��9T����W���s����w<�|��8Z����C�/����w��>�P�#��L&h��������W����E����}t�5�����Q���K�\�4@xq�_�6JlL���D/���������@���\c�)W���{�,^||
�&������������ns�}�G/�m�t��k%��{E�%Bg&|�48���q�W
���<�Z�W�$�G��$i>�Vx�k�P0���:�]���y�����6���1$����/k�p����RQ&�P��C0W��"pU3�ij��3B�����C��;=����g�������!�����~�mM'�a��*����*,����;����1�B��,�
.�������i
{��6������R�����"��:j��4�������1�l 0��{����;�������dl�jPH5�\EYtO��,FI����N�R&�e� �.O=��'����?�\���V�6pxx�i���j�&\lb�.%5�s�&��ZZ^{"U��Z5�d�i���������K_�@�Qz����43L��hh����1V)����[6�(��t����V�/�$Sl�1#�#���(�XY�V��1P/?\��C�R��%Q�n�
� I�`���������]t���y����yv��r?��l@��fe%�]^w����9T!��mhbD�N,���S�a�����19���|���������	�=��_����h�����m?����Of���A�������H{F0"�0
��tvf&�����c
@o(������
FX�t�'��1��"P���7�b��e��d�/a���-��o���D�C�<��H2�I�\�x�����I8^	��!��:�	�s���B�x���o��D2���7ipS����8��h.Q�$��1��7���q�P�T0�X�%7��2�����uKF|p�}�VMV�%jRD�<�O��VC�9b�,�&FUX��E�KG���D��S������?'^�m��";��tTY�����5�,��!�����|�����������*�����E7�"d=��M.�&zn��4�8oI���F�VN04~�U$W�#�2��Y8
jj�m�H+�x�q:����'2��������d��[��n�E-�Z�=�E�e����R�B��l���������'���N�T'_��������gg����p����V�������F&�r�+rE\�y����UKE~Nh�J��\Zjp�K_L����q@�����Do.V�5UP�l6����y�5K���/ZN�[�,���'�\>�PKj���M��y0 Q�l|4�����#��@j��x���+I��=c���X`	�=�')���5<wrCP�&p��L	N'�ET�K���U�d�,8[h�s�%{��x�#d1q��!��r��p~G����z	�]�P��p��r�`19x��	�h#N����sW����h�-}'��������E#�s��f������_�D�+��d�.����������s��;�:&�b	�A�E�V��jk"^g����m*�3���f���5����
v Rqtnh�~J�*���!������F���J������J��t9L���1H����)K�uj��vE�����P����@ym���?�S��
'���2���1�
T������FI�L��J��q���Vf�#�*����Ap�[��O"�"�)�<�����G�TN��P��E���F��W-�!mnbo,Bg |��4I����Lv��Qt�����&9e�t[Is0�J�p��S�V&P�����g��)�K��\1���/�
�����tC:�)���	�!�e��!�?bRH���k�,n:��3 o��2N��Cd�u2�X���;��
�IMT��<��-iU�������rf�{c5!����>�������B��]�ZJ�/}�R��@l>��{/*VC�.E!�c3��Qry���V��J� �Kb`�N�O��y">'_mqk�az)�(B>.:Vd�ST�#%�^:���I����W�C�y��z��qA7+��4+n�"�h��.R�!8d=�L�!S�3*���1u�Nj��6��� z��V��q$}��G�F�}�n���'���k���f�w���b��o�}V��x���~��5o:��Z |ao��
��c����
e�U#��"��X6�D5�J��BRQ`ALcU����(�^�� 6n�T*����p}��������[5�|���m��[��
�f?a��O�S��U�/x�?��J�f_�����/����(���������*a���<�?4[o��/��Q���T	v�����Di
����#�?�c�����3G�����V&e��	+!jH�&d�c�[(1��O�A��x@*�vY�����p��^(�$�1#�@�i��gv������z@���W��`�4���`:����=R�����&��C�v�y$��B�G��a�n���?||_���0t���
�>]���G�aN"�8�}����z��*�d)6Di�v��M�m*�d��5^��!4��\���l=
(s�A:���)MGP���<�^8�%� ����,���E��
����II�cIFnI4F?�����BTd�w��������m8���>��C��.'h{^3�	��@w���l�^�S�3<z��K�zz�VX��O�e]}L�<'#r�#���2_5�m����{���z�����'��?���_�����}y���9���>�_�a��������3Jd�p���I���Fc��e����I
S q�V4���\aq�-��|��c��
����)����!�`+mA���D�[&,�U���{���&���u�����PR������������X6�����I���18��G����r})�������FT[
��$�����fq��%|7iD�_����Y[u~���?���gv���W�RN�Y�wfD��K�������p��-��/���NF���*���
��zE}�4��|���F,*&��@���UCC���+K���H�,�Z~��Y��0���h�XV}Z]7��9[h���z�,�rZ�>�HQ,V���)V�.b���U������i#����w��%l3���&g�c��K�-���c�
T0�)Q��*���
�Vn��	�S(��vJ�n���To'��r��d8f~�i�dBsi����c�"h�� }��&��`��ja����\|�^�����D!�~��y��Q8#X/��;y�RP�����Ut��$��i����9�����h/�L���7H�7������o��3�5`�b�p-��Z=���>����wQ��l`��sW�.��F�0&\��M�g�)ewey�=+�-%gR������y��{�'��3hL�/x�I|���;/U��fy��I����: &�B�i��xuE-7I��M�VX�6Dh��{=�+��qy�^}=�`?�W��4��8	��F�[<*;%OwDz������F5��16G�_�\v�b�������Gb�p�#���W%�����4xHzT_u��:8>)�����+L�b`���B|E�b4Z`3������a�������KTX��4C\L�	�Dd��J��q������*C����������{�F�����{0��}n��|O�0� O��50#
����RT���k��F��&S�����x$qu��|�t�}�X�b$n�_o���@cP��b@�<�������2�8�D1M
�KU�(�h��w����:��%[m�Cc��[������_+Yh�[7)�1�m��Scf�%�y�l������D���]����L.z~.\h���a1�^���Y5�U��&i6��N��>��� ��C3�/���i�W�i/���U�\P��0���,m�B��t6��gD�5��~<t�$�������la����l���N6��2iT1%����]�Ld�y\�t��v���,�����v�n'7��l�[��I���� 0I:�^�-�EJ��6{�I��P^������MoF���d?8v3��}��rO����A�m���;�w����J�����8���zg��2��(��>�L��NTB(�&��PIW�<�s=���[������������;�5%L���L���;�!K�ck��u�$���-1��xA�g�M�
mm����G�8`e� u�?v�B����������y�
���#��F+��,7"���6���7����>V���na��� ����KW
X�����>!�
�������*7)��;����	�'1����r��=nHA��!��3�*������+is��!3�mk�+[2�{(`0��%m�E������������
��`=z`F�>d�����ch����e�C3��d��t�	�����Ycf�da8VS���.���l<����1Hc�.�%�����,�m����w�*{��Q/UP[��[����BD��6��Q���4��w�A���-��st������'�#x'�kr��
�>I?�>�
���
a%��e[B%w�����5B�]��+�(M��C�+���
��Q�Q��6\��H����������@K"��Xq�����U����O�.�&��.k�{�m��U
������~�S����|��;����<��_A�b��F���3�_���=��6�[z��>`���l��x	�@;-�hS$�������^�o��{G�;:w2������vn�����zw�VJ�+�=�J��Ai�j�����Y����7�lm���t�ux�>8|���������u��s�A������}�g�t�L����l��EV���N�a�H�k��y_�jKE��k���XK-��)�������=�x��K���N�K�Fm�	��2��3�1�qC��}�@�c������<n�l�?L��TV
�W���C�	ef����0��;2��^��$������[�;�e��Q.e;4l��im�3K�
NT��W|��6��vc��W�����b�-�r(�)����L�����c�I�9|������j�rhb�<���8�K�`Iwc�G0 H��t��z����u����rS��2�/��r=L�L�l�&)���4�Z������������U��2f� ���/�R�J���������KW�Q:�T�<&E�L+��.w��Y��8^w,Ak�luc�����r$�����q?����@R�����Pw���.1������2NRo-�<(G�u�w��������Z�'4$tOD������O�[2�t?H����x.�_t{�F�_X��QU1�J�;���>{���7�6����������������V/��5�������?��?G������N����E�3.������)|U.Sr��9~"���{�^3n }IM��	�E�u�����'����
����$i��q��Q���K6&g��I6�F�`��^�9+Z0��?����Uj=o^mx7~����{����������u.!�8-\�y7�!8�}6M�R����M���9�������������/~�'��uOgCa|��w^���i�~BN�����ry�8�����\�}���)�p@�mbU��>�;|�f����,�4�D��
)�k�b*��x�<iv��W�$��3�7��t����� �R��E��x
r	�k1�����01��>�rY�5|gmm��8}���/��}��58�6wv�=l��\�d>�1��>���t������X2����r�^������w�En�t�b�+k�*f}jVc�K��������E�����:2����X:?nIt��1F������SQ_�h�%��A r!�
p�s��xi@\�4����1�&���,�A�Tv���A�4�����C	��<�b����I��U��D}z	ka��e�>��O[�+"����I�P8c�2���z8+F"�J]+�T&V���Y��L�a��<I>i�6�/���=�����'��Y�FT�G���y���Om����5�K�+�]�4/�
���J �^��@|�g��G�53=U����
o�k�>c��i1x(�zP%s���&���>m:=�/_*�?��F�U�q��[~���v�����x������"��k\����*��R���n��n�H�Aa�%�X�a�@����'�ltY�#�`�=��,"C���r�<����!���-=�����q�Q-����c+���6�3$��M�f
.n��'O
���S�������D��Q�9������Dl7����\���j}!�{����f:\G���W�X��k�#��g�l�2lO�B4�����-N�i2��T��z��3��R���(Kk,=����<�����MQh��������>����}���`��'��'x�n������3��h�v��O9���$S*��f>f���4��x��+���X����T��f4b�[�4;�>�8~�3?J���P�*8]%V8����Im��r�2�T������<�r�p�j)9�Y�{��S���y������������|�$�h�����f���G�����Qc�)����S���j�Gi��������f-h�/22�,�p`j���x7���}CL,�C���Hd��`,^�7C���3�.ZX�X2J��J����Rm��x@Q����*7�U(7
������JxC��z0o�
3?���c�a�
���Y���Q�Q�|$�	�f�l������~�}@�����g�^��6����-O����������g9����������&I�]���=��}��c��d}�������'�%.uLg��\����5���z#qk�.��9 �=�����_�qF��u��,��{����,�%���]RWG�������lA1�iJV$l�"�)�K��}v���!zTH�y�;�h��/	T[�(##L	��`���%�/���������1�D+,�1��g���z�9��@�����n~��)�Rt�����a��p�2�����A��{��������%!��J���������<������
�����p%J����N�*�K%I��=V�]]���/����@9�R�Ubz���#����	���w�p]bc�p�f�Z �GnY��!/����0��a���-@L+�M���9n��ce��
Q�rQW����?@���R��?f�*v�CYT,Q���l�^:���P���\��f8�d�;������5���������p4�f�7�}O���3����9� 0E<���f�yV�y�t���zEC����<����iEP�@�
$���z8r'5	:����q�]Lly]�o�_v����ro;���,cr`FL�����x�������������-w�����/���~��
a��2w�"��6�,����z@��9
+&�f�JC;U
�:�7�����.��J��u���m�R��!t���-6d�0�q��|����LO������������w5n�(>.��XR|��S!�����'�R�&.�A��D��&q�*,�9P�RK^�g2)w�����[/�����u�pvL�������	�9>�h�z���]G|h�y+�b`���_D���z��-��3t��	� (k�EJle��#�G?��*6N&�����m["K�dN �W�#H�e�0j�x>bY��bjx�"5�NM�|:��C��Q�'��7>*��ZG����y`
��������@�q����-P/���!��t���@�������,$���x�%F�]'�w��<z�^��k��Zo��|�1m�����P6l�T���M��
�d��c���w@�
�j��$��������H[2���S�<JF3�,H"���n�m���
Z*���Z������C����<����mPh9~��|������i�}0G���
�nv�#����������x�N�
�kmI����F��^d���f/�.��\
*�c�t��h�\��-���8��n���q
��i6�#��+�w����:��rq��j��&�`��os��Zox����h�0�9�E���f��������zKU�g���� ��d���z3��6zG��H�5��\$�����>�g���O��+�W#������g�����T��}���^�)�A;�t<���5ez�h�������},
"�����[�>����b|���vH�]*����Rv����l?��S�]�{N�������b�����NH���rE������c�V���d�-y�*w���`A��	��$�^�9�aNcF���n�B�����-��'�t�����&��MQ���{G������~]%CA���2�0�W�I��"��y���	��k�&�[i�~������e��-����mF���~�	b��E�p�O�3��8���~���F���u6�J%Dh��d�6���ZR����P������<b�o��61�D�*�<��Ml:q��*R��Y�]�-��#3%-�b��� �}�^�4G���Q6��r�YUa$y�c_&��hD��ShBh����Tf}+�`k
d:J��JE��6,",m^��N'�a�LG���H!8�$F���%T�w��%1oD�<��!���0c�j'b�� �>?���tG��N:BL"��	
����p��H��Q����IK(��~*�L��������^$�h���c5;����r�:�M��Oh<�"�8L�qy.6�G��T
,z<�������/�5S��Lq���{H_�wq&]�>�`"g,��t���kSv����XR�	��W�-��$������&���<���[6����2�js��q�3Z��9Z���S�-�:��46��s
fs����Vm(��t���f�O>�p�_�)�Z�c�Q���t�O7�Y���Em���Q��=/���?�I�]I�|Zh�4F���+v���\�C��������9��j0�L����U�R����c�(�r���X��S>�����O&�8a:U��D�����=���_A�j�O2�#��$��h|��G��%S Y^���
I[_����w8�m��sX���x��LJ�"�Pl$�$�-��|N���V�=H;[����W]�q�������E���!�E�k�$���o��.�*e-@g������
����F��I�M��N�M�
�M����3���Io��Pf~�M*���P���H�]�J��7�����U�5���4�����q<paJ�cN�51��?%�8�|IP�P����z\�����O{��jP`.��{h���*G'������g{�[�������K%�I�	�r����v�DM ��3�������T5����r2����f�����`��|�s��T��9�������	� o�F��1GN��p���W�lD�>�L���v����z�Y-0��(v1��'i��9�h���f�K
Q�'�M����T��t~xz~px�~���k�g�����C��;�;n�B5?�K�����K<��v����||����������_�?;>�?:@����"~��	�F�p�uM�$2]'���pq�i�%������U�&,����=����kS�Y�]/�.U����`l[::g��3�|���0L�N{���KP�&�;�o���^�{c�	����V2�����8��i2�h�dr@��4�b�mc�H���f��ZVF�����bEu��7�+��w�oN4b��:1�w���&����M��%#A%$�P��Z4�0����H���kP�J������&����LBk���#�Kv8�=���7%mm�����=8=����	m�4����������������_����:<��r�c�Hl�������j���	$������6����g��������w��;pG�����I?7�b��(��@N������z�����JhL$������dO_G��.���H1y�9
�W��3�$t�k��`�L ����kYs���/&2��%�$b����1��i�'�0F	E��7�����Qwg��� �)�C�r�?��f�)E�.;x��0z=���[%��L�K��3>!������6���r������+��
���uc��`�Ov�%��h�a�����w��|�S�_D��<�s���
m:D���^N8mS2V��|��2w������I�k�A_�t�7�����w����^�i����.��\WV~<J�6����b8��J;������6K����|t��	�����3��[r4���x<�6c(�wn�&��#3|��iq���qk��s��a�6,2gZ�����H�=x
$!����	N����WN0@p�W���d;w�����Q�_US6��}�wo��s�)��V9_i����
1Sy�\�C|I������<J
�ki0��P����E���u���jOo>����VMfO~cyZ�=��v���CxD"������_�"��,���S���"�W�Jq��8����=���^/8	��[���66g��
lW��E�����Q����ou��a�:2\<�?�yU��P���4F_g�)���]���.DD���ST�N���m���j?���=8o�G�4�XCZ��O��DF2���
,�x�������w��]%���'��8xR��Q��K�;����Qe��l�q]veo�u'�Qu���C�s�X����(����������7��g���{����kkh�kw��r`4��m��&�Z
�-e�������Y4��U�F�jN��w�6�P}[,�_��l�]�m1�V}�.K���MQLv�Y�Pu?
��W���HC��L���J�n�^�{=�T �}��Zo~Jn���B�C��,����+���{d�F`��6����R�
�����E�&��[L�*�����0;�;C�	7~�]�+��Y�{����E=��6�n�2f�Jk�7��6�P�M*� �����9�����6��$��7dA/I@I�����q�F�Q
�����a^�4�\`>=+�B��s�����C���f��
Q��:�7`�������;E����qN9z��������b�(rv'�R�0�z�Lr,��MI�|���{}:���.D�;o��������H7�N�b���TY2h.��&��vr�X���$u*w��V���&1�.-13���N�%�n�~�au���`����1Y��x���V=@�&�Q����D��CT�C^w���YB����7
{^1v������@�������Qa3����Ge��Q�R�p��8
��E&��x6����;��XSk��j�}�Ui���������>C��A�	6p�I�����c�XB^�<*=�FN�Lo���v�x��t��0�2�
<_�mTl��s�k_3�#�=`������8�)�
�������g0��+76��aq�s��;~s:0��?������������s�m�u}�~��a?�/f����1~V��!e���|���b�%�sp��)��R�
�7P�����`�����_�\��:u��-�WQq��)��.�B^-T�;��%m��*7��Bv�w�2bN��Fm
3d��������}����g}�g�u��t������?�NC'>j8{��w���-bGA7����/��y��
�U������,j�Xy5����������V����������wx[#�
�V���zg�R,w��S��CS'i�sS��"�5��t�hZ��1��Ms�~�%�:I�^P��m��"eZR����d��
�H��%�0��.�������s�&���K�5��u��k!kmb����u��K���v�C�$�W�X�%n8��~H%�����"����l?��d}�E����UY}9�?*� �/Kw�W�y�>����������+��/Z����<���v��pP�,��&��2������u��F�'��%rs��"&.7h��%9��k_r�b�+}��5���=n�H[�>������jz���V�9�+�4[tG�T�=B�M����UMLs��EA��#���Z&
j���tC������J�@��;*�����g��kh! J�������kTY�7<�3�f���MS�LT�l]�����=	�'�������k�`�D�h�k����H�b[�@.�6$�
���"���	N���S�im�7F9r�_L+a3N��;�M��h���Q���������^#�h���(�^��E��Uw{b�CD��d���>�WO(3�|S������=��P^�����U����m�0�
����[�
K2x�)�)�aE21�e� l�x��rMaS4����\Fr�����q�����
��W��t�O��A�6z..����"����,�A�@��15�=���?eL�.��F
��hF>Ni�q�z���;q�������rtb��H������?[�h���F���F�O���9ng��nDG���J>���al�"�c��? ��o����5���;���%���V�U	[Tef��S����P�������S�[����g��P��1�l1?(��+��&z66�s�9j��t8�9�0����(�����4&.F��d������Lx���M��2u}!�	VAX��.at>].��g�#(��/+�p�����L���{�d�`������rAa@��63���;������-��h9��Y7�5���������8���P��WL���_D�v�o�u�
���?���O��4��G���Y��y�Jk[nTT
�s��q������Z���������Pw����������N��@%I?����S,-I�`�-�����CL��c\���r>-��`����j��U:XR;RsM�NP��XU	��g�*@�k�_��V>?HK�3d�?8���\��Hu�[���3 F3DO1���Z����:	���C�vi���(�/|�H	��M��$}���&8'�� �K����`����K���6��~�m��-�4C;Q�K3�ql��U�	�r�?Q��� �i�������(����>�%Z���rG�)O����b�(��Jh7��y�����tB��J��_G��XN�J�
����0m�������8(�4Zi_@�L��4��13����H��2gL�,��F4�����F����uS�aa^Vp����aB8a�(�V`f��	��&��4��i����d������D���V��\t5'P�$jk�jO�|O�'�����BF��/���R�#�qY��$;��V���H�Yvu5&�p�I]+9��K���he���l�#3J
\���OG�@�0��]�	�E_�J�����[��M�]����L�g��A��~r��\?���j89�B 3h[�e������0V>�`#�5m����3��
��\3���P����B�.��?�q)E��J`h�����U_Q���6��cJ��������2��8�f~7'$���S>l���	����$�kPR"�*k��L��������F��`cb<���@�h�z�?S��]��,o�\�s���g(	��D��>}R���4�9^�,z�c^���1��]uj�3lJ�^���v���������5�br�' b�-��T`�	o�`�5�C��Kg;~�|g�2D����*��E��vJ�f���z�������A�L2����9U�u��Vx,���b�7�x�*��J^4/B&�b���5��qC�3��
��2�����a���k��s���i�N�Z�|���+5k|��gW2�T�R�PT�7����������)b��D�$w�^$M���0<;���Q![��*P�Z��������`uY���������#�d^��A���V���%'|�:���Q�9���%��&��,���O�T�T��R���6��S���c��/�~�����n�����q���p����������97�,f �����5��k ���>�9��,�:}�2���9�����4M��4��������{BD�`D,�����9"Y0���L�%^����y�qD�nv#�m��({������A�
�� �x������e`&�w�-3v��)&fT��;�J27�f3�l�A���z��6�Vs�D#"I����Y�7U���zerk4I�X�;�0�,�]%!�D�IY��QS�l��m%�^{`&�@�+��J4>��}�RJW?��������"�};�#Y����W���A���A3��pBu�G�-,z�2zL) �n�!^������]���/���s�,�{�Ws�_���W���"��>����n�)i�A�y�V-H�8U%8�� T�c��v�����$��L`tM�z i2��#�p�#�;���������\���&Ah��n�����y2���v;�K� ��9C�~�m�/�����o������M����������p�Li���q�Bi����\E�����l���9*:�=x��@6�_�'��'�C��Le6<,��,mF���M��Vc��/:K��\��o���m<��7���o;a�#J��3F�1EY7�s��k�9+��P?O��H��RP�e����o>J4�G��:����"m8\q�e�����/��-��YN-X,����qZZh�|�MG)�[����Gq��|�@D������10?:!:A�a��E'zYr��{�������_�-�O^j�M��b8���@h�U�u�����E#���:q�A�=[�B�N�����H�&��*jX��Q����`��f��wb���@P�6��������^��?@.�Y�p��D���vt���!�8������mn��M��
����j��FY��0�Ry�d/��bZ��p[#b|����
!f���w���u���������X�������&� 8�#Y.����y,��
��:�`g���dsz��<M�71��=��������)S�_S���C{�Fb������j?2<�����
(BJ�T,���d�n�LE��jt�@�z��	�F����m����\�>XEsK=hza#����S[��W���l��/���@i��
X����:��t�PWiQ��)������&g�]�LY��}MLTI�*�X��R��hN���T���Y�P�Z���%���E@�_*B-� ��#d�r.��X������^�x5�J���h��bd�vS��&q�O>���]�k.���;@��YV���3JY!?^$�����Y�D>����9�����Q/i�t�xl �]b�=i�U�>{��8X�AX=�Ni�����$�karK��h���~���Q�����#.��hH8����~Mlf�<a7�f�d�vK!v��B\KeH�2�/U�$�QS���S���M��rTP���]8N��f%������	WS��g�F�H������W(B���<[���H������Z��?nZm�@-�mI<��}X	K=�
�D����Z�]'��o����E�X��1���O��E'����-c���z��4�!	�8z@G����G��V������!.����9�����k�
m�5}�#6;7���7����x~���g�!u������tE5�|�Ra_)�Y�T�t#���;9M:�F������P���� F��8Xbj����!T��p������-H3���a"�������0C�[������fZ
c���@~V�XVZ4"	K5H,��e��+���![�[�C��XD������V��K��VbhJ�%��5�C�
kK�L��N�)��=�p�rK��bN�9x��|;��jXZ��8:F�C��j��y���Rf�\D����I*_�P�������g���^O+���05����k��|S���X�d-�R��tJ�aek]3O�D�^p���04�b�a��L�pM������U'���r&�{�0����lv�dtd����jy���&������~^�Q�����/���-�UE��|���=�4��#~���#�"���@����0�/�"e�/����SA)�Q���X�i+�6��� 2@�d�F�6�ZzY_.����6���
�$�3�.�
�O��)���U@L^g������3A7����<��2��J1��T�{��sUYlq'8��6�0����Oo�������S
�?�c�	8�f��k������j��S>�#&�SV����w��0=a��c��5_�~�h���B��#�.�.6��G��L`p�g��1B������YC�i�;����~�b��a"'�>9�szJ/5�YKm�Kf'��I���-���.�EI��I�/��e7&g��Y������4�����7R:������=�Z�7�RK�����?HbXi$������� w�5}������s@Q�LNDzf!%!��K�Es�
rK�����n�V}g����5�	��_w�2M���S����<����v���G�P ��������F��YA��F�a����j��!)�.G�����rd��kX�����?n�5�:'�BUm�T���2$���[
U�����O�g!���=��B��d�#p����N��$�'���l=��H����)(;h�u�yT��.L�])76��}�.�!����70�w��X����Q�#���P���aq#������W���5����]�
���?� �yO�~5�i��I�&+<D�*���]�c���*������Fdc(g��Ri9���a+�a�����U�e����X�_g6�C�)��8
tZ��E�.iB�s5��04~�,bekX��s�[A��Th�	q4Z���'?=t(Y!�����fN$~�){�UX�_��n����/����������%s�����,����������zh
.)��L����knO�
S*]o��i_���F�����a����"����AI���TaF�A��Y��
����^%������� .���|��B�m����NF����+���-M���O� 1�,g,J�Q�x�b:���V�X�A6��:�(@���=$�8�8�n���$�JP��$�P�$*%c�/I�j�E�5_�� ������TC���xLa{�?�Z��Y`}h�c��L��B����?���i7�s��EO#n���9�N'��W�n57��Ur��>�5�8�6���CC���JC������}������Vw��}����e"25f��u4���*C��g�fZT���P0��p4�r �d��������WFL]7��&��J�)���G{�f�������i����`\����i����A�b����`��W���r��� X��s3���6����s�_r�E����'����������hM~� �,��)��F:$�PE��,����9����%����lXt��-���m`	?e�*8b=�n:t��5�)��y���&�BPb
����z�x3*��dX�F�n���������������&��q�{�(k�C���!���
#�='��i�K��$#�����J�s��`"�e�3�k��_��"��MT�I�jI����I�
?�F6��������u���O��4���f'F���|���v��.����[	����2�n\�-0�x���A�z)�It�x|���#;�J��1�/�pT����
�7>����B��#�3�\+�)8�8	
�1�9�L�����~�L9��A����X�)��������.���;�J^\�����P���QKV���xr�/��d�d�����tlR�F�����5D����'6��v"�1�m�z�
����\��t�*��G��E�j�����u���7G�~Ma�"^Vk�0��-x��0v��p��+jm/������D�!^"��T�a6drSU��(���b��q���'�����N)j{<1���aiS��T.�:����{Q���f��I�$�DN �5c��M����o�b��R`�c��W��v����Q���zzF����qo���=o�=I����h��d���V���E��TEN�^+���C�^���})���Y����[2��Y'��G�	e���o|�8�#m���������3��7Iw6M�\{YI��3�:����
��N��g	P�!����:s��9�#�vi�e(�
��w
L<7��%�9��u�i��`I>Q��7��X�Hkp��% d CT��0?�
4*mK�����	�����8f�J��S.�����R�1M�u�=��4��6����W� ��XieBvI8c8b�9�X��Ub���}�J�6y��@�<���IKl��		�&H���e�`tk
�lX���
���A>�f�;���:��&�>����w���g�e�D�;����fhj���k���'���[�t*L��������.��:�k ���������
3����I
e�7���uxe�\Z�P�de��G�����u6������*��vY�
g��K<zI
��Bvz6z���4��
n6����}����C���C����a?O�K���!r_ ��ftB���pRc������h�$����� d�{u�2&_�@�]��N~����{7��A]�i�uP�h���w���{���R�T6�$�XDW�K&��%;i���.��Q��s��G5���������8Hf���3��H+w�����c)��$�E��c�k�6��2�<�kojI ��ft�I(��C�v�*I�x�Le^��g��D��
�3�#	��iBO�X�k(?�;uou�����BI��S]6D���`&(w�V���T:���n�X��H&�z����l�t ��I�)���lR�w@�4�u\���	l�{)O��1���^��]_%�<�s��z;9�J��l��]+R��a�X�a����qH�l�f��V�P{3A$TB,��	�����}�,�!���<�����"��>d(�lN�Q�����R4�HH��s��*��i4p���������]R�ix��bR���aVR.��q�w�LB�P�M4�
�$�RZzb�������
&'yn8�����+����t��q(��[a3J-e���q����C��QL

�t�IH��"��bY[
����1��h������"���oy3wL@
�b��0~�e�3wS�$���_�b�p����}�#�yk[���ln��"��s2��d���iI4�+��%��D�_�q��0�{�-!��>���o�%����)����!�g���O���>�y�QH���L���T���Fd���@�"�w�t5���@�����Y�l�4)�>|�������.�E�M��:���
��3�������������F����e�>������;`�1�:>�T����~��=�s�L�tH��G�<c	qX&�e��#����s��sch�x�6g�*���H�9��AB�����H�R�S��,���z�9]p�o��2����T�N��&����?��T:�IE4>1�T�)=�89?h`\1?_��v-'�Ei�c�	��Xk���@dn�y/�5��8�����b�H��b.��L^5s��k�W���D�C�[���D��%#�"U���\�\1���Pc�$.-f��	f���a
��f�+����5���~�������G7�j���*,���mK&�
���J��?-���������Eek#.��L.��U�cX}���?���������|'a ���w�l6�������S{9�	\�����xm���D��R���;��N�ql>�W�����v�`Am8�f��O
i����[ J�������D,�������U5����3uqvo����-<����������y���{��|�&����G�C�v�D�����T��%�$��?�Q�H	��G=�k��G�U�^S-7���oq���2Ot�j�<B_��=l�^5����������� O>B���^�l�$#��7�G����7��:'�\��LC�)����M�]0V��@���5�����_��
d(�����H��tjaq���8z������M~����{�"�I�u/qG�s^8�u�'�����A[a���������������%h�,�+&DP��������2W���$�"4�<��3d���5QW4����z� H�W��rL�n�h������j.�q%��A���6�:nI4��2:��c�V���`B.�W���UC��c|-,�J�	����;���^;-H�&�������3A���2�y#,1�u\A.�M����M^I`�
KP@�42���'-|9/(��o=

�\�����E��`�:�_=4��|�7G��9r���(|��R����/PL�wA���`��!-��(�������@X���H3����f�%\�q9c|��@���w�EN`@�~#*wP0P����<x�M��e+0��[G�����!N�|��
�i'�JU�T�]��P�5i��!� ������&U���y����Kf�D�y����0�Swh�)���;'#�Q�T�\�����F
�VS�S���7xAf�w���Q������ ��5��D? 8���`�K��?
d^�g����hL���x��j�o]�?C]���c`,D\L.��b:2���-G���"s7;9�����o��p6���v--a�D�����)Ows������y����VUP[v�tT���-�X��]�������@�A.�$�`wO���G��D�/���#L7P0dC�����@���!H'����}�;�BK>�!���J�U��t���b!�#���9���c�����6����������W�������_����������M������_��Z����-���>�_���������(�G$3��q���G��)$A��\��n1M�9�(C�N(��Y����V���;���E�SL=�Bh���� <��K'����@Wb���;��Y"�`'�LR�!���o�la+���2���d��E({JH#�W��/"��g+��V;���N��/9,F<��c���d>�4��m�U���m������WR@G�	��BV)���&��i�������j-B���]����
;�_0	2U�e�����p���-�@��~�t,v������J�R���`���7������4���@�X��B�G�d`�n����
��)����y���r%���������j�����r���%��I H��La	�4D��[���_�Y�����a7�F��\+����8������s����g�o���<����.��2D�p��D]b�������S=�@N��h"������C�����AC�a����G%�i�lb�a�9��^�Q�(�����c�F���l��* �<RZ�������������z��i�)���:�P`+Dl*�:uo�����9u���
3-�iz�>���S�V���s��^2,i�xi����pg���-�L�������_�v�b��em��zs������W�7���8���)�3cI\��XIY������%l���x����l�t>=o�(.V����.�6K��z,Qqz���F�,�����h�6w+��!~W)U�Vph���&I�������TQn�5����gD�i�{/���2��������������hA+/�J��*g-�*�!�G�m=�$
_���f(�L�i)[�C��:��������)a��O�>��Q���b����X��a��2�0��9/p�tW)����B;7<���2X0��-���������2�v�am���`��)�_�BI&x�0S��K9�)���c�*�9���L�c���c���W4//���JI����� ^y+��WY�I�3 U|�	�
U��x�p_�B|p�+[.�Z+G�0����2
�g��@��#`
Q,��K���-sj)���+���P4�3�#�����$��������#s;�z\[
8xi�P��( �+Z�+w^�?~h6���"����y4`kZ��w���B���+2�y�FV����W���I��A:����qY�~�p��G�F����R��y�]B��N��������6���F�L*Z-q���p�h?�
�6�=Xsh8!+#3���6��w�����hg���DFD����^���q���~�fI�����T��,��Q*
���<�=�����W�qV��\��r~9s���Y��9���[�v���������^vT��+&��Q���q��S4���i����l��h�Q���
T��=����u�MmB���w~"Qe|Q���6>,��p�bc���G��P�61��lLo�����KW�� )	��$�����W���o������y�|���}�Q��7���S:yj����Q<�N�k[���F���g,V�H���i��?��k���Fo��k"����Z���j���ZG�qX|iR���(U�+o�[�a��5��F_}n���F���oR<��4Z=�0<8���[���bW�S������
2�$�nN�k�)�8��q�&|`�=�0i�`��0�-6��<�{���U�]W9�k{{�������M?���K�co��>�Zih������.��oe����R���+gH���.	0��Y��������mL�5�����n����*dv��@��
1�;�L�� 2�!A����,M]�j��9�J�D{�B;�~Qz�.j���F�f��5A����
��I�]��&NP����������m������m(Q��Z/��-����V�.7��I���:��O��z�>�x�L��V$6��a�A�]-'������k��k0X�sG8������������h:�����G����s�wl�w�b���R�z�$�y�������o�����l�f�4��t�"W�yt�	t�8{�eS�J�=����c�b��~��trY��>D7� �[t�X��������`s�A�|��O����h�D^�B����i�_���{��P/��w��C��>�'����9�w��H��z�Jc��Q���}3����L��Kw' ��P��g8$�L<���5�����ZoO�/������.H����Q��U�Q/��<�e�D���>��N����D�Qr�V
4���Imv�b���^�e��A��.W/�gv��@�.�������U�dfl��H$h@�3���'rC���S����J�[)<6�o%7�_8�`>E��g�����C���yo���wu�����T:�*���^�C�{��IF."TN��,�v�������������N��/�t��`��/bd�B�Q�Xi�\�8S�`�k�����J�|fA/}��/k7<�2�����0�������Wb��m�.��[�3�������4
��0��b5Rv"��;�;X0���8�~.!����s9�"^�$�6���l�5S��������c����"�i�����e����CDY����bL��]L"��_@"=O���>�r�^by�����5�e�'��y��K�����0_c$1��^����m6_���a��D�+���nX�v��(�'�dG��k^u�yE���������l
4��M��3�QdAc��������v��?�������g��0�m��#���DW>D�'�Ya���6�N,<	��-P(j���p�+������+A����q*8���7�@@�U������P�t����_'i+���D�L:��v���(|��7'�L2����/�z���$(:$[�:�!�X�B��}G�g�,CMv,��;�A=8����N�3��R�9��pt=bFL�n���Q��
���(q^��0G��?6���+j��i�.�uO��l8�Xle������}3�m�cu�
3����*�6V��(m/�7�n�)��kE�C���W�D�1h%/��aKe$\�r�xJ���"G6!SMoY%�EjJx��7�H�sU�)f�X�����bF
��b`��.T���0�7�.����|hC7�Y5�H����w=��I�,��@�c/�����ET�I1�;,��� 9
��'�P�/�I�c6�#��[s������G[��TOt���9C.��)C�4��������G�k/��k���)�V�^����Ki�%��o)��O�Tx��a�]g�H4����s�A�e��0�-;���t|���j��r�a��8&����8T��OJk�������c�#������4f �5EY}8���l�@_ ��uj��r7U}x�����
������Mj���z|�G�yLI:���xqh���.�1jcK���&N���9q������w�^�U
W[8��K����#�M�	�._�@6��e��e:��Kkb`���T�/dY�]�k�`����1�T�.�����C�P#0[�f��������-�G������Uw���R�,��#;���k��'�NL,��n]�]�oEW������V�vP�6�HN)%�:|"�
���*��I:(GD1"��]���?��j���'-��#�����f TiNQ0�r����"�R~�O&U��s,kw�UhZX�}Y��t������X���H,6`��$�`�f����i'�S�^M���!�N4����}�8�>������N��� U(g��SoM9D��l�K�4��os��b��H2�K-�A���,���M�,nL���=9��_��G�����y)-��}�i3��5�F�$��V�hbY��!.�����":�5E*bn���f�����	����k�#���P�W�2�cok6��$�&W[*�b�<
��X������W�	�?���$M��C������3���qk�ee=��(���9�n�H]Tf�-_'�Y+��S�#�3b�^2��'��a��O�`�t���P���9]E�s�8�)��Oun�v�TlG�M����<Fj����+���z&@C�9e�N&�Q���xpK������k��0�64�:4��
�;�����FG:��T.8�#7�#�Tz-v 3�U��r����&&S8�����3\�1]��B�K`%��
R�("�Dd�l����L�]�T�@/A��5��*�bF�ol^M�
$��2����W�8����s�E��I�%
!��D�,�H5"�~?��\q�R�<�q��60���
��V�����1b���-��.���^���zZ���r���{��B������%��#��>"�������
2���{��6d�����<�?�����K��v��{��M�d�<��&0|�����W����;�6���~�e��'D����o�-�`3�;�O�z�M��q�������Z�;���*����[21�����R.QKvW�-�TxTB�-�-����������C�S��>�f�2����|b>EP������BT��N��&�Z#"QSR:i_	�W��lC���"]�w#�<R�>x�"jZ�q�t_��������(�}�Y�Q�0C�!Hy��U�j�z�P��O�-�Y�U�{M:��z#���U$�$��@�q������AW\�XU�]�
��T'�P<
`F�D��*��c��x�|��@���}���C���`������������u�w.�����#G}?o��{�I�����>���!�5vN�1g);�����z�����S���@����Y|x��\g���{7�^��d�w"��8Q��1�j����.8����L`��������?b��N?�s��:�
�h���.����\y�'�<!��K���y�V�`6#�H�U/�p���%e��lFp�tq�?��.m�����^������*�S�����[><��JE,
���������,_��YF�Y�?�4��u,��Eg_�R�Y:��#za;�[�M�7���������5�������"��@�Eo��C�D�O�MTEP5�L�[|`�� &qa//?�h�����5�
��!t����
EB���J��=�X�`0�j{����%��+j���EZ"?I�b.���*O>OP��?:-��v��j,�n��c��B�m�A����>���<�CS��u���i���8���?F���G��W�d�'_������`a����5���UZ	��M��"T9��4(�3�l�������/���u�L7%�������."�0b�������<�U���^�0����d}*x���NF��(���n����,G�<zw�2V�;��ME��Q��c;P'��i-�u���yk�6�d��Dc�K���;�b�k�T�fc����8���9���w�P�u��\��~vQ�3%x�`��v���{�{�O�@�i�����MP���9��P�<���~����7G�{�����G�������.,�} k���d�<�i���KD���P��	�fX110RO#�T����1��7PktO���U�n������5���^s4vl��ZEs(�;'��8%����������M���B�sx_�j^�f�J�d�r��ya�0���R����G�����\�����p�����@�;}&�0i���q��J2�nD�R����lls��8X����$�St/d�n%���\v�dt9���a�q�A���?��'
C����
������R��K�za(�C>'����m�]x,�H���<���!H8DQ��Q�5���Q��q�������/����u���Qe�#+�
���f����/L4U��� \O�w%8��}�?���H���h��}��H�QP��\b���,��7M�Lt
4"	6`�A�A��<b��w4�P&j������:���I���;�����e���C�����[
w��������4T��|�S)�OP�l�u
��3r�q��,\��}����
�p^��3{@��u��"Gh�DOYh����->plx\���2M�/i�4K��N�
!���^n	�������<�:y�����O��k-EG���-�0/:_%����.�$���B�YII�6����!�x�8��~:B�KcL
���s��s1IL��sC�]�p)�ANuZ ��������H�q�Z]s+�����8�aIe#���76���G�D��
���~z����
+G>�J�?u�fy$s\������.�:������A�/�v�u�M3Ez������0�r��T��t�Q���O��'��-����{)\�������|���8I)�0�� Lm�N��D�C�uVyzH���Cd:;���>{����D���f�������w�����V/����o��?^�[�s������4v�{�C��?��R��o�5<�$f	����:y�r;���������A%�"#��)���~�I�,���k�sg#~0�f��`J:���L������a���|HD6���@
��#�������_cL�:�WF��Dlt�G*���e5���CJ��T65=��kQ!�mU��@.��P��6�w?����������B�cch��FDsM��2V�����7���]NG�I�v�;����o���)LA��js����+)��L��B����C
����M��C��}��i9�U:a\&=��R8N�cd�� ���WA��������W8��{�.���7u"v��z��1?��-�8�'q_�\#~l�-c�����0;C���;nh���i��	��;0
d#'�-I$�h:����l��y�4j��&N������M�SE@C}�?�-2h�-�J�Z~R�O��gD:f��NV�p
W���u]�b��I�/9���
G}�&���y�&�rA�N�6r���r���I�u>F�Q�E�����$������p�N�~����MGy
�R�!�����o"�*��4�����`~@8�O��B) ��$bc'O�ibY�13���������7$����tBqu��
7R�Z����I9,-I'�^c�������]#�z���Lc�
`fA"7�q�c���
0�d��������)�((rZ�S��	}E�hY"�>�k�G�0�irR"��%����{���In����4���s�V�n�<��xb��t�p4\r���0��TA]Ie�����#�&�F�R:8�:xw\�����iND��I�G�0��(~��|�w��)����V�X7VZD��XK���dSXW����_A�e6�2q�|�H�&��2U��a����|>X��Z�`�.2����	E�`Q�/�JTf������z3j��8�}	��������b���cay0�������J��,I�j����p���L��������n����5u9��)P��>��I*yI��v�3GZ���*&��.~6�WZJ��t:'�
�a�kL����<�AR�r6�Qs��E�[>�m��[9��<�L,�{������&\����������mj3JP<�|�a��7�i�OIU$��"l�+F���DtH���h���y�O���f=#�����U�7����m�9U����{N[�7L�<���,��V46%��[�g�q'M_��\�����S������k�z�d{��&��i#:��)p���GoYzy-�1Q����i��������������^������l�!�cG.���� (n���y��x�u������]t��y7���+���T������}�K��`^;��*�P(o�O�l��9�.�����[�)�pSh�o��?�7��RusxY����}&Ci����g'��./�YPv&��Vv�o�}�[p^u��"�D��\t)�x�q?Rb��[�p�	�QBp�`FG��%���a�d�1����S�Oc�O��N�����>l�b�Z�� ��/��?E�:=\�G��B;���%�7[�Aa�PE�/_8��$�FV��F�#��x�qU4���������������j�N�M( zg|���$�b�s�s�h�#�e&	��u�
�s�j�|;�5:nqw�m�\���[�#���v
�+7���md!�F����=��P���eUMnP>��$L&��L��������D��%m_�����]k�'(���B��K��U+�T|O�2I|�L���;SB7������� lm	�xJHX���u�wSg�\H�����sQ�������.���@�����h��N�N3qF"�>@��-��u��b�"[
41&�Kz��I���^zW2B|6%��`��a����tb,sihh�?*��,9zww4}�]w#RU��y�����Z �0_����X��lf/'�:�r�������dn��0�����%��PM�
@$�sua�
@U?���E2�9c���8��]�e?���L��cH��r^i���1��lk���WC��A����f�I_��PEj�7���W|R�����A���a��o��}�MpSj�0k$+^{��u�����YX����������/@�'�'�-��-�q�o�%X�26,I�g�����|��h�T�hFjWf(g-g��g.@�l5P��q�p�<�'_(�������#8���dq
1�9�L61���N>l7��
�&�'�n�A}��R�G0�Gl�"����Q�D�kf3�X�20s�
N�a����-�sa��}F�.�7�7Nr�e=�����g9:$�z	��F��q��c�!���-X�!^#��VX1'&�$z��e��ln�]����1@U_Z����$����%9C�1����i��Ph
&�z��C��T�f�����bAO������ �e��L�����z<Sw����w�#���$���)�;�$��l�k%j�S�a\o��&E#e����Z�D���f�C�o&p�b5����L�h/Xc���%��X�1hW-��]���z7���="��YY'�Z�����vc��������������_	~z��,����?�3��}})<X�����[�G�����]#�����&�Wmp4J�5f"�������I������p���z������$cf�#�{�����=<T�j��
�.'V%��f����RP�@$��v�E�_z����KNEp R�t���������R \�,����X���{����s���f+�����G���J������0�������K:��J���>�����^�n����$io��@k{�����vp�z���E���	���}����b�C��>*��2(�>�v���k���?���O�VS|����3���<c;����+�@�����\|J��X��	J��*��3wn�$��� �v�]Z����0g��$B��6K������YG�'������$I�^�2�:��?�(��T��\����\�A�
i���}�l��2:��w����]v��!�*y��'�2���=��@���mC���(���E���w���;��$+�����v��%��L��t9Kyy0jk��{
��A)f��#������=L[Fv1�r�B4HB�t�F�
P�b�R.!�R��8�^���-��19�/�F��.�N�s�HrSV��<Wky��|� �
s�:�6� p�����(�xj�\��$�98�t����XO�'P�I5I��`cU���)VW�=�S��
l�Qq�r�F���V�
v2/Is+1�~�mk�R<��S���.����O:��R���tJ�SQ"��{���t�����M�B�Z�]V
�t4�d�������!�������,�����	��>��E�"4�w.�d�/M+f@�h�b���O����!U�#�"n:�����n^��D|1|��a�FnxZ���w>�������)3P,w�X�F�V7��UJ?v��w��.T��4���X+N'�=�C	�0�B7�c*YS�a��j���_��%�������a�U��w`��nnj���
�>�C<���L�Ji����b��4����7���F���*�����o�1B[})��m�����P�]v��{���	�w{�����?�P���s/xH�������,K����w1��
9@�G.E�,qT���n��6�l�=�PR�Kj��Lnm�qX�E!9�;��i�T����d@~�G[�%Z��m�����V�ac9X������bN�#��i�no�1����~�N](�S�(���oz��%bD~G9e��0Q��������G� �M�%R���S����vU$j�Z�kQ�$�8P�l�+q<x���W����;��H�w���}M�����X��G����q�FI��#G-R@I.!��{��-���b?�%��w��~m�l��y)Nm?f��/����#�j���W��[4�T]: ���[��������g'IA^��f�|I����%=����O
�3b�b����0p���b'��uK(yQ��u��*��.|����]�}������WAm�����YD�7�HfU�G��!]��MQ:������Ch������x�f6RUjxW�u���1t"��A��#	/n�&��{�x�T*f����}���"R-��
���\�||S/q����Z�-<A�^;V*�]T
+2.d�����<�|Nmj,$,Y^cp��}�q�x�F����c
��Z^/��A�j3�.~n��o��X6b	�����#j,��T[� ��j��p<�i�|�O��:�y����
�!��?��%�Qi>�;�j�|X�����xr����_D313�7�p�[����X7��H@o?(,5|O����?����qX��aS�##�2�a9�u'H���an)�%���Etq�w������H�H+��&��P�2���>{I��z(`.y	��6T�=�fq^��A.���1��"A����ie��G<LM�r���O�C�����������|��(R���	5��$|�A����%\0#��I��T�pan-��q���x���l����Q��X��!uL�w�%<��NZ�=W���OAe6�$�_dka���Bq
��Vq�,��*[(3`���b�o�<��V�G0p��iOF�/��?c@m����%��_~�H��_7a������w�;8�8?������hi���!���-�=���:b1��,���1{� �bql�=�Ar�Fw���BL]w_+qLC$x���h���;;����,8>u�g��u��>n���Q�e�s�`WC��S�"��8G�� ���S��G��"��	��D2!]a`��*��[���[b�	�1nc�`<b�1'����=���������Z��>j��%+����Y7A�#+|��h>Z�������n|��*;L���F��#�Zh�-�*��\��y1�-�)=Io�(��}U�����*�����Y�p"���'�����z�#�Rm>b��}f���/��ZC����Y���?;�F��nH,r�+b����3P���5��&J���cA�8�u�$��������x���a����o��D��8�WQ��pV��*��p��xm��]�9�q��4r��'���d����#�g��B����^�����i7w����rUM"��;p���z������]b����
yG�����Z�8"n#0
����lU�ET���
)����^5��m���3y.��,H���q��"�w]��vG�Y���N�u�W	������`�J	�]��fu���W����M6�	~;����FL��R#��Cc:U�Z�`���nwj$l�G�tdx���D��!^��FOy"��CRv���+�G���h	s�������B�
��5���B���*d�<��
<���DZ|N8�K�o��dZP�����uH�NTW*����B5��}�H>}����1���YP��q��v�$�pDL�����]���o� T)���7���4J��i�&�������:�������W�|R��o9�M�d��I����>\YA����c����;���)�TP,��N~�����K	d��1�[zN���0��E��4���C%��LYRiP�+x4��^�t	q50��["���(��ja�b����yvl[G9�?�17r��$D�F�3�����kt�ky���e�BsFRB����2�.]!L���	�k�p�we�$��B�D��h:*�kh����z�#�`?#�����!��2��_�V���E��
;.G�8BVp��x�,���b�0k"���]F
���&1F��Z��%y5�
R�h��|DH(HD��~$v�ro�)h�b��+$a������d�V�dx�yt��+<�L��yp8�V�#�*~�!�����G�n��"^����-��Q��2�B���g78���|v�4ca���z=�P�
����uK�����TL����5���}�Qt���&����4T?�D�	�dM]^�(���i���"#|T8=E|����/���L��h.���d��p�C-��M*���>P|��s���rn^:0��r;����$�f8����
���|f������N1~����_x��%7��z(���yh
fK1�@3��IF�@�1`i�,���O>;�w��0�5�g��j2�x�$e��p�H_`^y�8HU������K�cb����������o0[��������Y_r8�M�A���s�x�S������k��
>6������b�CWM6�p�U�;��{%)J��t��##�'�[�� {ez*�����`+���q�������[�$���l~��A����J*�����YB�{wB�4������7M�z(�+�������60�H���1�+���^9Hb�A�!��`�4~��"x��_B�U���G�k����_�������c�w�H�Nw�,���I�nb��N#��G$�P~�����m=�0dZSV+���8�
k�vf���P��}B������1f�Y��b��Ke��������$ |��"����e�=`P��hn�y�C�=O������L�O*Y�N}T��\���l������^��5���T�7���(*�K��XH"�2�^��s����\��s��]�P���LYyf$�b$���c�3i�O����IC^]'��@��P��V�x��14�����~���u�����Z�X�mD�����Q�����\����~�_bVJ:@���,?���2]%�w<&������:� ������n����2t��<N�=e�.�}�2�%�9���D�t(X?�K�Xm�`���R�]���5^��������[�����	�>We�"�;Q����-��y���yM��TP��4Oscc�h�#�(�*��������*0lK����N�%rPZ���\q]k)�
f�b >�SH�(���d��$��b�\��ZBd�20J�X�m=g�+���p�vF|M�����|x�kN���\iGMiL�����&[	���7�h
<�)��"�P�9�d)�%���u6���P�`>�|N?*����))1��b<x�k��^k�6��� %M����&@8C)�fz�jM9���]��K�W)����#7������3����ZWU����W8�����}�u�\�Z`��v���k
���s�W�y������x��|�Ku���N��)��!��6���j��~��x�(q�����@���AA?������9<��P���_�V����W�6H������xlf�d��6�}P$�bC�Z`����l�&^�%1��wSS%G�6CM<
�A��X_�$���o{1'r��D_���OG
[�^F������f��`��2�tj�A\�
�,A�
m'#����rx/j�Z����_9��D���Oz�sJ�]����j�����z�q|\a�A�8x6�_���>p%�HT\��r��#�X����
2\G1VnC�KU(��
����e���U:{"<J���#�S����N����������
�t��EJ"�@5y���/B���i�D��/:������@��g������n!R��� �[wc'�T��b��x�d��H�(Y�8���Q�#�P�=�)ta�y�$�I
�M���
�q	�
j� ��oSk�����	� �x��e	V?�T���|6LL�'�Nh�n��xK`W�����IFI?��p
rd\��^>��V�I0c��e�r����"F��p)�6%6����)������T��X�4Yt
�)*\���o��~��
��$��d-uJQL�qS�M|/x@be�JgIcs�
�����in��L0��v�*�C�{��������	���WD�E*G�O��������������^����O����0=�Kqyl�qS)<�b��~�w1H$��53{����-(8��-;�T/ ���������������<{gi=9��Xf��J���:��Y)%!��4��F#�&,[������sV��q���#*���#�w`��;���/���p&�������2��z�v��]��Z�8v������}��+�����)��'��	$��<�����)$`�rs�m��u�e��zA*,�2��(�S���E
��D����"�{j�u�_!�c`��1�
����(go(����JX�2���������n�W�s�z���J��t��C��������"�"�
UdU���tW���z��#��Pd���C�"��Iw�Z�����U]J���(lTFt���_���M��Q��L��������S�k�V��������g����6��g��������Ub��4����#Wrx��[p2�V)�����I���f�yh�FU
#�/W�F��W	.�`U��EE��j����%������`�d�������	4���� 4�&��!J%6ia�LPs���A�fc�1^��f|��/��h
��D`v{��Q:r��u����u�Md*��x9 T��F]�������^~-j��I�>K����H3-�c@�0�13_�(h��B�Q	&�7\����+����
��[��B��dQ����U���N�b������m]�����,}��Y��x���u��j�@�
*B���.��X����}���+[������a,`����h���^�������2����5��G�����s����[�{�\�����V���}�����H�3��e��k�RLF�$�w�$�k��u�'J���-*��1g�����&��vw�/�5���	�~E��-/��p�zE��s�>[�� X���KbL�R�J�6L���J������.���M��N=�e��S���m���\��m��_	J�=��E� #�`+T�������{���K�v1Ua7�@&RL��!trA�2�1!���V�9~�(�8Qd�`�7�p��:���������I`�����{�0��+L�m`ne���(��'b���v�an���ih��~����b�������o��)-�%��@�����2_di2��Z!�$��Y3je���p�U����l�\��o5��KLt������u�a���La�e���"@z�������d^���cb�Xc��!���9�3���tH�V�"�a�2_�^y�������*x�e���y��*@��,
����a��d��,��UCoFo�Z4�R4��[��Q���\��i���h9q��1W2 ���Gh�1�9Bu~KdT�2�%��0�9�#_�h���'�a�,�
N%d�{>�J���_����M����2<�`���Q,{@�i7"w�����_�.�3�m���}�������X�Q'�'���n��,����+����N=��S����4��9����X���`{�_m�I�h�z��Y�nER$���H�8V��-��){�(f���f�$���$=K�A��i!�Q�P�z�^f,�yv*����&�����s\�9��1n�Zx�s�b9�!	�S�'��[�6_JA����nPL3��P���4j������)�M����m�3�,�������'����B{!&t�z��%�!����%����b�9s��n�]�5R*GT���@�Pv�8ge�l*��_�*tY�8���LV�^�
6�_��p��RN�(���e%.[13T�g���J}����������BaG��t<���I�>8"P��Y�9�$g w�DU'4�G��d��%1K��6����$��{�9��\��RZ������d����Q�p��1��tf�����/�C�����<hz���������/���V�����.G��1�+�����;�W%UU����C���Q:jx���(����<m�\^o�^���x�{eAH���������Q�����=|����x���2@��(x�t
������+����c�	�6�����
�g�u-�,�sv���E\C������!�����#,t�x*��R%�K���b��@��gRN6��I��u �U��s����y�����j��	�*�eY��(Y��U����z/,�F^�~10����l���������4"#���K0�q2d	��5-�a
	]��z����9i+�����J�X2��UC������+��_���Z�����j�@�5����4�^�?��V�U���`�nz�2�+��Ji1=���VJ��`lO5��K�Y�+fU=��B�v�%J���k�����=[���}�����=	�����f�c�I��=*�"@�T�@Y����8+�0��b���s�Sx���AX�t�(C���/=����<��ph��D}C�G?�\!����Y:1�<������	1�v<I�@�*�!��qC[�G���A�L��� ���+��#-!�����|6w��l6�%A��q��t����{�����
:�`N��pF��3��I�D����!_�`bB��������"0�vI�}.��T�C���NK�qz�o��X��Hj�<���G�[p�_M<��q���a�����I�.��5���+�N�B�pj���M�<�{�\������Gg��b�e
��E�-��������$���3�c��s�~�Ml����T@�����h���	Y�BU9��+G��������/�.�����t��>��5V�?"�a�]�-
�R����r�y^��]n�
x�-3Fb�{����2R�U����dc�9��$����n����2��Ye��-�\A�
����A.D���o
[�o$�'�{�2,_�
>|+O�n�s~����N������%Z���{�3g���7G����7;{;���;�g~><o��D;��m���:�b���=�,O>����*i�[�RI(7$)�cKs�r7������#A,S�$���8��=d�*|(1G4��������������<$�j���:�����Z&
�+��O���P�E��=�F���NM>u8q�U�@.�������KO&W�DV���f���?z��z�9m-��&v��x��y`m:c��_��}<{s�w��{/p����A����%(�����F�J���z���K=��*R����"*{���B}�<�}'�T���ur��J-H�������nq���G�>�.�a2@����[�x("i����M.(#��2��
M"bJ!"�N�Qy"z�Iv�:i�l�e0��a"���l�L|�sO���S�E�6l�c��a@�O���1���D�RP���S����_CjE��6[�[��4�����[4��q�`�1s�%�?P2wrX�!��a�
t
H	~����'P�F�c����4vx����7Y(nc������j��R�ez�p�T��l*eX���2F�jP|e�c�eBv5�;IU�KT�����+���(���Q�;1�k!���s��8��I�D{����\T�9�a��!_�v(���m���5��a�a<]hn�fXw%���Nc����
���x���A0����Z�{�L������,	��P\6kk����e�������#����b�\`�5����8�6���x��&Q��1{P������3Syy�:�Lb
c���0��jZ_������T�>f@�$���:��n���E���b�!�5D�T��L�z�jF�r������r����b�*,�������@�������5B��
��@Fq-�kp�i<J�
E[��
���N��y��'�f|
,)2�Y).����O��l�F^\�tknn��*�#��$SKM��j��
����I�6�\�k4�xU�d�R*E��'l��l�3��������k�	�U���q��=�)���!���Ln�"������r��X��},��0�/�b�f*�c"d�R��1Hn�q�[��k�X�{�%�����<���_�5<�13�%���������IMV[uz���Y�}c���j�X����y{�z���gja�W���RN���t��X��Mp�g������6y��5����h�����X��69V9z���Vi�Uk�O8�`�#6���]f�27�3WA�-����aw�*��#�$]�*h��	1�m��A���un�<�����Ap���0R�51^7�r���tB��Kh)����q��qb��kx����
f�)��p�"��:��L|8?=9��y�����d��/�+������9���>�_4�k��&����A#�88HC�ytX<H�c58|S'����|�/���?�������K�_7.-�Z!:s��FE0�ku�F(��	
�+���tj�X��r.�IM�YV�M�wL9R�%w�����D�c���i�x��x8=��T�($�� &Q6���������F
�z��B���������B����T�q��D���I6�zo=�����dk�#U�,������?p�y��h�+{������m�j/tH�,�E\��(��B�)h1LX�T�Ut�B�+�Fd��c)��K�P���$���X!��9s�h�U]����9G�]E���A��_���Fm&A+;����+T�s]w��9������W?_\J��l��3u�H�Scl�Gr�n�,��YQ9e�QbM*����g`����wI������Y�m�gDb��dy/\�y2?-E
����2��]���S4����7f����v���=��w�i%,?]����:����4]0���XY!���N��%�� rZ����/<u�<`��2��Y�<��(�Z@��QI��X���}�C$N�Hz�"�U��i��{/����<	f�*��[�����L�+i�s��:�� �pl���
�t3P v?�(}��Y�IU��t��uv6>��K�Ku���;�w�Uv�9���"������h5
�j���S~�_�i||W�J��\rZt����mnn�b5��\3lU�z��5,������D�Z��i���;�n���3���������F[ZTS��+�j��R��W]����w]��=[�O��[60��%4M<E��i���4��nO�p���{��sb|	�0A����{�4r�����#N�N��|,L��eZ�%��:'���K��
�t�p�������)�lP6��b>p"�`q.�X��*q���l�<�
������d_x*?�l���"9��
�\F��)�C7�m�@nB����h��h;�R���C
��\��&�0��.g1P�4IL�4fK}������������
`B�����*�2��R��E��v��nX�?��r[H�))��)��:���B�
���)�lO��-���4������QD�1��,e�(�g��*_~�|�l��-�����%�.���s&�=0���!%h�0�D�I���?�B�.��c=�a��?����R]�j���G�������@K�/\{����n�G��+V�Z/���4g%V�pw��
^)����b�IS�>\����J�A[.%�5+7��4��[�"5���\������U���T�N)
�NYH�� ��a�D#�S����r�?�:��T��R��%�6�������2+�S�W���J9F;���X�L(E� �Q��!���)2O%%�a{������P�O���E����=�d��� F}NF�-d�G6�{�t`�6�]@�e��I����8������)��Q-P��������`�$�l�/,�y�M��~x;�;h#�]Y�;b������l*5���0�^:8:'��.P��N��h�E�+|+�g��%W�E��1��t�����h���!���@5n��S��3�����������AP�P�mq�����[��>�"z�����T����">�kb�������(<L��$��g�|��T����N�o����j�<)����8�7����6����".�,\�� ��|U�9�V��%����ohC!�����3I����]������8
�t���?o�w�}:�������6���3��:+ZH���d�Dn>�����~4�������1��{��At��>j����j�CPr���/���A2�$��
��D�j���R������{B��4��	�DJC0K���v�Y$r�q�O��
����g��0�����0C�[�i�+(��~�����(K�#F����d�JS�j��1L��A��H�C��a�^���V�dD�O+b�5�O����F���� �5�t�#���g��*�G?��B�k4��2(V�a��K]hlP���_�u�O;�Kk����?P��#��57N������J�A�&�M�+�.~���e;�^����v��mn^b8�V>�j��s0l]�O����Q2�:�=����7�����������o����6�����������e����������?G�;;O�k|=��.�8��[����kXql���h����G��iR!�C.��������@�N�^����6���X>�tv����5F���EO��#Nd��;a���zAQ�/��t�����V���,�����wq�l~�?��N��(�]�!\�'�5��?�[��My���!��lu.1�&��,3o^mT?�&�I��O|��}��.�/�T����r��->������n?�gc���L�����"Hl��������+��r&c�9���	);%1{2E���l����k��5�O#���Vz	���&��YDk/��t��^���3!v�/<�zA$9E�]�3��[0!K?��`��_]���%j}�C�����X��J��\�N�f��*���x��A��3S�E�sq9N�T�{.;q���|������lv������,�S�v�3�I\�oh���E��n�-�v���^gc8���a
RO����;����9�������������;�,v���������������k��Z8}�I�8�T�Q
G�1��,u���G#i^��mbcp.z_Y��[�m,cHg��62H<�<.�$�
�����l�=���>�h6�`{����?#�Og�)^�J�H���>��Y�����'r���>{H����>��Z.`�N�1s`F>����!�9��E�������`�0���N�^�l<53xX��v������1R�+�p��[�����!Q�f��;A���AlKk�1����[����~�'��w��m$��((B�7����?�Zsi��3�|,7{�C��U2p\��������q����s��6F����bp��R+�#~�	��Ov~h|��3��S,��i�]��(#�&?/�}&"���d���>
��]A;^D������$�\xn-� ���Way��l#���=��n������S+�)E�E�v}���@��	V� ���&���{�)mC��X�$'�T��GhO`��.����
(�}���0��c�!��Lv�70���D�k�<@q�A x���Q6<�������rBf��V/��X��'����EH��8��&��)9�%�1h�������\&�����C����ra�!�s8���:��J���jp���7�]jQ��}����gX��c*##d���Zz�U���p�d4}3�:\#�9��%��P���qF�FqwJH_���&a!�z�#�{����������������oR;���%����+!������Q�������t�M����c��1�w�,V6e��Yk�<�2��
�4���5�t�,w���!~)���RJ5��5t�(���S��OE{�(�tQ��d�-4BW�#Lb����j
�;p�N�/��D�)�`w�����
L���7��X��x|�.�3P�-� \N����Q�&����w���6���P����o����y��$|��un��������fy���U�/�d'�%v%0 ��=l��;�o�w H���D��qp��S���WW�����V�X�o�>k�����8�����h�����#u�Pu�P2�^�J�):W`��$1�BDNhG�j�6A/��~��7��/r�?E��{�`�0�
���5�aoJ�k6H�o��5��'Lz��N�q&!�������Mj��d�ym�a�G���Q$�����Xj:p?�������MX�Kr���S�e�r#b6�<z���-������� �vZ_r��`��W�f�}r;�s�b�<pn��gza��i`	���F�L$��}�3�	���:09��*65���riH-��+������&��9��.T[�����\�����}���d��v����x���������B��#�j�c�������n�U�}��bTh���Y��\���9l}*�_Tw��'eg��C8j

���%HK��%�������hm�=�nq.�������[�<���W�z4��U��W�h!�;T���z}g����+����������L,/vV�9^�#$T�u�;Y�g���H��/llZQ/(��;�������r���f�_rJ���88������Q�}�o)���#���3b����^�o7����{�}�,g����7���p�|���?O���t��'Ld���O�HO��k��i���(�s&�}-�#���_�Z��9����0�A=|K�-���P
"abS���]��(U�SH48u�6����������$G600yxV�r[4���R��#�Uf��y���Ukw�A!u��IF:M!v��N;�F�����\����;~�A��>����9��;v��2���t��T`�h�$�5�19�)p�ZR�#���>�|q�Nn-I5��Y 	E)jo������:�Vk�m��u�8A�	�?|���l~���7���j>j��BO�;���\8O.1�f�@�D��S����x��$��q�1\��;��\6
Z��,�B�l���\*�c��=�3l.Y��f>vG.��M��������&������w�^����;��a~)*2���/aD/)t����oiZ~��
m]����8�n ��
��`���&-�Nc�_����c���PR��1�jD�O��i�������)������.G�@��\
�i<��N�������}��rD�43���qM�#���%�3�3L��G���7��a�!�w��z�L�c�)(x��!��%���@~�sOC7�=����o�4�<�tV��p�V�F �g�C��$O(P�.]����
�Z�!g�h�U��'0TbO��?�>��=�l��I�A��#�3g'�t�m�/�:|������\z@<e����s�'��@�8�	Y�5 ��Y��������6�
7�p
ql8��TE�2�U�n}��$�������iW���t���j�W�9�B�!
z����w��i�#���a��G	zQ6���M6�Q�A��D�������u
/(����J+���K�������.U���K�8n�����S�t���n+���zOqN����<%�Q�bt*����u'�R������=
x�3�4,��yQ|��iwM� �a�+����1�����~�k^��>�
�?��@o��op3t�D]��Xf�9���<:���D����?�a��������PA�y��38m�G�&N#*����,Q����S+]�2�t�I�"����g�T�r67�����rD�|�<�&\�:V*W�D��P�e���/S4�*�����wi�
��G1�V���u��0���67�P��i#������6��?|�x��;ywvu�#���z�otbs����I�=�zI��9�(�����������L���&�
�Qb��z��������]��N���k�V�<�I(Ub�<v����5H���c�D,�m�@T�����z��4�.��RC�mdI�S]w�)��%@_1��5i�J3��T����<
.6��I�/��N�zS��J+�v���!g{%��L���E���3��7�f��aL���(�br������r��A��J/2��"�LX���H�D/��bJ��1.���I�)�^L�(q	������s�����Q�SiH���9��om[C>��	/t�������?(Q
z��n������m\:C�7
k�H3� ���5�"��B���CXXLBv/��~Jdj0k���z����uq�~����Na�c0H��4\��[������z���e�B���\����s����N5E�@�a1��'|�"=�v����|$�b�����{<�����;��'=J�+�G"��>�0L0��z����Zs�	�Ft��8�.�,��d����
��:e�"h����d�l���4GIL���f
��r�~�5:�������
�`{�D�����$m���w|��b�V�~>���9�A+h���l:Y3*�Fs�|`3�a�X0������h�M��`(�G�V�b)�)���#�-d+,&��u9�V�J�m��t;��;��l>�&���l�)�i��>�<B�.���fn����~�q�&�y�:�l�;g� $-����@�Xo���}��quk����^b�2EF=B�J~4������|$���H5�1>	�%�
g�������Ft�f�}v��x���8�<?<;>���8:=i�O�N��n�����k�d��	�&e��fT�4�7A�DP��`^�\^����!�<��x����n���F�Mi��W{�?A�Z������=@�_d��gK�0�+��@;?�k�d����u�������G��Np�������`p������E������D�4���OQ#��HB�@��mo��f�kj���D�T�\���d���>Wfefej�1�Uy���{�Z��!�6T�pD��������w��'�J��_=:<Y������{� -��Zm��1m|�&�%e�@��qQ���J���S�Dd.�i��d�����p�d�����kk��L+T���V�o����0��!z*�#���k��*,��.(	�����n���a��h
�{@ZC7�P����W��zx�.��v8
v�E*��;�9�M���m�F�)�3���6���)��6������R��jTT��T`�y�T�9`�$��	UJw���]L��n�2�
��]+J�];�:��2E��&�y����fJ�*�v6�����c;��������n�s���r��SH��u��.=<AB���5%��!��a�go��4-��,� �������S!-Zja��r1;��0W�r!�(7��vn�5�� �P��x��/��E�"V�)����q���N����:i�7�e� ���n��t�
���y)s	��`�k��p�"�Z�;���o�g�x�|�A>���r~u����V��uZ�'$���j��P_]�W���v�*b�~P�h7����<�w�^��������7��eo2�Nzq���9��]��=�	mi�>�/�x�������Y��/�9�ea��N����������0��,	�DC���B��H�Q`��7��?��O��=�6Iv�d�8Z�t!f����w�1a0��>7O��$
�W������.1�t:�Y�r
�N�������Do���n9��1��3�)���z�J���z�)�0>�(u'w!Uofy+����r��i�V������u�����G�'��!�����\��M=�+����ff�e�&N���B&7��,���������$�I����!��o<��pLVA���m�%�����7�@7����X���Q���z7�������_'D�
u�F���������:
��F�pNm�'*o��^�6����hh�$0IJ������,?��aY?��8��\�|l�wJ���o..6���|�Q�#���CU8%�������6�@��]$�(
�D�� �+�F%H,�#�������9L��p��J�3Fyf�n4������T1�"������:�&�_2D/���������H�&���'�	Sg��&gk?x�?���e�d��H�R��G�d4��G!F�f��
V��&A�����k��������(���[�{�����8���}y�H�d~���k��^7��.|�������9�FB"*����m)���
���m
WgQ�o�����h�� ��A���9X��7�9��7����u�y����y���������5K�~v���vL���*>\	�;%�*Xp��:C�_O��i%z�"��G�B���a��(;e�Ld�?u����,�,����KD�`k��D��Z�h�T�T�B���*�y��]��p�������G9rJ�$�cn���s�^Z����f��
��TEh"���������)m���e�Nv�@�;�MB��L_����!��U��fg�ZDQ��Oq��v���������� 3�����NC�L�)��
U��h����g�������XO�y���)���6��?N&.$DP�Om	o@�7p?��#�����s�K���x����hkhBwo<+�Ak[/9
�7WW�v�q..�0O�O����T�!0�I:c[=T���9�!�J����|)�7+?_�l�2Aa"�,��rW�
������`�/&h��$
d�|ckZ����R`q�*1X$����P�U���	i�_
���x����$#�B�[�@!�c�v%�O�q���5�f�srb(��e�����W�M.���	��D��{ne��5_s#�}2oD<z�Y��8EL��d����9��(�������+R��a�_��B���u9��bq������'��2A��D'G�����V��Qo�����]@0>o�o���
`�96���.Xa�Z����vS�|�T����"*�k\�z�����l���l�T�9�G�4�������h����L���;Y���PR�&�>.��!����v	!;�T|���s���r3����a]�eu_��5�i���.n{u��������/���m_����b��;������)��&W%]G�m�E���:)�+�����:wgD�s�}@��S�-)PD�����,CZ��qRp�����
��;���J�H���hf��N�m��\���0���mE���h�3CFbTH�A�
j^��������MAu,V�A����@�\���'�$�� [�6�?�&o��E��WKQmIq*B	���D]�<I�/�0���A�Y���Y�GUI�OP��V[�� �����Wf/kP<�OMu�V�<��H.��Sk������A�"��x
r�Xf����AH���I@����:�����/�_$�v��6Bx�GO��:����
��zk��a~��`#���qf�d���}�DI2Q8Y���q��,��;r"���@��"R�1���GL�1r�@E��`
�0��\�
���e+��[���J���@!v��R��p����`�~��0J����pP��p�}�'2�T�I���L	�N+�����`>�s�M�
�;�I�J�������5���������\hN�,��x4�
u�m�Z�K��c3��l����g����M�g�7�4����&2c��>1 �$u��"#Z�o����k�i��V��-gD��CEf��MZ������f���f�������J�
�Q��W5��PQC�v�����r(�(&��0z�t�\��`}�����6�������
��$������jW���v�]�u���GsC���Q$Jb�����h4�j���Z���:M��m�<����b�h(p��������A��O��(kH�_������|S�:+�n�t��w�#�}������h�h�Z��j�Q�g��kX�x��"������=)L�t��iN���L|6���M�,�1���;L�=�W���?:��t��]���aD@��G���
V~D�������:�|�(�(�
[�w��+��������~_XH6���<y�_mj�K�.o�w���u.���j���>��z�/g���������sm�3��)��MLju�����?�5�TamA�������
���O���j���r��)Hv��wm(�`W�v��owO��12+���a�����S�G���d��\���MG�D$�U���/��(��GV&��T*�:����X��5�S�w��N%�&������Ad���	���O��d�w)�y�����B��,M@�(�fG�;�O�bX�o2*�6:��_�S9%��h� LXF �K
�J|LU�����|��#=E��km�wFC`Ic�Xe^,Lf�Y�u7I��A���{@�O��I��My8����57<����:C��8���Q�e�l��M�.�S�:p��g���P�8#b��
��+��^�*|�Y�t�?)e��������5��LgdX��I8��T��O9�i:��Ba^Yt�|%������k���w��0��]��/����P�pgc�]����������\cg>}.V��.�%�������esx��V*��hP��_z���^6v2��C`�m��g[V�&r�����@V���+�;�-�y��x3��J�5=�Pn22�T�0�m1�C�@PnX�u�G7�sP�q�����t�|B1�^��|�XgJ���n��^�+���)g�/+c>{�e���� q��hY�+T��8�����/�9�C��a��i��!�hh���~�];)�����:z� v�c��Lh&�@������JD!gT����U5m��/��;�q���]s�c3$�K����g����������c��8p}��<��x	���C�fq��
������8e.N����]�|� �Zh�iF w*}*�J�~YN�h8�w�]��Al��:��7����m�����M"��B�n+���ME�Mg�'K�x�������N_����&�D������������\ :�'�C��N�e@
Z�������:kP���=��6�����}y��O7XTcDX�'W5P����<p�g�}rcc��R��P���N��p�:@Vh���<��
(���$�_A�>�%r8J,���@�q��FAn�+����Y�?�����g�	�%G�df��;��i�p���Uell�B�S-&
a�@�xyO#��u�}u#6�`�y�bW���R�Yw�?�����O��y�n�����t	z��_F�^���jK��-ZVf
%�8�����=��q������(\\V*lD��v4���F��qv��C���L��&=c�G��
M)��sW0y�m�[��-H����"v���V7��h��b������#%	���Y��-�9k�V�@��}�>��#czat�.r:���L#
_��3��Y�P�$~z��~
��������f+�������+��AZ
�}{\,�&{��K��RD�1��?�L:��Y���
���x���J�zR��_��K���|��0���'��:?k����5�� x�vk��Jlr��/������u�6�}�QJ�������7Z���	�StD��H+�"��QTr�QXQ��i������&������M��,H�I��ll��������+�������hp&9�������R�?	�6�I����7"J |�#(�/*�����a������A���6���6�<��t������E�����������g��7������9�\e���S���2��Kw��a:�����c����z}���l�c�*X���e�e't�,��
�=�@��q��Q�v$����'�&�����TO���Ec�i�U�G���}��R�9<aYS�#�<��t�u��>n��/�z��C�����M�]������=�{
��t��F��#\�*#5xR=��W��x ���|�_�u�iqtDg4
pq^�#�t��B��#�\'x����N��[��y8��D�M')	�����q�O ����h��G���&f�����8"��Aa-+�p���o���q{a9������������w$}��LHA1!�#���6%A�si��a���+���A	J��J�H�]�m�$��;���X?�>�nk�FY����.�M�(�����K���3G(��QJ�W���{�Z/$Jr~y������
���-#o��	��;�?6�PUa�F�*nK���K2�$7^�$D�������yE�����%!m[��	����Nm��W6���j�9��L�d�>���KJ�Ga��N�l�~L�4h�O��<C��q����
��=o�o���j�C���yU�q!Z�%1�b����8"b��9�M��(��Jn��������93�r<[�Ma�A�|��qt�,l�f��S;���^�u���S=9�K6OV���{��~��n���k]	W?������Y���@�O�����q���M%[f4�rp��k��m��h�HJ,�:hk;J�[��V&[E���rm���A�x%�MJz����4�Q�{.e2�����=����7�D�n����R����U]A�<������	
)H���;w����k8�($�?1����l_�h���.��#��#��q>�O���;	��2�DA�PDK��J��L'�bXR=�\!b�$�R��0���}�ZYBq�����Xd��M����"���T����#�l������*��r�KL�C+�L�c����)���P�p���aSY�6�T0�z&�?�g�L���Z ��74�_�u��xf�g��(l.�J������4��:��4�����'G��iQ�s����Ya�
��C�(8�=�d�����P��G#���y����r�E��#t�!��I�
�J�y@Pj���������E'�����d��Tw��i�Q��$��I�7��S�m����l��w�fq���������Xn3�EF�+�X�`����g�*���e��u������s��z���o��I/�13��Rs�dXD6�/(��??-�C-��z)����g����)�YRu���}K��gB����A�4�E��E[C�I����}�i}{�j��6��MP��s�8��-���B�����b�Y���Ua���;������wh�������N+������
1	}�&a�-L#�[�����%�f����-�O��U��"]��F��e�O#���<d���CuE��h��i��L��`��)C�g_���N����`2�9a.!)-"�#��E��I�%�Z5��QTw�����+�PU����gt�d�g��Oz7�v�~P�qT��9�4��r+FeD�L����
6I��#�{�����,�A5���P��017��Xk�V/^u�
za���(=s�0�����y=��?��l^����	(1L%1����y*|�M^L��/1Z�}���F<B}.�f+N���'<�KZ :��|I��/�\�n#����l>�������D��������Ss��DXA��`�Jszk\�v�;&'/[��$@�1���9R�����6���$4�Up�jrkJ$r�f�4wST�4]��jQ�uL�d|�hhx^�*����	�t)�qz�����+ZU�X�Z�����hu&2�.�O��H�:A'��fTW�UjH���1�c�E�Cj�����s
1LX�����N��e�S�\������s,��4n���E�R���s�`
���M2U4�IW�J����~���-�UU������VU�+���>U��+	^/���-��U?_MQ �6g�+�F�Kf����V�sA\wp[�U�D�\F�������|����'�v^BG��@
���e�:����V�()���� QD������o<G�`K8)W�z������_���z��w������O�:QC|�]�5{���f��o���;�������n��X]��@�p'����Y��Kq�v��]f"��d�G���%��M%P��q��w�8l����a]�<�;��Q�"[)�n���B�^�U�X����;��v�O��r8��m���F����m�g�|�i�%���I����Q�n��,�����!�`����QK�Q�z4O))1�;�2m��ED��k��}���`��a�e}��8h�<^eD�����ap�����?][��Xd�`��S@���.{��P�?<���%s��/.���>=��Zd����2��������l`M>�[��7{�!���|Vi=�8'�����W&%j�����RK�tGr.���H��t��r%�f�z\��^g���c�7{���Xv��I�)]�""�`���Qp�?�2	V��Mx�5n��w��/.��e;sv^x*�;�1q�����2}�M������{���
��0�s������f�h��3Pv�70����8�m*:E���>(�����*�S�G�k��0V&)~%G�5���v����9�Q5n]	�4q:"�A@/�����HQ����9����Xc����a����\��0�Sa�?�!S��m��ziCi�/���#������}C�A�������y���n@���X������9-2d-&�?���������_F��Mv�E��B��w����4)��eL���r$i�0�'�`�T��1��B��1��@C�4�i�����,{&�u�Zc+Y~����� ��1��1^��i
����P�>
��g�����D�W=H�1��pI����j�?������YW�)
~e�R'2��~�@Rb4�T���Lwb��L�zP@�9E�h��
�d����w^��C��uf�F���+����@��,D�h�E���[8R������s{�x�=1��[%	T��%�pd%}
f�S
�#e�jjH�5AS��1jt M���%�@R��:HE�f�R����u��Sm8�{�r��s3gNH6��~ �n`����9�L��yH�����l���6b#���8y�a�(�x���!<����IO1�c�	�c�`����n�������wIm�����b�l+fB?;�3�xt��w����F-<��)
����}��:x��y-���6?�6s�i!����m���C��(T�8�x�k��@�"fI��i�P��K���C$Ni��J�������7���$������?IP�#�����������}��h�`8��/�'��s�I��1/��9 M�C��}CMC�v��0�)�N���AH��B�whr����K*X����-*��	F/'���DvP.�.�Z�V��L	U
��Vh��Q������e�m6�d& ��8�����:���vP����yKV�dH��3�����#I�~Y�%���k<kJ��.��lnJniI�8L-G�����>�Q���a@������������AE�:��f�;��h���d����x���z|`����fQ$W�p��Dna�:����a�L�8`�c�F�.�]���u�"P�T��@MB����b8�Tw��7�[�X3�F���26L8/����mw���i�$,hY^����Wd��
>�_Nf�o�2�]\��$[-tQg�V|#"����+T�%-�[�4�h�6+�����F��{{K�0
,W�)�J?�=2M#4���N�
�H�������$�r��U�<��\[o�w�w�������������
��"?Y1iq���4,�X`i:�i6���	}�e%=���C
�x�o��c�> 9������i-����4q<�<�Q���/��0�m�a�^���������Nw��j4]X��XO�:��gF+[�!��D�\TnMY �*��za��|�d&�U^F]6v/5�J�
e3����*J��T7��(��[C�c`�w36����-�
��3@w��B����,�W�iE���F����F���n~��v����c	��|�V�}��6���A��D����5�a8�����c���DxZ����wrD�V	g����H�r��V��m�"�'�n��d��D[�1��7t�@[���<�[�!ST�7��H��S�x[�'�S|��f�
.�\U*B&B�����j�����;w9���g���+c��cS�,Q��(d$w,92���
�@�r4)�Y;xK����Nc�����#��	�1t��A�[��sA�Z�h������2�\G)�3���d&�
+���t��Uc,1��|�|�����`�vXGD�/��sI���ZL�i���������d��),}rz��M3	�����dY�_�s%�������7p������$��u����[�
4t�4�b��2I��0����BH�k��\�T�|Lw�Q�Yh��&���x4aHs���}M������vb��/�m(����SA����[�_�Rt�������c.>�1`��t���+��R�|='���a�]kj���~`'��6;����J���
2Z�o��l�J�ti�����gN������:Uz�&a��^Vpt���6���	��)�������}4So|�x����E���.���n����Z��R�r�2@���1��QQQ��`�W�)|aM�z$��*4��%��D*�b�^C�R��3b����������`-����jZ�_�o4�:���+�wQQ7K{�O��kE3�)������u�B��]Q��������
������NOz&aBu�����������r�Y���dM3g
�e$����Z�E�U�|����g��(}H��q&���B����~��?!����,3(V����`��|��B(�C�������bf:������+�5�?�
�V]�-�qd��r���p�z�>nkr�]h�f�Z�2��?�����2����J;��+��������������.���a4�W���ns�W�����n��7/�M39�a�$M��F�Z_�^�,�k�y�����v?�P�3#��"��V������~�T�_`��"O���J�(�K!o(��H��:I�g3z
j�L���QXS��2��������i�`�$�q��X���J�L������Lg��?E�HB�=���Zadh��]��h/�//;�����������c�I��A-�V�	HI���I����<�r�%}$M6vG5^�'CQ%O�EP�W���:Y��n5[��u��@��Yp��)L�\�7$��9������zM���J��t��-+qm�����8�N0����,xz�f:�O������H7+�rk�>(�U]������� =#�<~����,wc�9���hU�q�S_���]4��y[�:������M�		
C ��U�mz�D�^<��(�egI=-������0�FO[���7������9�t��H��"���3^�	�!�Q�1#�~�
��E�����#f,j�tA�-��p��|}�^���M�g���.���]��3^��Ixv�[�����hN�Y+�y�xn#�x
��N_5k�����-�YXy+J�N�%:����u��I�j�/I.�C?��!�F���~�rt�;8=�T�K�S��4��J��qb��t��-��)�,c�3�����]���$S|�c�"6@����~L�%�"[r��R����O�-h�66+r�����]����
���%1R6%@��)������7u�VBfB~��A�b����N��	�,�^���?����u+��(:��-�z]@���$8��U�w�kE0%[�������K[/c�\�������!Az��I�9J����q8�{,��tA�d�)��p��P����F����c��-����T"��T^z�?��=����2�������-pM�xo���F%	�p��2����q�n��_�V���,�x
�T��*>���n�}4�?���(���k�tj�o����,�*!�}�T�C�����	L�y7�����x�D��|>2��o/DId-\-������_���X~�4�tH(C^`H��h��t���tVb��/0@�"v�9�`����X��v���J�`���z�|Y����N�Q<) #~�����=�N KA% *��Dy���&�N�u���TH����RH��7���6��S���7�E�f,i`>^�y�Q��,h����N�e��j�Y�?Y����E�=��������qt����K�h+?����Bpd	����5�^R+GJ����e��dR��+=65P#LY���,hq�w��[���jX;>�U*�^t��.A��/��,��{8{�v���D��0uY����H�����nr�(�,|���{�D��[����WS�e�a�5S�_<�H���w+����y�.�w27����a��I�{p4�.���hf�{
PFQ��/���Z�7��
m�r����+s���Wt������.0-96��V����f>)4x�L*����GH��sP�o��x!S^�r�Q�G�Z������W��D�������i���Z){��c������}�E�m��X��6�,b��������F���<:����D�x�^�8�����\�s�,o4����6�?f| �A6��������~���:9��q�o�h.����j<9h����A<�fI����8m)��*��� ��k
�;��A��!"(DsB�0������+���� '��C>yvLj�`�C�Y���S�+�������WMNu�R	/`m��2�C�k��"���*h��1F@-\�Y`�W�N��-y�������E�d�%�Q������O�r*���/X^����w����k��E>9���}!4��M��&z����M2G���*���=��D{��Q��;��������e)��8<�8��Y%�U!�)����,�=h5.�v����;k��*���&b^��sux���LR�M�G/�7KJHY�OQ�Q��+H��b.�n�4GE5@��*����]V�(������"��O�J�?���b�0PU:�D�T?)\������w���
�������V`�0�;�O"7�%H���M���q����ym�Vps�J���[��M
$�gh�����^ie�F�K��+�d����8������n������
�-M�eH���=`��~��za!�Z���	�c�{��J�$�v�@��j�~��b�jm��p3����5�.�b}����<U���c��>�w�1#��I~��s�"�}�����Q����w$��
��N�@
�Wg��u��}@b���!3��4At�u�r�L&bq�[�<�)�'��U`��h�$�C���w��g3H#�����p\�;�����#�E�P#��)�b�F����k�O�r��%�!���r�p��E���)�xi
2��@�-��y�~���������=w���oi �V�R�q=�d�78�9;���w/M*}:{��0I�6l�,:�v�G�Jr=�
g����7)g��?�X������������F���X�_��>���f��]�i`��V�C���Og��w������������]���N�
�d�9*^�Z����T>J�����M��"�,p����	~Kf���N����N�x�:���Jy��g��u��c};m���8^O��d�����M
����z�r\9�$�Z�'����/��V�
0005-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch.gzapplication/x-patch-gzipDownload
�'P�R0005-wal_decoding-Only-peg-the-xmin-horizon-for-catalog-t.patch�[{s�8�����l�G�%Z�[�&;��$�ul��L����IH��"�|�V6s��� ���$Suu��&�
��~z�{+6j�f���u,������{����w�Fl����p��������X3}���W����z��y�&������E�Z�/�������_��Y�M�����,�&��3a��e��Uw��;bGm���M#����b�����~`�����{����g����v�
[���=�l�-=��
+�{>3y�oQc!7X��@���mr��lj�V�Uc�o���u�MS��R�5��+�d��o�
�����]K<��i�oL�I:���<������45/L��y"�K9]z������9jo���!g�Ud�����t� ~���J��P=p3�V���I�����Th�Y_�Prh{���w�	��X��J��V5�<_��~HvS�Iz>_�c{m�}���������e3�YC����������3����vA�(�y��v�1I�j\qn|��3���F���$wo(?��t���F�5��] R&0�
����G�&����Z�Z���s�j-����g��xvH�|�����>k��������z=��=��������_Y��6A��������� ���}jr�L&��&{�x��U�2��pL���_������+�+�������pC2gHD����"n2{��^���19��&
�~G�d"��u������C��{�3�����8
\�9����������?���K#����d����,�~L7+�[��j��
s����]'�����N~��=CH�?�8����@���K)��`+Q]���%�C�2P*Hrb����	�~�0��.F�0Z;�Y��G�R{�
R�o��#���4}0������gSi�����ab+���`�(�?+�`/�JQk�%/U���G��P�M�0?�t�;���r���rcH����t�+�q��F��w�M��>O��Eh.ghg�|��|����K�5�L�!p�G�k��0�Z��Hk�,����m�������5�F��Lx�g�e��&v�f��I�������?v9{�9y�w{}��b���9:�����R�~�����]!����U��f�(���q��nP��AF.-���zg<�-�i��9��8������J���C�u{�	�N��Cq����g�a�gs�/����i�}����w���)[����i9���F�q�cd��x��m.��[@E��w�c@�����c� D��C?��s�������.�3!V��P�<z�=��<W!��b~���;C�
`o�����,��{Mn\���Fa��V]Nt"�;TB���D���N�b��	fa�(?$�s��^��el�?��D����/��]o��
�P7�sMz�k���t���o�h
��cvD�������v�85����2X'aKb$�������~������
,�!��j��!�X�,P�
4v� ��J����1K�������A��ax��TH�zdw�5Ap!e����F��2�'�u`�,���fS������+�5��oX����R��69���c�'��l��d�9='$������)�������m����Xg���}�"�k�OX�PC,���,�3ki��b���J�3b�F]�>Kt�o���"��*"��4����C�w�	m�=N=���w�W|]>�	���BJ\�����z13=G�'�t��H�h������;�}0��2f�{��CkA�P�B�\}���DBf0^�g�|�V���s*��������@�BL���X#t�a
5>��P�b�D�����������x8L��6�c�N�mf��#��n�y���"%!����K�d�#�OI�������������?��@
�}��b�%��vFc���LM������=�u�c��nQJ4��\��1Z���7T�� ��k�b�gW�
�����2F�����L���"�=����Wv���_��dFE������`���SST�t+v�5�&;|G���`jW�./����	�/���()J����`�j����-1&mni�w��������=���I)$	T$�	�+1��}��p��=&u������c��a�IG������X���5���`����=l5���T�1�L�1�����m�X��Q��5eY�����1�����"@���hp+�[��4�Ko���!r�K\i�����q0	UQ=�~�����c�`>��#�A,�`0�����4&�]H���HY���R�����G��l����T���p��m�5%�B��J�_��,����wz�<��zjY�|���w�'�����0q�:����V�0�X�)�����0|��c�e�x�!���Kc�@]oh���;.��k��"��}t������0�UQ��v���t+o��-A"/�bY����$K���)�����m��_%.%��^�������4�G/r,��aI��
9F����
������cS�:{����,D�q��=D)�Y��P"q
����Gr��)��������������L�;m�m���$�f
� s_��N�U�^��~i�)�C��z�O�/�2�]������@$�L�P�%���"�o�
���
M&3�|��ev���U.��r��5
\���;�������{<Y���lPp�4�/<�����7=;6y*�2������ar)Q�����\�&��n��������M�+���#3���ZP���O�iF�����(��83x�"�������=w ���![�CIC|��m�*�a�-�����`��v q�s� V���'w6?
GA���Y�T������
�U�K`���g<�P���?J(#������=i���q� ������h���kP�<n�(����L���Y�O��H�z�Mbv����������jvy���tr9;;?�>��z�A�soL�OP�tp��nB�?�� &���bR�M�
B�O���.��-�d�D�U9
��������aC��z�-�m����%T�j��
���6�������������?#�����|�@��Q&�L�+�2f2-�#����LOCG��N�Te��A����@-T�\���[j��TB���e(�_���H'��#��e:���)�w�x 8 87�C8�R#0*I�E~ �Hzal�BSH�g�PLX��3XJv�+Mg��A��p�������������Srn���g}&�r��������s�Yy���3Z�:���6��X��PS��ZC&�.|,�/�w���D��7�������:�N<�x�u�M���c����.�.J�h4I[m&�?��/��"����[���..�/pY��2�"�V&��sA���Ie7�S_z@�1�MNFb���tm�i<����B���b7�W�����S��2��3�����N�2�T��k��p0���hd��c�jN�S��X��z�n��Ox�6��;����?L���"��<�Z�D\~<���dx���E�'�K/��v6W<�4vG ���R�g���������}�`�	P;�[u��g�R����9��b�������W�)��j�T*5Q��'<��	�q�;����@�Y�6�
3����V��}��h+�	@"�pA4��X�P&k�������r'���s�~iat^)kU�H�;B@@�;���a9G��h��`C&����f�A��
���'J�C�H���E���f�t<,��n�#tAj��e+\t������Nr��LN�`����P1C�9D���PXd*��.����cs<�����(���{L��1ZN�+�l��E���:�F6i�a��_y����{�N���~�<�CG2��0��rM�0��R�q bK@�<�������P���S;��j!/$X���
S
P�y��+x�������]\ai��hg27?<T�hc�O�#Y��of�
!�mf|S�����L6=$�VW��B��}G����)�1��S������,^�{Ey�����o]6���Empn� ?4���8ycv;C<�b�I�P�GC.'����x����M��'>I"hz�u��S�� �5W9M�T����+n�����R�H����.\P��Q,����5t
k)'�D�Mc��l��
_����E���Vt6��/�a)�0v����>��%�u��!��mJ���G�l�5�7�����k1���r��qQa���A��`2���<Rb*!���2�T�w��S��,~�3����!�������^*��hFZ.n+>g+`�IE�A�1�N��.����a	H�B/{�&n������:9�[\�����,sPV�����P-�������T�Z�������Wf���2����Aw>2�cMu-�b�:�-eRLjK�����G�A��U������M���)Ou�����>��|�y�|N����
+�������gE=����#p��^�j����1AB�]�?�O�q��^�@��(��,x�	7�|u��z�MC�{�1�QP�:������l��9P{z�j"v���"$Ux�fjD�R��BPq���Z=&@���=�X�=b"����bq{�����"���B����Qep���=��K�OU��e�?w���='>�H�;����R��Ac�����]�kh�`4�;��kW��hR=F�)�����~��K�&�;�� ���=F�SW�
Y����z�#�K�:=cG"7�~wH*.����8����~���br_5v�T��5�=�kj����n�|����h(����0G����z_��������!!�]qp S��l���Z�G������yH�pz�k�$�������Rw�z{��lt��!�RZd1��\�����W�C��0K/������}���Ln�?]�1�4�$!��x+��0Lp�`����y�������Pu��pL~�kJ�����n�Tn{z7�;�}�L������F��R��������r����
��������G�ju'!�����/�w�e;�N�!���t���s�_���5"��f����\���^W���,�����Qt�G��v�o���`%������*�)�)r���4���y����kzRk)�����!A<���l�n�]1��z�eQ���~�2�x�_.��3��
��.����^/��a�E�������=wS��i}4��G�v��/�l~������n�^I���%�����K"��m��#2�����d0���<��(����K�_B���t<D]�����|��Y`�Z����"F
0006-wal_decoding-Allow-walsender-s-to-connect-to-a-speci.patch.gzapplication/x-patch-gzipDownload
0007-wal_decoding-logical-changeset-extraction-walsender-.patch.gzapplication/x-patch-gzipDownload
0008-wal_decoding-test_decoding-Add-a-simple-decoding-mod.patch.gzapplication/x-patch-gzipDownload
0009-wal_decoding-pg_recvlogical-Introduce-pg_receivexlog.patch.gzapplication/x-patch-gzipDownload
0010-wal_decoding-test_logical_decoding-Add-extension-for.patch.gzapplication/x-patch-gzipDownload
0011-wal_decoding-design-document-v2.4-and-snapshot-build.patch.gzapplication/x-patch-gzipDownload
�'P�R0011-wal_decoding-design-document-v2.4-and-snapshot-build.patch�[iWG���_Q�/m�;'2('�qr|<>�������[�2������E����Kb[t�r�.�]���4����z���'=��d���?����������^�;99<yv�?�c�:������S��?�����~��3��a��J��S���x.���d,S��
;���q&35o����Ly��������t�?����~�6���l �]�N�^�w�^,e��W^��t |��i,���#gbq�9�R�X��,�b�!
�����Q��n�B�^w,�{��T����Y���0��c�=�����d3�~���C�����z���������s;���97����Q��rx�����Y��qp�/*�����7���8|��A�UJK�'����R	�(������
�4}�	
?�LD�=
2!�_!��Wn�j�,���b_}���O�s��`r<�H#D�W�n��a����~�I��[G�E��O�f���k��!I��k��B�Lg�B-T`��$��0k��r�N\':��Q�h4^2_��
���o���d,i-����^�I>'�:�,�g�$]���8���r���/2���*�A8�n+:^5� ��8��4�s�	|�����]��"-��Ou&��y(q��l%�Jd� �sw�X)_c�$������X�$����r&3��_�c3�$��J�UI��d�x�w37��.}�2o���<�!�4���A4��v�$���P�r,-c�dX�RM�&��>��,�f"��=�c��@@m�=ad�i�m����H�u�-�z�KfRLU�R��� =O�r]�
����<\��4�8M�	v�����aq�8\	�-f����#Y�������2BjV$J�6���2@x�0����k9�A��K�`�)G'�5H��2���,�{�&��*��s��I�C�BZ�t����/�u�A�C3���N+r�R%$�d����P�S�P@�_�5,+��R#>@��@)�OY����[h�L�Y�����b���,���n��,���#�v�t����r����%��?��{�������Ni�U������<�u�����Rz��a�sp�?�J�)���}
�`��)�2������XiUQ0���L(y;�xoD��dB��:��8�.�:#�kZ�!��A������Th����{/zF1��0���J���.&�`����������H����Ac�+�h���U�����O~���H�y��K�9����4���B�4?��-���1e��)l�	%k]	�#2p���,'�,�25_���K��E��<%%��n#��~
��,��>Bltd9�2��H��&���	Tm%�<MA,$g3�+�����c8f	 ��sB3:h�j41�MP�� �����f��^ay�~��&	�+I���B�f�hR���*�_A�c��zN���(�a 	���<�|�b{&fy�$���KX,��{=[i6�I�d�U
�]M``�����1(��h>��'��Z��UE��b�-�z?,����=���9����\{T����
JL���2&��^�"���cL��% ��b@�wO�A(3��q��"��
�!
�� Yj�<5.�:/�}^(��L.�]"Lx�z����t���$sQ���{MP��E%�Y�4�`��"E��Y������3�������{�8����N���S�h�"���Y��?s���5�t�zZ�	�}L����^
�)<�5��E�ko��D9�i�I[F!b�+���R��-��2��l�q�����Y<������Z~�-~F�lG�V��ja���~�`��%�#C��q�1����e��Q��`�SEl�PVFPV�8��sh�UL<�%�q�0	p���?vi�^��,Mb2��a����NP� ��,�����M�`��s�NN�s���������B)]�c�Sjl'c(�M�Gz�<�:����:"J2� X����I�h��Fs��B�M���<� ��>��.8&BI����"p
��U�������)rh�!=4�OLLb�V�����r�2�&�9{�8�l��s���	)��ad�js''QD�27�R���������U����$�n;���}K��B]D��rNac}mN=*	��0��b�Jt��;/�ju�B����W��&X���R�A
���s��i+$j�M>����Y^9��O�&���l��`C/z&��}���"��g�,V�������YB�t?����yF����q~�����"����"���fN(uKs��"��������r�Y-��(�*R��t�q1��E����y�*�2��y+�;�d��e����&�!A��HZ�"���0 ry��Wfd�.�@������L;�*8E��Fs����$��1\wp�� .>R�[(���,lc��e;���30��W�%"D��M�y�@��'��C��X1P���b�2���--I
�c�������sz��6����{Vu.����Y#d��u�[�r�n�k��j���)����h�Y-~ ��F.		��di{���mt�*�O�A�P��.d�����h\��B���Q�l���e���z��~�q�M��Z5�s�W����:����b�h:��&Ca�a4|r���mF�k.���o��C����LJct
x�]�X��\������������Jy�8�"!E�$���'�6��^���1��/W`��ZU�n���3��M���
��y�2�j1��E6.�Z\��Vu�h�8�����UQF���$��dT0��(7�� ��4�h��<��*=
����lU��H��)yb����Kd[x�%(����l����x�2E,Q�
��W���Y��Q
�:'�
�(	�Z�p����Py^f~i��{gjX��/
V�i��j�\���v-mu���,����e���fd�z���6���|�2�ZR�C��J[�K&�p�8k<Ho
[��������!	�L2��Zr����P-@���<�C�a>
�u��W8W-GZ��T9�(�x�A��iAv��)��.3`�/8yDM�".��� �E��]6R9�i.
S��}��
i�q�.�+j�����8�c_<SA��v�@�[D(��F$
�
eaI*����b�~j�/�<���"MWj��Y�nOd���)��H�2�I���vj���4��
�G����ut�)�3C�����w���m�F���Q� ���n_��v���4��&�.�Ehq���	�'����Mx/����O�<K��'����������Z��iv(�
����7��I�$7��n��-������:���G/�_0��a����94L���O���v�Fw ���u�F�&}�J6W�����@e��g�HLh������vv�����<����<�YYh��n��hYUX��s�|�,��jdE �-j�?[v���|��aT��.-
^��6�5=������s���S+������Z�|����j��J�n�GW���2g#�����C3�eQ��R.�1��6y�*r��l�m��sx���P����O��Y�A�7����P}��}~���m���97�s�{A������q�Z������.�}��r���S��mQ������V��7�}������0<$;���K���r�o���
6���nXn������SL�H��4�f|��bE���-����S�E���z-LB��te<0���.�U1���d��'�Nl�=�+�J]&�3�Y�6M�6�eX��:�.8�QG��i����\3��r�#2��e�"ul�-'TD#u�(�M��QF*<o5�\���\�+2�01�G�����t��FR�\1��v&+���������P���Ufx���dS�_���j�)�puy��r��y�����56�&��,������U$fD�������.@���K-3�$��8���0�8���+*�U�B�v�EY�����d3���mF`���������W;�#2��uz�4��c�e:���Jc3
�S���2 ���ms
R�PI1��D71��)����27s��7w��X��dyq	��Wd�I�MH��`S�H�Yiy�J�e��\-3���yq�mn��0�:BG��QW�����"��,[��,�����d�B����/�OQT������+O�*�-;P��v�J�Q�%��4�e��`#V���U����kyj.���H��6H�������j2!��)�)b��aE�P��� ����X#���/bb����(phw�"d]����6h�f}$��P�>���P�]RL���7��/oP�<��$>T��!`�Z +����|z���������{�����1�f�)�msW`{{���|�i�:1��c��k��D�XA����Y����km����R�r�c%W�f�T!k59b{�����+���f���A8V	�;����9X57�����Y��������P�fP���tG\��,�
xC]�(�8��`�n�Sa'{p���j�4�J�� �k[��t.i�c���0��\ bKh4#�d����O
�T��WQUl0X��Y����I��e�wo�������m�d�d�]��S���I�MV��������;�5������;E�H� ������;l����e�A7qsb��[��Z6�	��T2���&���)�SN�'h�A��qZS��!��F����bK-����kSz_�R�:u�������4VQ��Rs6�p�4�xHB�]-$��<�73�l��e5�Tx#%)����������B�'���C����l)�)�Z��e��uW�h%�NX�ku��2����?��g�ef������jjm�������h��3�O��zgM��$O�����2�����(�����	���J�����+�]�i�-"qS-��l�J�K��_�(��Q�A������<�����
2R�@�����O���]�;8��6�.�����R	M���23$��%��\B3�_�?�	�sGE�$�E����J���zvv105s����I�����3G����Ww������G3-z|j���G���%i�7���6�7W/���������ft&v&;��h��:����7���H�
_^�x5��B=��+;C��/o��w�W�vr�yvsum'��,F�����V�?�C�������N����|�a|0�u��R7��������W��w��mX�I������o�_o����0Mf���A��\����I�`w�r��K�p'����-�Zf�]�_��7�[���������z�����3���l?�������7�/�X��>O�:Z4�$�i��]^������v��]���s��o..�S�!e�1�EHi�I
 C�2�k��R�7l^�I����w(L�*���n���w���q�16��.��G��2�Mi�8��R�������3��m"�9|�p��HL$��bu9�������G����?�Gd��������F��}3����=�Qq��Wf��l���r��:6l�D�����{taD�(�4��vOv����g���';-��������m�o.������F�>;$������G�{?�_< �zgQ�?���c|%��v��:�� ��������;������[���?��ae1�}�s��"�����?�k����c{<������[x��_���a����I�#�
R��M�<}S@_�)��/��^��l����S����/�rZt�d_��l���`�}A<�2�F����M�l����_��_��;T��z �������l����-�P�Bs�j��K��8�
��gK^Yt��
_�����x�����^V�r����9�(s1�Z8k��X���s[����7H�����q�Hc��-���_sW���ud?E'c��������X:�"�pE��`$���dh� �D�������4@j����-��o�W��[
�(J�i�t >��N?�u��9}�n�
�Q�����x�t��b5��y�8o���i�?�������e)F�-#`\�c��Y`A2������B�
Zg��5����"���� �nt|�!>�h��h�/��I�m����*�$r'���9CX��|X��";X���$,�YW�F���G��`:��=&8p2%%�����0������(��������J�t-^��y=�xo��_��4��5�i�6��#����5T;/��V�/�(=rQ	d���!��O��i��9q��w�K��M�]3��uk_���nNJ$Q4
$OD������NE�)��\37�Q�QX���[�
����x2��{�G���Gq��	��c��y���T>�m��x���K�<B��CF&O5%�A^��~�������L��|���"j ����:����72�����z��x�b�eX�B�@���C$�����,��l=3�@N��^vrZ���d�F���v�z2#���?"��j"�Jq|F�������^h+�z�pn[��l7��A�:���x���������<wb����Wc2�9< LlH7�8R�y�A�0/|���+fsm7��������1�"� p�brm+�"��������-�_
���zm������SO��<���A��+~:#�I��sDr����h_x����P�=��\���b2��R������������Is`3�"�u����������^����7\�J���y[�G����I��aFU]NJ����J_�����m���U��B�l���1Vf��u~���Z��v�I��;e��������,n1��H���E���W�5��<�U�^
d�\q��>����q���sC5!2�hQ��E3�^���5����g�t,�������b1�YK�f������%�M��R�L�h�;$*�x������4	��zwfl�F������ol�g'����I7��_�$�.���c����V��Q�*<�yk���2�QB�������&�N|���	I���k�\�I�V���V��>�)@$7s����P6a��L�.�	�M��,\�}���a�1�4f�0�#�]��<������m�Y�PK/�v}��tY�L��$`����"
Q��2N]�J@Yx*d��*V�F���R��H� m�����a�l�R��8�m����r*��S�+�O�	M���7mC��Y���F�T����?7=�'�3j7��A�D�g������&�b��>)b�����8��M�7�S6�f��Q��H�B	Q�V
�����"��4|;���?��M�	Xd�C�
�
<����u�n��	�P�Ku�i���c0=g�Pv�>+��Is������������xT�����u��!���G�,��&W��R��k��gH]-�4��T)��SyDoN�T��{��\H�Bxk	����$�v�k��^�*����������/:�V%��R7�xH��\-��FBN��Q��H���d]�7����m�^/�dIP�BJ�q����X8����WR�����62��?�w�a6�t4{��6�^����<�����{b9E�)����M��7�� �5����$�~���0	~�Q�r�%����ta���]�\���S�I
�zX�L��|vw8	�-A�9AC?�����c}2}h�����"LD� ���mE��{F��������ZPf��N��e;A_�0����A����H�7[O�[��(
"�hwqn
�&��k�~��/�q�gO�V�.A���,�X��PE� �~?].r(gEb����V�}���.�)D^	<���P�w�Nq%�^3ku�����;
�U�X���7E��i�9������������Pw�:{�)Dgo2�Qt��/�O�G�W�fsL**X�O�."�/����-��|F;[��a44���+����U�Va��2��f����A�rrE��^�b�m�����dPz�%sr���\�ql=Um!���	x��G;zI8!���c�U4��B��F�n%����$Q^4�aHm�G�SfY��(�p^$����=EFA�;oD>�#;���Wc����)��2k����U��� ������y�[<�S�������wKKpG52��2f���"#-��&�9��vN=���XQn�@g����,C�Tp�i���������B/a�@uP�%�2AT�����RDhF�#uT~����j�I�L��B���}�k0r](��DS��1�q����������3��u���h�����C��N����y���+P�;�Y��rA��d��jY����P/�00&l)O�����������#A�>�����/��)�n������JDq��.���s{pk;�!���\.�&�3Fkc�;�h�p���������W�d��O���<��������w������������'?e1		(����:�O	,��|f�'=w,{��F?���	u'�+���P{u�F��Q�:N���&}��v�W%�k�)������a���CC�51���+�e����U�@93e���1���@�S�U��_�+c5
���i�w��I��$�����Z�j&��I�y��
��x~��������4s�z�f��+
�R�����Y?���
��������>n���������������/^�=;8�{�\�OB;�����������`�dpp�,������?�
�_p|��d<p;�h�a�����r��w�����H�*��%�lH�����
�����9������[��[X�
we������?���#�m'������;���'�/��w�+�7��{����H�B~�������`���Q:���?��N�P��"�o��o��}p��?m��"��_�A���>��.rDw7��LK�&�'����)X�G��J��`�+���q�����hD�)����4J��q��g�9�_����;��X'�<I�L��V���L�p1{T�|}�������?]���o���?���,�G���v��:�����������{����������Y���-����og_l�'�����>�����,�����AK|���Q��n�6�
o�|��T�s}U2���&���kT
�F�gQQ����e�k�\�,�]d����������-Gn?���2Ollm���n�����JY^��=���Q_}�����~�Y����4*!��^(!�����]%���N�����!*3����E-!�f���5[������y������1�
������B�+�u�K�AT��G��
�Tm��{�b�a^@�[B�`�Q`fz�\��� ���Y�w�2����b�a����g��YL�\v��n��:@���.��&&�^���%��,-���T~1c�E!���.
�����E\J�W���N0	�%�����
'��(�J��n�>�0Z������TT���?AlI��`#�
���'��8�!"��
M���W	;����Z\��A��	<V��
��K|t�}����U/Rr(
��r^Mo�X���m�����1	�(%u5�����AXr��t���d����B�V�Y)$�{�0����t���������X�X�/"N�$ :PB�Q���I��"SRc�N���J"p2�M���V�mj�����q�3�X�zj��`�HC%��"���?Y���%0��R}����S�����p*�%���`��F^>���s��.�;���T���kHj��p���������k~T@��V�������I������X.����;B8@4	�����,�8��Y(n�k6"os~'����������(�nR^����E����9�t/�Ziy"7ka��c�����+xS���X��.C)���dR9P,\�{>���k��F�|���G��d)�P@�)R��1zb�E�a/;�M ����H�;�j<����|��>}��G��X�w�a��,��������J{2q��]R����[����X���>Kw�_��
�e@��D�#�����(J�y�Yfb��_���!���)�P_k���W��A����'�{y:4��f}:u�
�[J�$V@��v7U��UW4�
E<z�ngeb����dw�x/E9%�]9���y;2����)��^?�"`y��#^����k��#R���p���%��Fi���du��"4IJ{�=���1�k;�����`re{�����S��#�	8B�"�/zo5Q�zV4�2T������I�>F��]�dp`7U��J�T�{�Fi����)��P��,�H$����n��}���]9��ij�������!D�j��;�DL�`<��]�$���������h�+��y>��c��� ���g)D!6I���F�T�(��$���s�s"����(�pR�$Z�<��J�����&Ec9���h���b=�~�Xs;����rzW��4x}�0�,�Wd�?�����)�p�����D]KR� �K�I��T#0�"w;i�p��V��/����4P�T�M�Rt�����m��C��p���
�Ui�t�"0�N��wcu��q:d�B��mH�E�#�����i-7�w�i.�;�Ok��<)'���Nu��y;������!S����������J&��ss�wLq�H>o4	a���bv_�������
8�,������cK���pWU�����w�#�����?������;&��$�m@mt��d�DuQ����������l���.m����z��i�
���Sj�^�Jh%7��O��|�hg�M#$��<F|��.����f�N�
C����[���N~}��c��S'�#����:YD��w39��,+Jp.�!m�vn��p.�F+�A��bZ���+O%G)�i��T"X�Mk	����6Q��&5|<H��������3o�]c���:���j[LK�.�"�����n|��/���,�{��+����StW��$�*7���3��������*�*�;��)Q����H;u{�%� (���]oO�=3����
@����s���;��z�1��Zs���b/
&oZf/�B5�ZX9�N==;<<�@&�����- ��	s$��`$3fW2B��OeF�L�P�S����22��Eh������H���I�8��:^�p:��Ey�nm�wwR�m$�,B��\��j�B����f�o?zz0\����KD]b���g1\��j��
�����s�����/h�I��h�������j�!�~T�,Z\w���V��n�}�p�N��wkPd�P4����<����$B�]��[�7�Ci�8|�cc�?�+zdD�bg�H����X�s[a��+;���+�o��|�oq_YUKo~���(:������yb��������q=�y3����U�q����W�����|�[��_v}������:�)%��>V�1������������������
��#���[}i^�������=����MV�����^,�����N[e�	yN$��1���y����l[9L�b�%FQj��SK���M�����
'��#��H0��}�n7��R$�)�J;O�D�d�����,���=�$�4g��p�SQ`�g�6������#�$��������l%��;�3[�C�����'�	��f.A}�#��k��F)nt���B.c���RG��1��������c{��\�jm�o�`��WWF}T,��L��:�#Wd)�7t
�+^y�&v�����$���������p���/��)��D�����:�k)%X1�Y."��j�n[V�
�����g�"�^l|���zK�N�c��n,k�0�����c���>%H���������2��[\\2w����^.���R��:��B=����0|�����-j��KK4����q�H�X����d����s
�fC�
��{![h�hU��%o�sdSlLg�O@��������n#{����C���&$�XZw����&��B��,f�XJ�rZ��56j��o������|p~������>���8������]�9�/%�����~�I��=������0[���5y���Z ��������w-�L�$�b����"r�b�������7�^9��������0��[ &�D6)���:��mhT0e��]����C$0�?�b\l�{V:zh.�!\�!�4D��l��N8�p���tD�OV�4���9�.�ZN�������Yz���������10��Cq�k6g��kt��f.%����w8��|��4?����:A��r������R��B�(`9�^B1��dA������i�#����5�����X�u��jX��~��y�:I����)w�R�����$��{���i�6�����*�8�A�q�dS���1���H.@Y��|��/��OiD��n�$�5��Wh��WN�9Y�k��K�����G��t#%m\tnTdzCy��� ���]V1v���9�DHM���Jl&���l34�o:-�7 �����0���>7�Ap�����|r�H��������3qW/��	Dv�Kk�-�Q�����J�r�p�^�~��;���M�������C>��y@
{��5Z������/��N1H(f�+�T�Q_���_v���^���j��\au
���q����eMd]+���e�e��"����n��[`V��H��4�7�_��G��#*h!�q2^6)��5�%>��CT�_��+`ic�y�C�?+�����nk���%��2!2��:2B����e�D%����{����"6.FP�����-�X��,~��DH��H~��UG��:�0J����5�]I�j�{
7:Z�	e�GQ��g`�cXK�j��'�=��}�� �M��k�
�k��&Z���G#�����<f��tl�:=<������iL�l�T(curu���L��hEJ�i����b�C8�mh�m��m�����?>����ku��%4����
0012-wal_decoding-Temporarily-add-logical-decoding-regres.patch.gzapplication/x-patch-gzipDownload
#173Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Andres Freund (#168)
Re: logical changeset generation v6.7

Hello,

Will send the rebased version as soon as I've addressed your comments.

Thank you.

===== 0001:

- You assined HeapTupleGetOid(tuple) into relid to read in
several points but no modification. Nevertheless, you left
HeapTupleGetOid not replaced there. I think 'relid' just for
repeated reading has far small merit compared to demerit of
lowering readability. You'd be better to make them uniform in
either way.

It's primarily to get the line lengths halfway under control.

Mm. I'm afraid I couldn't catch your words, do you mean that
IsSystemClass() or isTempNamespace() could change the NULL bitmap
in the tuple?

===== 0002:

- You are identifying the wal_level with the expr 'wal_level >=
WAL_LEVEL_LOGICAL' but it seems somewhat improper.

Hm. Why?

It actually does no harm and somewhat trifling so I don't assert
you should fix it.

The reason for the comment is the greater values for wal_level
are undefined at the moment, so strictly saying, such values
should be handled as invalid ones. Although there is a practice
to avoid loop overruns by comparing counters with the expression
like (i > CEILING).

For instance, I found a macro for which comment reads as follows
and I feel a bit uneasy with it :-) It's nothing more than that.

| /* Do we need to WAL-log information required only for Hot Standby? */
~~~~
| #define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_HOT_STANDBY)

- RelationIsAccessibleInLogicalDecoding and
RelationIsLogicallyLogged are identical in code. Together with
the name ambiguity, this is quite confising and cause of
future misuse between these macros, I suppose. Plus the names
seem too long.

Hm, don't think they are equivalent, rather the contrary. Note one
returns false if IsCatalogRelation(), the other true.

Oops, I'm sorry. I understand they are not same. Then I have
other questions. The name for the first one
'RelationIsAccessibleInLogicalDecoding' doesn't seem representing
what its comment reads.

| /* True if we need to log enough information to have access via
| decoding snapshot. */

Making the macro name for this comment directly, I suppose it
would be something like 'NeedsAdditionalInfoInLogicalDecoding' or
more directly 'LogicalDeodingNeedsCids' or so..

- In heap_insert, the information conveyed on rdata[3] seems to
be better to be in rdata[2] because of the scarecity of the
additional information. XLOG_HEAP_CONTAINS_NEW_TUPLE only
seems to be needed. Also is in heap_multi_insert and
heap_upate.

Could you explain a bit more what you mean by that? The reason it's a
separate rdata entry is that otherwise a full page write will remove the
information.

Sorry, I missed the comment 'so that an eventual FPW doesn't
remove the tuple's data'. Although given the necessity of removal
prevention, rewriting rdata[].buffer which is required by design
(correct?) with InvalidBuffer seems a bit outrageous for me and
obfuscating the objective of it. Other mechanism should be
preferable, I suppose. The most straight way to do that should be
new flag bit for XLogRecData, say, survive_fpw or something.

- In heap_multi_insert, need_cids referred only once so might be
better removed.

It's accessed in a loop over potentially quite some items, that's why I
moved it into an extra variable.

Sorry bothering you with comments biside the point.. But the
scope of needs_cids is narrower than it is. I think the
definition should be moved into the block for 'if (needwal)'.

- In heap_delete, at the point adding replica identity, same to
the aboves, rdata[3] could be moved into rdata[2] making new
type like 'xl_heap_replident'.

Hm. I don't think that'd be a good idea, because we'd then need special
case decoding code for deletes because the wal format would be different
for inserts/updates and deletes.

Hmm. Although one common xl_heap_replident is impractical,
splitting a logcally single entity into two or more XLogRecDatas
also seems not to be a good idea.

- In heapam_xlog.h, the new type xl_heap_header_len is
inadequate in both of naming which is confising and
construction on which the header in xl_heap_header is no
longer be a header and indecisive member name 't_len'..

The "header" bit in the name refers to the fact that it's containing
information about the a HeapTuple's header, not that it's a header
itself. Do you have a better suggestion than xl_heap_header_len?

Sorry, I'm confused during writing the comment, The order of
members in xl_heap_header_len doesn't matter. I got the reason
for the xl_header_len and whole xlog record image after
re-reading the relevant code. The update record became to contain
two variable length data by this patch. So the length of the
tuple body cannot be calculated only with whole record length and
header lengths.

Considering above, looking heap_xlog_insert(), you marked on
xlrec.flags with XLOG_HEAP_CONTAINS_NEW_TUPLE to signal decoder
that the record should have tuple data not being removed by fpw.
This is the same for the update record. So the redoer(?) also can
distinguish whether the update record contains extra tuple data
or not.

On the other hand, the update record after patched is longer by
sizeof(uint16) regardless of whether 'tuple data' is attached or
not. I don't know the value of the size in WAL stream, but the
smaller would be better maybe.

As a conclusion, I thing it would be better to decide whether to
insert length SEGMENT before the tuple data segment in
log_heap_update. Like follows,

| rdata[1].next = &(rdata[2]);
|
| xlhdr.t_infomask2 = newtup->t_data->t_infomask2;
| xlhdr.t_infomask = newtup->t_data->t_infomask;
| xlhdr.t_hoff = newtup->t_data->t_hoff;
|
| /*...*/
| rdata[2].data = (char *) &xlhdr;
| ...
| rdata[2].next = &(rdata[3]);
|
| if (need_tuple_data)
| {
| uint16 newtupbodylen =
| newtup->t_len - offsetof(HeapTupleHeaderData, t_bits);
| rdata[3].data = &newtupbodylen;
| ....
| }
| else
| {
| rdata[3].data = NULL;
| rdata[3].len = 0;
| ...
| }
| rdata[3].next = &(rdata[4]);
|
| /* PG73FGORMAT: write bitmap [+ padding] [+ oid] + data */
| rdata[4].data = (char *) newtup->t_data;

- In heapam_xlog.h, XLOG_HEAP_CONTAINS_OLD looks incomplete. And
it seems to be used in nowhere in this patchset. It should be
removed.

Not sure what you mean with incomplete? It contains the both possible
variants for an old contained tuple. The macro is used in the decoding,
but I don't think things get clearer if we revise the macros in that
later patch.

Umm. I don't understand why I missed where it is used, it surely
used in decode.c as you mentioned. Ok, this is required. Then
about 'imcomplete', it means 'CONTAINS OLD what?'...mmm,
logically speaking, lack of an object for the word 'OLD'. The
answer for the question should be 'both KEY and TUPLE'. Comparing
the revised images,

# Of course, it doesn't matter if the object for OLD can
# naturally be missing as English. I'm not a native English
# speaker as you know:-)

defining XLOG_HEAP_CONTAINS_OLD_KEY_AND_TUPLE,
| if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY_AND_TUPLE)
| {

undefining XLOG_HEAP_CONTAINS_OLD and use separte macros type 1
| if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY ||
| xlrec->flags & XLOG_HEAP_CONTAINS_OLD_TUPLE)
| {
(I belive this should be optimized by the compiler:-)

and type 2
| if (xlrec->flags &
| (XLOG_HEAP_CONTAINS_OLD_KEY | XLOG_HEAP_CONTAINS_OLD_TUPLE))
| {

I'm ok with any of them or others. In this connection, I found
following phrase in heapam.c which like type2 above.

| if (!(old_infomask & (HEAP_XMAX_INVALID |
| HEAP_XMAX_COMMITTED |
| HEAP_XMAX_IS_MULTI)) &&

- log_heap_new_cid() is called at several part just before other
xlogs is being inserted. I suppose this should be built in the
target xlog structures.

Proportionally it will only be logged in a absolute minority of the
cases (since normally the catalog will only seldomly be updated in
comparison to a user's tables), so it doesn't seem like a good idea to
complicate the already *horribly* complicated wal format for heap_*.

Horribly:-) I agree to you. Hmm I reconsider with new
knowledge(!) about your patch. But what do you think doing this
as follows,

  if (RelationIsAccessibleInLogicalDecoding(relation))
+ {
|	rdata_heap_new_cid(&rdata[0], relation, heaptup);
+       xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_CID;
+ }
+ else
+       rdata_void(&rdata[0])
+ rdata[0].next = &(rdata[1]);

xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
xlrec.target.node = relation->rd_node;
xlrec.target.tid = heaptup->t_self;
| rdata[1].data = (char *) &xlrec;

If you don't agree with this, I don't say no more about this.

- in RecovoerPreparedTransactions(), any commend needed for the
reason calling XLogLogicalInfoActive()..

It's pretty much the "Test here must match one used in
AssignTransactionId()" comment. We only want to allow overwriting if
AssignTransactionId() might already have done the SubTransSetParent()
calls.

Thank you, I found it and it seems to be sufficient.

- In xact.c, the comment for the member 'didLogXid' in
TransactionStateData seems differ from it's meaning. It
becomes true when any WAL record for the current transaction
id just has been written to WAL buffer. So the comment,

/* has xid been included in WAL record? */

would be better be something like (Should need corrected as
I'm not native speaker.)

/* Any WAL record for this transaction has been emitted ? */

I don't think that'd be an improvement, transaction is a bit ambigiuous
there because it might be the toplevel or subtransaction.

Hmm. Ok, I agree with it.

and also the member name should be something like
XidIsLogged. (Not so chaned?)

Hm.

- The name of the function MarkCurrentTransactionIdLoggedIfAny,
although irregular abbreviations are discouraged, seems too
long. Isn't MarkCur(r/rent)XidLoggedIfAny sufficient?

If you look at the other names in xact.h that doesn't seem to fit too
well in the naming pattern.

(Now looking...) Wow. Ok, I agree to you.

Anyway,
the work involving this function seems would be better to be
done in some other way..

Why? How?

How... It is easy to comment but hard to realize. Ok, let's
forget this comment:-) The 'Why' came from some obscure
impression(?), without firm thought.

- The comment for RelationGetIndexAttrBitmap() should be edited
for attrKind.

Good point.

Thanks.

- The macro name INDEX_ATTR_BITMAP_KEY should be
INDEX_ATTR_BITMAP_FKEY. And INDEX_ATTR_BITMAP_IDENTITY_KEY
should be INDEX_ATTR_BITMAP_REPLID_KEY or something.

But INDEX_ATTR_BITMAP_KEY isn't just about foreign keys... But I agree
that INDEX_ATTR_BITMAP_IDENTITY_KEY should be renamed.

Thank you, please remember to do that.

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#174Andres Freund
andres@2ndquadrant.com
In reply to: Kyotaro HORIGUCHI (#173)
Re: logical changeset generation v6.7

Hi,

On 2013-12-05 22:03:51 +0900, Kyotaro HORIGUCHI wrote:

- You assined HeapTupleGetOid(tuple) into relid to read in
several points but no modification. Nevertheless, you left
HeapTupleGetOid not replaced there. I think 'relid' just for
repeated reading has far small merit compared to demerit of
lowering readability. You'd be better to make them uniform in
either way.

It's primarily to get the line lengths halfway under control.

Mm. I'm afraid I couldn't catch your words, do you mean that
IsSystemClass() or isTempNamespace() could change the NULL bitmap
in the tuple?

Huh? No. I just meant that the source code lines are longer if you use
"HeapTupleGetOid(tuple)" instead of just "relid". Anway, that patch has
since been committed...

===== 0002:

- You are identifying the wal_level with the expr 'wal_level >=
WAL_LEVEL_LOGICAL' but it seems somewhat improper.

Hm. Why?

It actually does no harm and somewhat trifling so I don't assert
you should fix it.

The reason for the comment is the greater values for wal_level
are undefined at the moment, so strictly saying, such values
should be handled as invalid ones.

Note that other checks for wal_level are written the same way. Consider
how much bigger this patch would be if every usage of wal_level would
need to get changed because a new level had been added.

- RelationIsAccessibleInLogicalDecoding and
RelationIsLogicallyLogged are identical in code. Together with
the name ambiguity, this is quite confising and cause of
future misuse between these macros, I suppose. Plus the names
seem too long.

Hm, don't think they are equivalent, rather the contrary. Note one
returns false if IsCatalogRelation(), the other true.

Oops, I'm sorry. I understand they are not same. Then I have
other questions. The name for the first one
'RelationIsAccessibleInLogicalDecoding' doesn't seem representing
what its comment reads.

| /* True if we need to log enough information to have access via
| decoding snapshot. */

Making the macro name for this comment directly, I suppose it
would be something like 'NeedsAdditionalInfoInLogicalDecoding' or
more directly 'LogicalDeodingNeedsCids' or so..

The comment talks about logging enough information that it is accessible
- just as the name.

- In heap_insert, the information conveyed on rdata[3] seems to
be better to be in rdata[2] because of the scarecity of the
additional information. XLOG_HEAP_CONTAINS_NEW_TUPLE only
seems to be needed. Also is in heap_multi_insert and
heap_upate.

Could you explain a bit more what you mean by that? The reason it's a
separate rdata entry is that otherwise a full page write will remove the
information.

Sorry, I missed the comment 'so that an eventual FPW doesn't
remove the tuple's data'. Although given the necessity of removal
prevention, rewriting rdata[].buffer which is required by design
(correct?) with InvalidBuffer seems a bit outrageous for me and
obfuscating the objective of it.

Well, it's added in a separate rdata element. Just as in dozens of other
places.

- In heap_delete, at the point adding replica identity, same to
the aboves, rdata[3] could be moved into rdata[2] making new
type like 'xl_heap_replident'.

Hm. I don't think that'd be a good idea, because we'd then need special
case decoding code for deletes because the wal format would be different
for inserts/updates and deletes.

Hmm. Although one common xl_heap_replident is impractical,
splitting a logcally single entity into two or more XLogRecDatas
also seems not to be a good idea.

That's done everywhere. There's basically two reasons to use separate
rdata elements:
* the buffers are different
* the data pointer is different

The rdata chain elements don't survive in the WAL.

Considering above, looking heap_xlog_insert(), you marked on
xlrec.flags with XLOG_HEAP_CONTAINS_NEW_TUPLE to signal decoder
that the record should have tuple data not being removed by fpw.
This is the same for the update record. So the redoer(?) also can
distinguish whether the update record contains extra tuple data
or not.

But it doesn't know the length of the individual records, so knowing
there are two doesn't help.

On the other hand, the update record after patched is longer by
sizeof(uint16) regardless of whether 'tuple data' is attached or
not. I don't know the value of the size in WAL stream, but the
smaller would be better maybe.

I think that'd make things too complicated without too much gain in
comparison to the savings.

# Of course, it doesn't matter if the object for OLD can
# naturally be missing as English.

Well, I think the context makes it clear enough.

I'm not a native English speaker as you know:-)

Neither am I ;).

undefining XLOG_HEAP_CONTAINS_OLD and use separte macros type 1
| if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY ||
| xlrec->flags & XLOG_HEAP_CONTAINS_OLD_TUPLE)
| {
(I belive this should be optimized by the compiler:-)

It's not about efficiency, imo the other variant looks clearer. And will
continue to work if we add the option to selectively log columns or
such.

if (RelationIsAccessibleInLogicalDecoding(relation))
+ {
|	rdata_heap_new_cid(&rdata[0], relation, heaptup);
+       xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_CID;
+ }
+ else
+       rdata_void(&rdata[0])
+ rdata[0].next = &(rdata[1]);

xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
xlrec.target.node = relation->rd_node;
xlrec.target.tid = heaptup->t_self;
| rdata[1].data = (char *) &xlrec;

If you don't agree with this, I don't say no more about this.

It's a lot more complex than that. This throws of all kind of size
calculations. and it makes the redo functions more complex - and they
are much more often executed for !CONTAINS_NEW_CID.

- The macro name INDEX_ATTR_BITMAP_KEY should be
INDEX_ATTR_BITMAP_FKEY. And INDEX_ATTR_BITMAP_IDENTITY_KEY
should be INDEX_ATTR_BITMAP_REPLID_KEY or something.

But INDEX_ATTR_BITMAP_KEY isn't just about foreign keys... But I agree
that INDEX_ATTR_BITMAP_IDENTITY_KEY should be renamed.

Thank you, please remember to do that.

I am not sure it's a good idea anymore, it doesn't really seem to
increase clarity...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#175Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Andres Freund (#174)
Re: logical changeset generation v6.7

Hello, sorry for annoying you with meaningless questions.
Your explanation made it far clearer to me.

This will be the last message I mention on this patch..

On 2013-12-05 22:03:51 +0900, Kyotaro HORIGUCHI wrote:

- You assined HeapTupleGetOid(tuple) into relid to read in
several points but no modification. Nevertheless, you left
HeapTupleGetOid not replaced there. I think 'relid' just for
repeated reading has far small merit compared to demerit of
lowering readability. You'd be better to make them uniform in
either way.

It's primarily to get the line lengths halfway under control.

Mm. I'm afraid I couldn't catch your words, do you mean that
IsSystemClass() or isTempNamespace() could change the NULL bitmap
in the tuple?

Huh? No. I just meant that the source code lines are longer if you use
"HeapTupleGetOid(tuple)" instead of just "relid". Anway, that patch has
since been committed...

Really? Sorry for annoying you. Thank you, I understand that.

===== 0002:

- You are identifying the wal_level with the expr

evel >=

WAL_LEVEL_LOGICAL' but it seems somewhat improper.

Hm. Why?

It actually does no harm and somewhat trifling so I don't
you should fix it.

The reason for the comment is the greater values for vel
are undefined at the moment, so strictly saying, such values
should be handled as invalid ones.

Note that other checks for wal_level are written the same
way. Consider how much bigger this patch would be if every
usage of wal_level would need to get changed because a new
level had been added.

I know the objective. But it is not obvious that the future value
will need the process. Anyway we never know that until it
actually comes so I said it trifling.

- RelationIsAccessibleInLogicalDecoding and
RelationIsLogicallyLogged are identical in code. Together with
the name ambiguity, this is quite confising and cause of
future misuse between these macros, I suppose. Plus the names
seem too long.

Hm, don't think they are equivalent, rather the contrary. Note one
returns false if IsCatalogRelation(), the other true.

Oops, I'm sorry. I understand they are not same. Then I have
other questions. The name for the first one
'RelationIsAccessibleInLogicalDecoding' doesn't seem representing
what its comment reads.

| /* True if we need to log enough information to have access via
| decoding snapshot. */

Making the macro name for this comment directly, I suppose it
would be something like 'NeedsAdditionalInfoInLogicalDecoding' or
more directly 'LogicalDeodingNeedsCids' or so..

The comment talks about logging enough information that it is accessible
- just as the name.

Though I'm not convinced for that. But since it also seems not so
wrong and you say so, I pretend to be convinced:-p

- In heap_insert, the information conveyed on rdata[3] seems to
be better to be in rdata[2] because of the scarecity of the
additional information. XLOG_HEAP_CONTAINS_NEW_TUPLE only
seems to be needed. Also is in heap_multi_insert and
heap_upate.

Could you explain a bit more what you mean by that? The reason it's a
separate rdata entry is that otherwise a full page write will remove the
information.

Sorry, I missed the comment 'so that an eventual FPW doesn't
remove the tuple's data'. Although given the necessity of removal
prevention, rewriting rdata[].buffer which is required by design
(correct?) with InvalidBuffer seems a bit outrageous for me and
obfuscating the objective of it.

Well, it's added in a separate rdata element. Just as in dozens of other
places.

Mmmm. Was there any rdata entriy which has substantial content
but .buffer is set to InvalidBuffer just for avoiding removal by
fpw? Although for the objection I made, I became to be accostomed
to see there and I became to think it is not so bad.. I put an
end by this comment.

- In heap_delete, at the point adding replica identity, same to
the aboves, rdata[3] could be moved into rdata[2] making new
type like 'xl_heap_replident'.

Hm. I don't think that'd be a good idea, because we'd then need special
case decoding code for deletes because the wal format would be different
for inserts/updates and deletes.

Hmm. Although one common xl_heap_replident is impractical,
splitting a logcally single entity into two or more XLogRecDatas
also seems not to be a good idea.

That's done everywhere. There's basically two reasons to use separate
rdata elements:
* the buffers are different
* the data pointer is different

The rdata chain elements don't survive in the WAL.

Well, I came to see rdata's as simple containers holding
fragments to be written into WAL stream. Thanks for patiently
answering for such silly questions.

Considering above, looking heap_xlog_insert(), you marked on
xlrec.flags with XLOG_HEAP_CONTAINS_NEW_TUPLE to signal decoder
that the record should have tuple data not being removed by fpw.
This is the same for the update record. So the redoer(?) also can
distinguish whether the update record contains extra tuple data
or not.

But it doesn't know the length of the individual records, so knowing
there are two doesn't help.

The length can be attached only when
XLOG_HEAP_CONTAINS_NEW_TUPLE, but it is right for you to say
that's too complicated.

On the other hand, the update record after patched is longer by
sizeof(uint16) regardless of whether 'tuple data' is attached or
not. I don't know the value of the size in WAL stream, but the
smaller would be better maybe.

I think that'd make things too complicated without too much gain in
comparison to the savings.

As written above, I'm convinced with that.

# Of course, it doesn't matter if the object for OLD can
# naturally be missing as English.

Well, I think the context makes it clear enough.

Thank you. I learned that, perhaps :-)

I'm not a native English speaker as you know:-)

Neither am I ;).

Yeah, you should've been more experienced than me :-p

undefining XLOG_HEAP_CONTAINS_OLD and use separte macros type 1
| if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD_KEY ||
| xlrec->flags & XLOG_HEAP_CONTAINS_OLD_TUPLE)
| {
(I belive this should be optimized by the compiler:-)

It's not about efficiency, imo the other variant looks clearer. And will
continue to work if we add the option to selectively log columns or
such.

Well, It's ok for me after knowing that the 'OLD' alone makes
sense.

if (RelationIsAccessibleInLogicalDecoding(relation))
+ {
|	rdata_heap_new_cid(&rdata[0], relation, heaptup);
+       xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_CID;
+ }
+ else
+       rdata_void(&rdata[0])
+ rdata[0].next = &(rdata[1]);

xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
xlrec.target.node = relation->rd_node;
xlrec.target.tid = heaptup->t_self;
| rdata[1].data = (char *) &xlrec;

If you don't agree with this, I don't say no more about this.

It's a lot more complex than that. This throws of all kind of size
calculations. and it makes the redo functions more complex - and they
are much more often executed for !CONTAINS_NEW_CID.

Mmm. I don't know the exact reason for omitting length field in
previous xlog format. I've supposed the saved bytes are more
significant than the calculation clocks. But it sould be wrong or
the efficiency was defeated by the expected coplexity, supposing
from that it is alrady committed.

- The macro name INDEX_ATTR_BITMAP_KEY should be
INDEX_ATTR_BITMAP_FKEY. And INDEX_ATTR_BITMAP_IDENTITY_KEY
should be INDEX_ATTR_BITMAP_REPLID_KEY or something.

But INDEX_ATTR_BITMAP_KEY isn't just about foreign keys... But I agree
that INDEX_ATTR_BITMAP_IDENTITY_KEY should be renamed.

Thank you, please remember to do that.

I am not sure it's a good idea anymore, it doesn't really seem to
increase clarity...

And also might be excessive. But it seemed somewhat unclear to
unaccustomed eyes:-)

Thank you for all your answers.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#176Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#172)
Re: logical changeset generation v6.8

On Wed, Dec 4, 2013 at 10:55 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-12-03 15:19:26 -0500, Robert Haas wrote:

Yeah, you're right. I think the current logic will terminate when all
flags are set to false or all attribute numbers have been checked, but
it doesn't know that if HOT's been disproven then we needn't consider
further HOT columns. I think the way to fix that is to tweak this
part:

+               if (next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
check_now = next_hot_attnum;
+               else if (next_key_attnum > FirstLowInvalidHeapAttributeNumber)
+                       check_now = next_key_attnum;
+               else if (next_id_attnum > FirstLowInvalidHeapAttributeNumber)
+                       check_now = next_id_attnum;
else
+                       break;

What I think we ought to do there is change each of those criteria to
say if (hot_result && next_hot_attnum >
FirstLowInvalidHeapAttributeNumber) and similarly for the other two.
That way we consider each set a valid source of attribute numbers only
until the result flag for that set flips false.

That seems to work well, yes.

Updated & rebased series attached.

* Rebased since the former patch 01 has been applied
* Lots of smaller changes in the wal_level=logical patch
* Use Robert's version of wal_level=logical, with the above fixes
* Use only macros for RelationIsAccessibleInLogicalDecoding/LogicallyLogged
* Moved a mit more logic into ExtractReplicaIdentity
* some comment copy-editing
* Bug noted by Euler fixed, testcase added
* Some copy editing in later patches, nothing significant.

I've primarily sent this, because I don't know of further required
changes in 0001-0003. I am trying reviewing the other patches in detail
atm.

Committed #1 (again). Regarding this:

+       /* XXX: we could also do this unconditionally, the space is used anyway
+       if (copy_oid)
+               HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));

I would like to put in a big +1 for doing that unconditionally. I
didn't make that change before committing, but I think it'd be a very
good idea.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#177Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#172)
Re: logical changeset generation v6.8

On Wed, Dec 4, 2013 at 10:55 AM, Andres Freund <andres@2ndquadrant.com> wrote:

I've primarily sent this, because I don't know of further required
changes in 0001-0003. I am trying reviewing the other patches in detail
atm.

Committed #3 also.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#178Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#172)
Re: logical changeset generation v6.8

On Wed, Dec 4, 2013 at 10:55 AM, Andres Freund <andres@2ndquadrant.com> wrote:

[ updated logical decoding patches ]

Regarding patch #4, introduce wal decoding via catalog timetravel,
which seems to be the bulk of what's not committed at this point...

- I think this needs SGML documentation, same kind of thing we have
for background workers, except probably significantly more. A design
document with ASCII art in a different patch does not constitute
usable documentation. I think it would fit best under section VII,
internals, perhaps entitled "Writing a Logical Decoding Plugin".
Looking at it, I rather wonder if the "Background Worker Processes"
ought to be there as well, rather than under section V, server
programming.

+                       /* setup the redirected t_self for the benefit
of logical decoding */
...
+                       /* reset to original, non-redirected, tid */

Clear as mud.

+ * rrow to disk but only do so in batches when we've collected several of them

Typo.

+ * position before those records back. Independently from WAL logging,

"before those records back"?

+ * position before those records back. Independently from WAL logging,
+ * everytime a rewrite is finished all generated mapping files are directly

I would delete "Independently from WAL logging" from this sentence.
And "everytime" is two words.

+ * file. That leaves the tail end that has not yet been fsync()ed to disk open
...
+ * fsynced.

Pick a spelling and stick with it. My suggestion is "flushed to
disk", not actually using fsync per se at all.

+ * TransactionDidCommit() check. But we want to support physical replication
+ * for availability and to support logical decoding on the standbys.

What is physical replication for availability?

+ * necessary. If we detect that we don't need to log anything we'll prevent
+ * any further action by logical_*rewrite*

Sentences should end with a period, and the reason for the asterisks
is not clear.

+       logical_xmin =
+               ((volatile LogicalDecodingCtlData*) LogicalDecodingCtl)->xmin;

Ugly.

+ errmsg("failed to write to
logical remapping file: %m")));

Message style.

+                       ereport(ERROR,
+                                       (errcode_for_file_access(),
+                                        errmsg("incomplete write to
logical remapping file, wrote %d of %u",
+                                                       written, len)));

Message style. I suggest treating a short write as ENOSPC; there is
precedent elsewhere.

I don't think there's much point in including "remapping" in all of
the error messages. It adds burden for translators and users won't
know what a remapping file is anyway.

+               /*
+                * We intentionally violate the usual WAL coding
practices here and
+                * write to the file *first*. This way an eventual
checkpoint will
+                * sync any part of the file that's not guaranteed to
be recovered by
+                * the XLogInsert(). We deal with the potential
corruption in the tail
+                * of the file by truncating it to the last safe point
during WAL
+                * replay and by checking whether the xid performing
the mapping has
+                * committed.
+                */

Don't have two different comments explaining this. Either replace
this one with a reference to the other one, or get rid of the other
one and just keep this one. I vote for the latter.

I don't see a clear explanation anywhere of what the
rs_logical_mappings hash is actually doing. This is badly needed.
This code basically presupposes that you know what it's try to
accomplish, and even though I sort of do, it leaves a lot to be
desired in terms of clarity.

+       /* nothing to do if we're not working on a catalog table */
+       if (!state->rs_logical_rewrite)
+               return;

Comment doesn't accurately describe code.

+       /* use *GetUpdateXid to correctly deal with multixacts */
+       xmax = HeapTupleHeaderGetUpdateXid(new_tuple->t_data);

I don't feel enlightened by that comment.

+       if (!TransactionIdIsNormal(xmax))
+       {
+               /*
+                * no xmax is set, can't have any permanent ones, so
this check is
+                * sufficient
+                */
+       }
+       else if (HEAP_XMAX_IS_LOCKED_ONLY(new_tuple->t_data->t_infomask))
+       {
+               /* only locked, we don't care */
+       }
+       else if (!TransactionIdPrecedes(xmax, cutoff))
+       {
+               /* tuple has been deleted recently, log */
+               do_log_xmax = true;
+       }

Obfuscated. Rewrite without empty blocks.

+       /*
+        * Write out buffer everytime we've too many in-memory entries.
+        */
+       if (state->rs_num_rewrite_mappings >= 1000 /* arbitrary number */)
+               logical_heap_rewrite_flush_mappings(state);

What happens if the number of items per hash table entry is small but
the number of entries is large?

+ /* XXX: should we warn about such files? */

Nah.

+ errmsg("Could not
fsync logical remapping file \"%s\": %m",

Capitalization.

+ *             Decodes WAL records fed from xlogreader.h read into an
reorderbuffer
+ *             while simultaneously letting snapbuild.c build an appropriate
+ *             snapshots to decode those.

This comment doesn't seem to have very good grammar, and it's just a
wee bit less explanation than is warranted.

+ * Take every XLogReadRecord()ed record and perform the actions required to

I'm generally not that fond of using function names as verbs.

+                * Rmgrs irrelevant for changeset extraction, they
describe stuff not
+                * represented in logical decoding. Add new rmgrs in
rmgrlist.h's
+                * order.

The following resource managers are irrelevant for changeset
extraction, because they describe...

+               case RM_NEXT_ID:
+               default:
+                       elog(ERROR, "unexpected RM_NEXT_ID rmgr_id");

Message doesn't match code.

+ /* XXX: we could replay the transaction and
prepare it as well. */

Should we do that?

+ * Abort all transactions that we keep
track of that are older

Come on. You're not aborting anything; you're throwing away state
because you know it did abort. The function naming here could maybe
use some work, too. ReorderBufferDiscardXID()?

+                                * for doing so since, in contrast to
shutdown or end of
+                                * recover checkpoints, we have
sufficient knowledge to deal

recovery, not recover

+ * XXX: There doesn't seem to be a usecase for decoding

Why XXX?

+               case XLOG_HEAP_INPLACE:
+                       /*
+                        * Cannot be important for our purposes, not
part of transactions.
+                        */
+                       if (!TransactionIdIsValid(xid))
+                               break;
+
+                       SnapBuildProcessChange(builder, xid, buf->origptr);
+                       /* heap_inplace is only done in catalog
modifying txns */
+
ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
+                       break;

It is not clear to me why we care about some instances of this and not others.

+ * logical.c
+ *
+ *        Logical decoding shared memory management
...
+ * logical decoding on-disk data structures.

So, apparently it's more than just shared memory management.

+       /*
+        * don't overwrite if we already have a newer xmin. This can
+        * happen if we restart decoding in a slot.
+        */
+       if (TransactionIdPrecedesOrEquals(xmin, MyLogicalDecodingSlot->xmin))
+       {
+       }
+       /*
+        * If the client has already confirmed up to this lsn, we directly
+        * can mark this as accepted. This can happen if we restart
+        * decoding in a slot.
+        */
+       else if (lsn <= MyLogicalDecodingSlot->confirmed_flush)

Try to avoid empty blocks. And we don't normally put comments between
the closing brace of the if and the else clause.

+               elog(DEBUG1, "got new xmin %u at %X/%X", xmin,
+                        (uint32) (lsn >> 32), (uint32) lsn);
+       }
+       SpinLockRelease(&MyLogicalDecodingSlot->mutex);

Don't elog() while holding a spinlock.

+XLogRecPtr ComputeLogicalRestartLSN(void)

Formatting.

+       if (wal_level < WAL_LEVEL_LOGICAL)
+               ereport(ERROR,
+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("logical decoding requires
wal_level=logical")));
+
+       if (MyDatabaseId == InvalidOid)
+               ereport(ERROR,
+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("logical decoding requires to
be connected to a database")));
+
+       if (max_logical_slots == 0)
+               ereport(ERROR,
+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                (errmsg("logical decoding requires
needs max_logical_slots > 0"))));
+

Message style, times three.

+ errmsg("There already is a logical
slot named \"%s\"", name)));

And again.

All right, I'm giving up for now. These patches need a LOT of work on
comments and documentation to be committable, even if the underlying
architecture is basically sound. There's a lot of stuff that has no
comment at all, and a lot of the comments that do exist are basically
recapitulating what the code says (or in some cases, not what the code
says) rather than explaining what the purpose of all of this stuff is
at a conceptual level. The comment at the header of reorderbuffer.c,
for example, is well-written and cogent, but there's a lot of places
where similar detail is needed but lacking. I realize that it isn't
project policy for every function to have a header comment but at the
very least I think it'd be worth asking, for each one, why it doesn't
need one, and/or what information could be provided in such a comment
to most effectively inform the reader.

+ * We free separately allocated data by entirely scrapping oure personal

Spelling.

+        * clog. If we're doing logical replication we can't do that though, so
+        * hold the lock for a moment longer.

...because why?

I'm still unhappy that we're introducing logical decoding slots but no
analogue for physical replication. If we had the latter, would it
share meaningful amounts of structure with this?

+                * noncompatible way, but those are prevented both on catalog
+                * tables and on user tables declared as additional catalog
+                * tables.

Really?

My eyes are starting to glaze over, so really stopping here.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#179Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#176)
Re: logical changeset generation v6.8

On 2013-12-10 19:11:03 -0500, Robert Haas wrote:

Committed #1 (again).

Thanks!

Regarding this:

+       /* XXX: we could also do this unconditionally, the space is used anyway
+       if (copy_oid)
+               HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));

I would like to put in a big +1 for doing that unconditionally. I
didn't make that change before committing, but I think it'd be a very
good idea.

Ok. I wasn't sure if it wouldn't be wierd to include the oid in the
tuple logged for a replica identity that doesn't cover the oid. But the
downside is pretty small...

Will send a patch.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#180Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#176)
1 attachment(s)
Re: logical changeset generation v6.8

On 2013-12-10 19:11:03 -0500, Robert Haas wrote:

Committed #1 (again). Regarding this:

+       /* XXX: we could also do this unconditionally, the space is used anyway
+       if (copy_oid)
+               HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));

I would like to put in a big +1 for doing that unconditionally. I
didn't make that change before committing, but I think it'd be a very
good idea.

Patch attached.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Dep-t-of-second-thoughts-Always-include-oids-in-WAL-.patchtext/x-patch; charset=us-asciiDownload
>From 47c93d8e7afbcaef268de66246571cdc2f134c97 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 11 Dec 2013 17:20:27 +0100
Subject: [PATCH] Dep't of second thoughts: Always include oids in WAL logged
 replica identities.

Since replica identities are logged using the normal format for heap
tuples, the space for oids in WITH OIDS tables was already used, so
there's little point in only including the oid if it is included in
the configured IDENTITY.

Per comment from Robert Haas.
---
 src/backend/access/heap/heapam.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 249fffe..e647453 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -6638,7 +6638,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
 	TupleDesc	idx_desc;
 	char		replident = relation->rd_rel->relreplident;
 	HeapTuple	key_tuple = NULL;
-	bool		copy_oid = false;
 	bool		nulls[MaxHeapAttributeNumber];
 	Datum		values[MaxHeapAttributeNumber];
 	int			natt;
@@ -6698,7 +6697,8 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
 		int attno = idx_rel->rd_index->indkey.values[natt];
 
 		if (attno == ObjectIdAttributeNumber)
-			copy_oid = true;
+			/* copied below */
+			;
 		else if (attno < 0)
 			elog(ERROR, "system column in index");
 		else
@@ -6709,8 +6709,12 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
 	*copy = true;
 	RelationClose(idx_rel);
 
-	/* XXX: we could also do this unconditionally, the space is used anyway */
-	if (copy_oid)
+	/*
+	 * Always copy oids if the table has them, even if not included in the
+	 * index. The space in the logged tuple is used anyway, so there's little
+	 * point in not including the information.
+	 */
+	if (relation->rd_rel->relhasoids)
 		HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));
 
 	/*
-- 
1.8.5.rc2.dirty

#181Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#180)
Re: logical changeset generation v6.8

On Wed, Dec 11, 2013 at 11:25 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-12-10 19:11:03 -0500, Robert Haas wrote:

Committed #1 (again). Regarding this:

+       /* XXX: we could also do this unconditionally, the space is used anyway
+       if (copy_oid)
+               HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));

I would like to put in a big +1 for doing that unconditionally. I
didn't make that change before committing, but I think it'd be a very
good idea.

Patch attached.

Committed with kibitzing.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#182Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#178)
Re: logical changeset generation v6.8

Hi,

Lots of sensible comments removed, I plan to make changes to
address them.

On 2013-12-10 22:17:44 -0500, Robert Haas wrote:

- I think this needs SGML documentation, same kind of thing we have
for background workers, except probably significantly more. A design
document with ASCII art in a different patch does not constitute
usable documentation. I think it would fit best under section VII,
internals, perhaps entitled "Writing a Logical Decoding Plugin".
Looking at it, I rather wonder if the "Background Worker Processes"
ought to be there as well, rather than under section V, server
programming.

Completely agreed it needs that. I'd like the UI decisions in
/messages/by-id/20131205001520.GA8935@awork2.anarazel.de
resolved before though.

+       logical_xmin =
+               ((volatile LogicalDecodingCtlData*) LogicalDecodingCtl)->xmin;

Ugly.

We really should have a macro for this...

Message style. I suggest treating a short write as ENOSPC; there is
precedent elsewhere.

I don't think there's much point in including "remapping" in all of
the error messages. It adds burden for translators and users won't
know what a remapping file is anyway.

It helps in locating wich part of the code caused a problem. I utterly
hate to get reports with error messages that I can't correlate to a
sourcefile. Yes, I know that verbose error output exists, but usually
you don't get it that way... That said, I'll try to make the messages
simpler.

+       /* use *GetUpdateXid to correctly deal with multixacts */
+       xmax = HeapTupleHeaderGetUpdateXid(new_tuple->t_data);

I don't feel enlightened by that comment.

Well, it will return the update xid of a tuple locked with NO KEY SHARE
and updated (with NO KEY UPDATE). I.e. 9.3+ foreign key locking stuff.

+       if (!TransactionIdIsNormal(xmax))
+       {
+               /*
+                * no xmax is set, can't have any permanent ones, so
this check is
+                * sufficient
+                */
+       }
+       else if (HEAP_XMAX_IS_LOCKED_ONLY(new_tuple->t_data->t_infomask))
+       {
+               /* only locked, we don't care */
+       }
+       else if (!TransactionIdPrecedes(xmax, cutoff))
+       {
+               /* tuple has been deleted recently, log */
+               do_log_xmax = true;
+       }

Obfuscated. Rewrite without empty blocks.

I don't understand why having an empty block is less clear than
including a condition in several branches? Especially if the individual
conditions might need to be documented?

+       /*
+        * Write out buffer everytime we've too many in-memory entries.
+        */
+       if (state->rs_num_rewrite_mappings >= 1000 /* arbitrary number */)
+               logical_heap_rewrite_flush_mappings(state);

What happens if the number of items per hash table entry is small but
the number of entries is large?

rs_num_rewrite_mappings is the overall number of in-memory mappings, not
the number of per-entry mappings. That means we flush, even if all
entries have only a couple of mappings, as soon as 1000 in memory
entries have been collected. A bit simplistic, but seems okay enough?

+ /* XXX: should we warn about such files? */

Nah.

Ok, will remove that comment from a couple locations then...

+ /* XXX: we could replay the transaction and
prepare it as well. */

Should we do that?

It would allow a neat feature, namely using 2PC to make sure that a
transaction commit on all the nodes connected using changeset
extraction. But I think that's a feature for later. Its use would have
to be optional anyway.

All right, I'm giving up for now. These patches need a LOT of work on
comments and documentation to be committable, even if the underlying
architecture is basically sound.

+        * clog. If we're doing logical replication we can't do that though, so
+        * hold the lock for a moment longer.

...because why?

That's something pretty icky imo. But more in the existing HS code than
in this. Without the changes in the locking we can have the situation
that transactions are marked as running in the xl_running_xact record,
but are actually already committed. There's some code for HS that tries
to cope with that situation but I don't trust it very much, and it'd be
more complicated to make it work for logical decoding. I could reproduce
problems for the latter without those changes.

I'll add a comment explaining this.

I'm still unhappy that we're introducing logical decoding slots but no
analogue for physical replication. If we had the latter, would it
share meaningful amounts of structure with this?

Yes, I think we could mostly reuse it, we'd probably want to add a field
or two more (application_name, sync_prio?). I have been wondering
whether some of the code in replication/logical/logical.c shouldn't be
in replication/slot.c or similar. So far I've opted for leaving it in
its current place since it would have to change a bit for a more general
role.

I still think that the technical parts of properly supporting persistent
slots for physical rep aren't that hard, but that the behavioural
decisions are. I think there are primarily two things for SR that we
want to prevent using slots:
a) removal of still used WAL (i.e. maintain knowledge about a standby's
last required REDO location)
b) make hot_standby_feedback work across disconnections of the walsender
connection (i.e peg xmin, not just for catalogs though)
c) Make sure we can transport those across cascading
replication.
once those are there it's also useful to keep a bit more information
about the state of replicas:
* write, flush, apply lsn

The hard questions that I see are like:
* How do we manage standby registration? Does the admin have to do that
manually? Or does a standby register itself automatically if some config
paramter is set?
* If automatically, how do we deal with the situation that registrant
dies before noting his own identifier somewhere persistent? My best idea
is a two phase registration process where registration in phase 1 are
thrown away after a restart, but yuck.
* How do we deal with the usability wart that people *will* forget to
delete a slot when removing a node?
* What requirements do we have for transporting knowlede about this
across a failover?

I have little hope that we can resolve all that for 9.4.

+ * noncompatible way, but those are prevented both on catalog + *
tables and on user tables declared as additional catalog + * tables.

Really?

Yes, I think so, c.f. ATRewriteTables():
/*
* We don't support rewriting of system catalogs; there are too
* many corner cases and too little benefit. In particular this
* is certainly not going to work for mapped catalogs.
*/
if (IsSystemRelation(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite system relation \"%s\"",
RelationGetRelationName(OldHeap))));

if (RelationIsUsedAsCatalogTable(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite table \"%s\" used as a catalog table",
RelationGetRelationName(OldHeap))));

Do you see situations where that's not sufficient? It's not even
dependent on allow_system_table_mods ...

My eyes are starting to glaze over, so really stopping here.

There's quite a bit to do from that point, so I think you've more than
done your duty... Thanks!

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#183David Rowley
dgrowleyml@gmail.com
In reply to: Robert Haas (#176)
1 attachment(s)
Re: logical changeset generation v6.8

On Wed, Dec 11, 2013 at 1:11 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Committed #1 (again). Regarding this:

This introduced a new compiler warning on the visual studios build:
d:\postgres\b\src\backend\utils\cache\relcache.c(3958): warning C4715:
'RelationGetIndexAttrBitmap' : not all control paths return a value
[D:\Postgres\b\postgres.vcxproj]

The attached patch fixes it.

Regards

David Rowley

Show quoted text
+       /* XXX: we could also do this unconditionally, the space is used
anyway
+       if (copy_oid)
+               HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));

I would like to put in a big +1 for doing that unconditionally. I
didn't make that change before committing, but I think it'd be a very
good idea.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

relcache_fix_warning.patchapplication/octet-stream; name=relcache_fix_warning.patchDownload
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 03bbb9f..2778374 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3954,6 +3954,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 			return indexattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
+			return 0; /* keep compiler quiet */
 	}
 }
 
#184Andres Freund
andres@2ndquadrant.com
In reply to: David Rowley (#183)
Re: logical changeset generation v6.8

On 2013-12-13 20:58:24 +1300, David Rowley wrote:

On Wed, Dec 11, 2013 at 1:11 PM, Robert Haas <robertmhaas@gmail.com> wrote:
This introduced a new compiler warning on the visual studios build:
d:\postgres\b\src\backend\utils\cache\relcache.c(3958): warning C4715:
'RelationGetIndexAttrBitmap' : not all control paths return a value
[D:\Postgres\b\postgres.vcxproj]

The attached patch fixes it.

I thought we'd managed to get elog(ERROR) properly annotated as noreturn
on msvc as well?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#185David Rowley
dgrowleyml@gmail.com
In reply to: Andres Freund (#184)
Re: logical changeset generation v6.8

On Sat, Dec 14, 2013 at 12:12 AM, Andres Freund <andres@2ndquadrant.com>wrote:

On 2013-12-13 20:58:24 +1300, David Rowley wrote:

On Wed, Dec 11, 2013 at 1:11 PM, Robert Haas <robertmhaas@gmail.com>

wrote:

This introduced a new compiler warning on the visual studios build:
d:\postgres\b\src\backend\utils\cache\relcache.c(3958): warning C4715:
'RelationGetIndexAttrBitmap' : not all control paths return a value
[D:\Postgres\b\postgres.vcxproj]

The attached patch fixes it.

I thought we'd managed to get elog(ERROR) properly annotated as noreturn
on msvc as well?

It looks like this is down to the elog macro, where the elevel is being
assigned to a variable elevel_ then we're only doing pg_unreachable(); if
elevel_ >= ERROR. The compiler must not be confident enough to optimise out
the if condition even though the elevel is not changed after it is set from
the constant.

Regards

David Rowley

Show quoted text

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#186Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#182)
Re: logical changeset generation v6.8

On Wed, Dec 11, 2013 at 7:08 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't think there's much point in including "remapping" in all of
the error messages. It adds burden for translators and users won't
know what a remapping file is anyway.

It helps in locating wich part of the code caused a problem. I utterly
hate to get reports with error messages that I can't correlate to a
sourcefile. Yes, I know that verbose error output exists, but usually
you don't get it that way... That said, I'll try to make the messages
simpler.

Well, we could adopt a policy of not making messages originating from
different locations in the code the same. However, it looks to me
like that's NOT the current policy, because some care has been taken
to reuse messages rather than distinguish them. There's no hard and
fast rule here, because some cases are distinguished, but my gut
feeling is that all of the errors your patch introduces are
sufficiently obscure cases that separate messages with separate
translations are not warranted. The time when this is likely to fail
is when someone borks the permissions on the data directory, and the
user probably won't need to care exactly which file we can't write.

+       if (!TransactionIdIsNormal(xmax))
+       {
+               /*
+                * no xmax is set, can't have any permanent ones, so
this check is
+                * sufficient
+                */
+       }
+       else if (HEAP_XMAX_IS_LOCKED_ONLY(new_tuple->t_data->t_infomask))
+       {
+               /* only locked, we don't care */
+       }
+       else if (!TransactionIdPrecedes(xmax, cutoff))
+       {
+               /* tuple has been deleted recently, log */
+               do_log_xmax = true;
+       }

Obfuscated. Rewrite without empty blocks.

I don't understand why having an empty block is less clear than
including a condition in several branches? Especially if the individual
conditions might need to be documented?

It's not a coding style we typically use, to my knowledge. Of course,
what is actually clearest is a matter of opinion, but my own
experience is that such blocks typically end up seeming clearer to me
when the individual comments are joined together into a paragraph that
explains the logic in full sentences what's going on.

+       /*
+        * Write out buffer everytime we've too many in-memory entries.
+        */
+       if (state->rs_num_rewrite_mappings >= 1000 /* arbitrary number */)
+               logical_heap_rewrite_flush_mappings(state);

What happens if the number of items per hash table entry is small but
the number of entries is large?

rs_num_rewrite_mappings is the overall number of in-memory mappings, not
the number of per-entry mappings. That means we flush, even if all
entries have only a couple of mappings, as soon as 1000 in memory
entries have been collected. A bit simplistic, but seems okay enough?

Possibly, not sure yet. I need to examine that logic in more detail,
but I had trouble following it and was hoping the next version would
be better-commented.

I'm still unhappy that we're introducing logical decoding slots but no
analogue for physical replication. If we had the latter, would it
share meaningful amounts of structure with this?

Yes, I think we could mostly reuse it, we'd probably want to add a field
or two more (application_name, sync_prio?). I have been wondering
whether some of the code in replication/logical/logical.c shouldn't be
in replication/slot.c or similar. So far I've opted for leaving it in
its current place since it would have to change a bit for a more general
role.

I strongly favor moving the slot-related code to someplace with "slot"
in the name, and replication/slot.c seems about right. Even if we
don't extend them to cover non-logical replication in this release,
we'll probably do it eventually, and it'd be better if that didn't
require moving large amounts of code between files.

I still think that the technical parts of properly supporting persistent
slots for physical rep aren't that hard, but that the behavioural
decisions are. I think there are primarily two things for SR that we
want to prevent using slots:
a) removal of still used WAL (i.e. maintain knowledge about a standby's
last required REDO location)

Check.

b) make hot_standby_feedback work across disconnections of the walsender
connection (i.e peg xmin, not just for catalogs though)

Check; might need to be optional.

c) Make sure we can transport those across cascading
replication.

Not sure I follow.

once those are there it's also useful to keep a bit more information
about the state of replicas:
* write, flush, apply lsn

Check.

The hard questions that I see are like:
* How do we manage standby registration? Does the admin have to do that
manually? Or does a standby register itself automatically if some config
paramter is set?
* If automatically, how do we deal with the situation that registrant
dies before noting his own identifier somewhere persistent? My best idea
is a two phase registration process where registration in phase 1 are
thrown away after a restart, but yuck.

If you don't know the answers to these questions for the kind of
replication that we have now, then how do you know the answers for
logical replication? Conversely, what makes the answers that you've
selected for logical replication unsuitable for our existing
replication?

I have to admit that before I saw your design for the logical
replication slots, the problem of making this work for our existing
replication stuff seemed almost intractable to me; I had no idea how
that was going to work. GUCs didn't seem suitable because I thought we
might need some data that is tabular in nature - i.e. configuration
specific to each slot. And GUC has problems with that sort of thing.
And a new system catalog didn't seem good either, because you are
highly likely to want different configuration on the standby vs. on
the master. But after I saw your design for the logical slots I said,
dude, get me some of that. Keeping the data in shared memory,
persisting them across shutdowns, and managing them via either
function calls or the replication command language seems perfect.

Now, in terms of how registration works, whether for physical
replication or logical, it seems to me that the DBA will have to be
responsible for telling each client the name of the slot to which that
client should connect (omitting it at said DBA's option if, as for
physical replication, the slot is not mandatory). Assuming such a
design, clients could elect for one of two possible behaviors when the
anticipated slot doesn't exist: either they error out, or they create
it. Either is reasonable; we could even support both. A third
alternative is to make each client responsible for generating a name,
but I think that's probably not as good. If we went that route, the
name would presumably be some kind of random string, which will
probably be a lot less usable than a DBA-assigned name. The client
would first generate it, second save it somewhere persistent (like a
file in the data directory), and third create a slot by that name. If
the client crashes before creating the slot, it will find the name in
its persistent store after restart and, upon discovering that no such
slot exists, try again to create it.

But note that no matter which of those three options we pick, the
server support really need not work any differently. I can imagine
any of them being useful and I don't care all that much which one we
end up with. My personal preference is probably for manual
registration: if the DBA wants to use slots, said DBA will need to set
them up. This also mitigates the issue you raise in your next point:
what is manually created will naturally also need to be manually
destroyed.

* How do we deal with the usability wart that people *will* forget to
delete a slot when removing a node?

Aside from the point mentioned in the previous paragraph, we don't.
The fact that a standby which gets too far behind can't recover is a
usability wart, too. I think this is a bit like asking what happens
if a user keeps inserting data of only ephemeral value into a table
and never deletes any of it as they ought to have done. Well, they'll
be wasting disk space; potentially, they will fill the disk. Sucks to
be them. The solution is not to prohibit inserting data.

* What requirements do we have for transporting knowlede about this
across a failover?

I have little hope that we can resolve all that for 9.4.

I wouldn't consider this a requirement for a useful feature, and if it
is a requirement, then how are you solving it for logical replication?
For physical replication, there's basically only one event that needs
to be considered: master dead, promote standby. That scenario can
also happen in logical replication... but there's a whole host of
other things that can happen, too. An individual replication solution
may be single-writer but may, like Slony, allow that write authority
to be moved around. Or it may have multiple writers.

Suppose A, B, and C replicate between themselves using mult-master
replication for write availability across geographies. Within each
geo, A physically replicates to A', B to B', and C to C'. There is
also a reporting server D which replicates selected tables from each
of A, B, and C. On a particularly bad day, D is switched to replicate
a particular table from B rather than A while at the same time B is
failed over to B' in the midst of a network outage that renders B and
B' only intermittently network-accessible, leading to a high rate of
multi-master conflicts with C that require manual resolution, which
doesn't happen until the following week. In the meantime, B' is
failed back to B and C is temporarily removed from the multi-master
cluster and allowed to run standalone. If you can develop a
replication solution that leaves D with the correct data at the end of
all of that, my hat is off to you ... and the problem of getting all
this to work when only physical replication is in use and then number
of possible scenarios is much less ought to seem simple by comparison.

But whether it does or not, I don't see why we have to solve it in
this release. Following standby promotions, etc. is a whole feature,
or several, unto itself.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#187Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#186)
Re: logical changeset generation v6.8

Hi Robert,

On 2013-12-16 00:53:10 -0500, Robert Haas wrote:

On Wed, Dec 11, 2013 at 7:08 PM, Andres Freund <andres@2ndquadrant.com> wrote:

I don't think there's much point in including "remapping" in all of
the error messages. It adds burden for translators and users won't
know what a remapping file is anyway.

It helps in locating wich part of the code caused a problem. I utterly
hate to get reports with error messages that I can't correlate to a
sourcefile. Yes, I know that verbose error output exists, but usually
you don't get it that way... That said, I'll try to make the messages
simpler.

Well, we could adopt a policy of not making messages originating from
different locations in the code the same. However, it looks to me
like that's NOT the current policy, because some care has been taken
to reuse messages rather than distinguish them.

To me that mostly looks like cases where we either don't want to tell
more for various security-ish purposes or where they have been copy and
pasted...

There's no hard and
fast rule here, because some cases are distinguished, but my gut
feeling is that all of the errors your patch introduces are
sufficiently obscure cases that separate messages with separate
translations are not warranted.

Perhaps we should just introduce a marker that some such strings are not
to be translated if they are of the unexpected kind. That would probably
make debugging easier too ;)

I need to examine that logic in more detail,
but I had trouble following it and was hoping the next version would
be better-commented.

Yea, I've started expanding the comments about the higher level concerns
- I've been so knee deep in this that I didn't realize they weren't
there.

b) make hot_standby_feedback work across disconnections of the walsender
connection (i.e peg xmin, not just for catalogs though)

Check; might need to be optional.

Yea, I am pretty sure it will. It'd probably pretty nasty to set
min_recovery_apply_delay=7d and force xmin kept to that...

c) Make sure we can transport those across cascading
replication.

Not sure I follow.

Consider a replication scenario like primary <-> standby-1 <->
standby-2. The primary may not only not remove data that standby-1
requires, but also not data that standby-2 needs. Not really necessary
for WAL since that will also reside on standby-1 but definitely for the
xmin horizon.
So standby-1 will need to signal not only his own needs, but also of the
nodes below.

The hard questions that I see are like:
* How do we manage standby registration? Does the admin have to do that
manually? Or does a standby register itself automatically if some config
paramter is set?
* If automatically, how do we deal with the situation that registrant
dies before noting his own identifier somewhere persistent? My best idea
is a two phase registration process where registration in phase 1 are
thrown away after a restart, but yuck.

If you don't know the answers to these questions for the kind of
replication that we have now, then how do you know the answers for
logical replication? Conversely, what makes the answers that you've
selected for logical replication unsuitable for our existing
replication?

There's a pretty fundamental difference imo - with the logical decoding
stuff we only supply support for change producing nodes, with physical
rep we supply both.
There's no need to decide about the way node ids are stored in in-core logical
rep. consumers since there are no in-core ones. Yet. Also, physical rep
by now is a pretty established thing, we need to be much more careful
about compatibility there.

I have to admit that before I saw your design for the logical
replication slots, the problem of making this work for our existing
replication stuff seemed almost intractable to me; I had no idea how
that was going to work.

Believe me, it caused me some headaches to deceive it for decoding
too. Oh, and I think I watched just about all episodes of some stupid TV
show during it ;)

Keeping the data in shared memory,
persisting them across shutdowns, and managing them via either
function calls or the replication command language seems perfect.

Thanks. I think the concept has quite some merits. The implementation is
a bit simplistic atm, we e.g. might want to work harder at coalescing
fsync()s and such, but that's a further step when we see whether it's
worthwile in the real world.

Now, in terms of how registration works, whether for physical
replication or logical, it seems to me that the DBA will have to be
responsible for telling each client the name of the slot to which that
client should connect (omitting it at said DBA's option if, as for
physical replication, the slot is not mandatory).

It seems reasonable to me to reuse the application_name for the slot's
name, similar to the way it's used for synchronous rep. It seems odd to
use two different ways to identify nodes. t should probably only be part
of final slot name though, with the rest being autogenerated.

Assuming such a design, clients could elect for one of two possible
behaviors when the anticipated slot doesn't exist: either they error
out, or they create it. Either is reasonable; we could even support
both.

For physical rep, I don't see too much argument for not autogenerating
it. The one I can see it that it makes accidentally changing a slots
name easier, with the consequence of leaving a unused slot around.

Athird
alternative is to make each client responsible for generating a name,
but I think that's probably not as good. If we went that route, the
name would presumably be some kind of random string, which will
probably be a lot less usable than a DBA-assigned name. The client
would first generate it, second save it somewhere persistent (like a
file in the data directory), and third create a slot by that name. If
the client crashes before creating the slot, it will find the name in
its persistent store after restart and, upon discovering that no such
slot exists, try again to create it.

I thought we'd need to go that route for a while, but I struggled
exactly with the kind of races you desribe. I'd already toyed with ideas
of making slots "ephemeral" intially, until they get confirmed by the
standby. Essentially reinventing 2PC...
Not needing to solve those problems sounds like a good idea.

But note that no matter which of those three options we pick, the
server support really need not work any differently.

Yea, that part isn't worrying me overly much - there's really not much
beside passing the slot name before/in START_REPLICATION that needs to
be done.

* How do we deal with the usability wart that people *will* forget to
delete a slot when removing a node?

Aside from the point mentioned in the previous paragraph, we don't.
The fact that a standby which gets too far behind can't recover is a
usability wart, too. I think this is a bit like asking what happens
if a user keeps inserting data of only ephemeral value into a table
and never deletes any of it as they ought to have done. Well, they'll
be wasting disk space; potentially, they will fill the disk. Sucks to
be them. The solution is not to prohibit inserting data.

I am perfectly happy with taking that stance - in previous discussions
some (most notably Peter G.) argued ardently against it though.

I think we need to improve the monitoring facilities a bit, and that
should be it. Like
* expose xmin in pg_stat_activity, pg_prepared_xacts,
pg_replication_slots (or whatever it's going to be called)
* expose the last restartpoint's redo pointer in pg_stat_replication, pg_replication_slots

That said, the consequences can be a bit harsher than a full disk - the
anti-wraparound security measures might kick in requiring a restart into
single user mode. That's way more confusing than cleaning up a bit of
space on the disk.

* What requirements do we have for transporting knowlede about this
across a failover?

I wouldn't consider this a requirement for a useful feature, and if it
is a requirement, then how are you solving it for logical replication?

Failover for physical rep and for logical rep are pretty different
beasts imo - I don't think they can easily be compared. Especially since
we won't deliver a full logical rep solution and as you say it will look
pretty different depending on the individual solution.

Consider what happens though, if you promote a node for physical rep. As
soon as it's promoted, it will accept writes and then start a
checkpoint. Unless other standbys have started to follow that node
before either that checkpoint happens (removing WAL) or
autovacuuming/hot-pruning is performed (creating recovery conflicts),
we'll possibly loose the data required to let the standbys follow the
promotion. Note that wal_keep_segments and vacuum_defer_cleanup_age both
sorta work for that...

Could somebody please deliver me a time dilation device?

Regards,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#188Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#187)
Re: logical changeset generation v6.8

On Mon, Dec 16, 2013 at 6:01 AM, Andres Freund <andres@2ndquadrant.com> wrote:

There's no hard and
fast rule here, because some cases are distinguished, but my gut
feeling is that all of the errors your patch introduces are
sufficiently obscure cases that separate messages with separate
translations are not warranted.

Perhaps we should just introduce a marker that some such strings are not
to be translated if they are of the unexpected kind. That would probably
make debugging easier too ;)

Well, we have that: it's called elog. But that doesn't seem like the
right thing here.

b) make hot_standby_feedback work across disconnections of the walsender
connection (i.e peg xmin, not just for catalogs though)

Check; might need to be optional.

Yea, I am pretty sure it will. It'd probably pretty nasty to set
min_recovery_apply_delay=7d and force xmin kept to that...

Yes, that would be... unfortunate.

c) Make sure we can transport those across cascading
replication.

Not sure I follow.

Consider a replication scenario like primary <-> standby-1 <->
standby-2. The primary may not only not remove data that standby-1
requires, but also not data that standby-2 needs. Not really necessary
for WAL since that will also reside on standby-1 but definitely for the
xmin horizon.
So standby-1 will need to signal not only his own needs, but also of the
nodes below.

True.

The hard questions that I see are like:
* How do we manage standby registration? Does the admin have to do that
manually? Or does a standby register itself automatically if some config
paramter is set?
* If automatically, how do we deal with the situation that registrant
dies before noting his own identifier somewhere persistent? My best idea
is a two phase registration process where registration in phase 1 are
thrown away after a restart, but yuck.

If you don't know the answers to these questions for the kind of
replication that we have now, then how do you know the answers for
logical replication? Conversely, what makes the answers that you've
selected for logical replication unsuitable for our existing
replication?

There's a pretty fundamental difference imo - with the logical decoding
stuff we only supply support for change producing nodes, with physical
rep we supply both.

I'm not sure I follow this. "Both" what and what?

There's no need to decide about the way node ids are stored in in-core logical
rep. consumers since there are no in-core ones. Yet.

I don't know that we have or need to make any judgements about how to
store node IDs. You have decided that slots have names, and I see no
problem there.

Also, physical rep
by now is a pretty established thing, we need to be much more careful
about compatibility there.

I don't think we should change anything in backward-incompatible
fashion. If we add any new behavior, it'd surely be optional.

I think we need to improve the monitoring facilities a bit, and that
should be it. Like
* expose xmin in pg_stat_activity, pg_prepared_xacts,
pg_replication_slots (or whatever it's going to be called)
* expose the last restartpoint's redo pointer in pg_stat_replication, pg_replication_slots

+1.

That said, the consequences can be a bit harsher than a full disk - the
anti-wraparound security measures might kick in requiring a restart into
single user mode. That's way more confusing than cleaning up a bit of
space on the disk.

Yes, true. I'm not sure what approach to that problem is best. It's
long seemed to me that well before we get to the point of shutting
down the whole cluster we ought to just start killing sessions with
old xmins. But that doesn't generalize well to prepared transactions,
which can't just be rolled back or committed without guidance; and
killing slots seems a bit dicey too.

Consider what happens though, if you promote a node for physical rep. As
soon as it's promoted, it will accept writes and then start a
checkpoint. Unless other standbys have started to follow that node
before either that checkpoint happens (removing WAL) or
autovacuuming/hot-pruning is performed (creating recovery conflicts),
we'll possibly loose the data required to let the standbys follow the
promotion. Note that wal_keep_segments and vacuum_defer_cleanup_age both
sorta work for that...

True.

Could somebody please deliver me a time dilation device?

Upon reflection, I am less concerned with actually having physical
slots in this release than I am with making sure we're not boxing
ourselves into a corner that will make them hard to add later. If
we've got a clear design that can be generalized to that case, but the
SMOP required exceeds what can be done in the time available, I am OK
to punt it. But I am not sure we're at that point yet.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#189Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#188)
Re: logical changeset generation v6.8

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Dec 16, 2013 at 6:01 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Perhaps we should just introduce a marker that some such strings are not
to be translated if they are of the unexpected kind. That would probably
make debugging easier too ;)

Well, we have that: it's called elog. But that doesn't seem like the
right thing here.

errmsg_internal?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#190Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#189)
Re: logical changeset generation v6.8

On Mon, Dec 16, 2013 at 12:21 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Dec 16, 2013 at 6:01 AM, Andres Freund <andres@2ndquadrant.com> wrote:

Perhaps we should just introduce a marker that some such strings are not
to be translated if they are of the unexpected kind. That would probably
make debugging easier too ;)

Well, we have that: it's called elog. But that doesn't seem like the
right thing here.

errmsg_internal?

There's that, too. But again, these messages are not can't-happen
scenarios. The argument is just whether to reuse existing error
message text (like "could not write file") or invent a new variation
(like "could not write remapping file"). Andres' argument (which is
valid) is that distinguished messages make it easier to troubleshoot
without needing to turn on verbose error messages. My argument (which
I think is also valid) is that a user isn't likely to know what a
remapping file is, and having more messages increases the translation
burden. Is there a project policy on this topic?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#191Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#190)
Re: logical changeset generation v6.8

Robert Haas escribi�:

There's that, too. But again, these messages are not can't-happen
scenarios. The argument is just whether to reuse existing error
message text (like "could not write file") or invent a new variation
(like "could not write remapping file"). Andres' argument (which is
valid) is that distinguished messages make it easier to troubleshoot
without needing to turn on verbose error messages. My argument (which
I think is also valid) is that a user isn't likely to know what a
remapping file is, and having more messages increases the translation
burden. Is there a project policy on this topic?

I would vote for a generic "could not write file %s" where the %s lets
the troubleshooter know the path of the file, and thus in what context
it is being read. We already have a similar case where slru.c reports
error as pertaining to "transaction 12345" but the path is
"pg_subtrans/xyz" or multixact etc; while it doesn't explicitely say
what module is raising the error, it's pretty clear from the path.

Would that not work here?

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#192Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#190)
Re: logical changeset generation v6.8

Robert Haas <robertmhaas@gmail.com> writes:

There's that, too. But again, these messages are not can't-happen
scenarios. The argument is just whether to reuse existing error
message text (like "could not write file") or invent a new variation
(like "could not write remapping file").

As long as the message includes the file name, which it surely oughta,
I don't see that we need any explanation of what Postgres thinks the
file is for. If someone cares about that they can reverse-engineer
it from the file name; while as you said upthread, most of the time
the directory path is going to be the key piece of useful information.

So +1 for "could not write file".

Andres' argument (which is
valid) is that distinguished messages make it easier to troubleshoot
without needing to turn on verbose error messages. My argument (which
I think is also valid) is that a user isn't likely to know what a
remapping file is, and having more messages increases the translation
burden. Is there a project policy on this topic?

I think Andres' argument is a thinly veiled version of "let's put the
routine name into the message text", which there definitely is project
policy against (see 49.3.13 in the message style guide). If you want to
know the code location where the error was thrown, the answer is to get
a verbose log, not to put identifying information into the user-facing
message text. And this is only partially-identifying information,
which seems like the worst of both worlds: you've got confused users and
overworked translators, and you still don't know exactly where it was
thrown from.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#193Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#186)
Re: logical changeset generation v6.8

On 2013-12-16 00:53:10 -0500, Robert Haas wrote:

Yes, I think we could mostly reuse it, we'd probably want to add a field
or two more (application_name, sync_prio?). I have been wondering
whether some of the code in replication/logical/logical.c shouldn't be
in replication/slot.c or similar. So far I've opted for leaving it in
its current place since it would have to change a bit for a more general
role.

I strongly favor moving the slot-related code to someplace with "slot"
in the name, and replication/slot.c seems about right. Even if we
don't extend them to cover non-logical replication in this release,
we'll probably do it eventually, and it'd be better if that didn't
require moving large amounts of code between files.

Any opinion on the storage location of the slot files? It's currently
pg_llog/$slotname/state[.tmp]. It's a directory so we have a location
during logical decoding to spill data to...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#194Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#193)
Re: logical changeset generation v6.8

On Tue, Dec 17, 2013 at 7:48 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-12-16 00:53:10 -0500, Robert Haas wrote:

Yes, I think we could mostly reuse it, we'd probably want to add a field
or two more (application_name, sync_prio?). I have been wondering
whether some of the code in replication/logical/logical.c shouldn't be
in replication/slot.c or similar. So far I've opted for leaving it in
its current place since it would have to change a bit for a more general
role.

I strongly favor moving the slot-related code to someplace with "slot"
in the name, and replication/slot.c seems about right. Even if we
don't extend them to cover non-logical replication in this release,
we'll probably do it eventually, and it'd be better if that didn't
require moving large amounts of code between files.

Any opinion on the storage location of the slot files? It's currently
pg_llog/$slotname/state[.tmp]. It's a directory so we have a location
during logical decoding to spill data to...

pg_replslot? pg_replication_slot?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#195Magnus Hagander
magnus@hagander.net
In reply to: Peter Geoghegan (#20)
Re: logical changeset generation v6

On Mon, Sep 23, 2013 at 7:03 PM, Peter Geoghegan <pg@heroku.com> wrote:

On Mon, Sep 23, 2013 at 9:54 AM, Andres Freund <andres@2ndquadrant.com>
wrote:

I still find it wierd/inconsistent to have:
* pg_receivexlog
* pg_recvlogical
binaries, even from the same source directory. Why once "pg_recv" and
once "pg_receive"?

+1

Digging up a really old thread since I just got annoyed by the inconsistent
naming the first time myself :)

I can't find that this discussion actually came to a proper consensus, but
I may be missing something. Did we go with pg_recvlogical just because we
couldn't decide on a better name, or did we intentionally decide it was the
best?

I definitely think pg_receivelogical would be a better name, for
consistency (because it's way too late to rename pg_receivexlog of course -
once released that can't really chance. Which is why *if* we want to change
the name of pg_recvxlog we have a few more days to make a decision..)

--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/

#196Andres Freund
andres@2ndquadrant.com
In reply to: Magnus Hagander (#195)
Re: logical changeset generation v6

On 2014-04-24 09:39:21 +0200, Magnus Hagander wrote:

I can't find that this discussion actually came to a proper consensus, but
I may be missing something. Did we go with pg_recvlogical just because we
couldn't decide on a better name, or did we intentionally decide it was the
best?

I went with pg_recvlogical because that's where the (small) majority
seemed to be. Even if I was unconvinced. There were so many outstanding
big fights at that point that I didn't want to spend my time on this ;)

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#197Magnus Hagander
magnus@hagander.net
In reply to: Andres Freund (#196)
Re: logical changeset generation v6

On Thu, Apr 24, 2014 at 9:43 AM, Andres Freund <andres@2ndquadrant.com>wrote:

On 2014-04-24 09:39:21 +0200, Magnus Hagander wrote:

I can't find that this discussion actually came to a proper consensus,

but

I may be missing something. Did we go with pg_recvlogical just because we
couldn't decide on a better name, or did we intentionally decide it was

the

best?

I went with pg_recvlogical because that's where the (small) majority
seemed to be. Even if I was unconvinced. There were so many outstanding
big fights at that point that I didn't want to spend my time on this ;)

I was guessing something like the second part there, which is why I figured
this would be a good time to bring this fight back up to the surface ;)

--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/

#198Andres Freund
andres@2ndquadrant.com
In reply to: Magnus Hagander (#197)
Re: logical changeset generation v6

On 2014-04-24 09:46:07 +0200, Magnus Hagander wrote:

On Thu, Apr 24, 2014 at 9:43 AM, Andres Freund <andres@2ndquadrant.com>wrote:

On 2014-04-24 09:39:21 +0200, Magnus Hagander wrote:

I can't find that this discussion actually came to a proper consensus,

but

I may be missing something. Did we go with pg_recvlogical just because we
couldn't decide on a better name, or did we intentionally decide it was

the

best?

I went with pg_recvlogical because that's where the (small) majority
seemed to be. Even if I was unconvinced. There were so many outstanding
big fights at that point that I didn't want to spend my time on this ;)

I was guessing something like the second part there, which is why I figured
this would be a good time to bring this fight back up to the surface ;)

I have to admit that I still don't care too much. By now I'd tentatively
want to stay with the current name because that's what I got used to,
but if somebody else has strong opinions and finds concensus...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers